diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2151320b0..3b11fa493 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,6 +27,16 @@ jobs: dashboard-api-tests: runs-on: ubuntu-latest + services: + redis: + image: redis:7-alpine + ports: + - 6379:6379 + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 permissions: contents: read steps: @@ -44,6 +54,8 @@ jobs: - name: Run Dashboard API Tests run: npm run test --workspace=dashboard-api + env: + REDIS_URL: redis://localhost:6379 public-api-tests: runs-on: ubuntu-latest diff --git a/apps/dashboard-api/src/__tests__/routes.user.test.js b/apps/dashboard-api/src/__tests__/routes.user.test.js index c8ccd5d85..f2dc36f4c 100644 --- a/apps/dashboard-api/src/__tests__/routes.user.test.js +++ b/apps/dashboard-api/src/__tests__/routes.user.test.js @@ -48,6 +48,25 @@ jest.mock('../controllers/pat.controller', () => ({ revokePAT: jest.fn((_req, res) => res.json({ success: true })) })); +jest.mock('../middlewares/authenticateCLI', () => + jest.fn((req, _res, next) => { + req.user = { _id: 'mock_user_id', id: 'mock_user_id' }; + req.cliScopes = ['read', 'write']; + next(); + }) +); + +jest.mock('../middlewares/authFlexible', () => + jest.fn((req, _res, next) => { + req.user = { _id: 'mock_user_id', id: 'mock_user_id' }; + next(); + }) +); + +jest.mock('../controllers/cli.controller', () => ({ + getCLIProfile: jest.fn((_req, res) => res.json({ success: true, data: {} })) +})); + const express = require('express'); const request = require('supertest'); const userRouter = require('../routes/user'); diff --git a/apps/dashboard-api/src/app.js b/apps/dashboard-api/src/app.js index abbda5828..77c7c95a7 100644 --- a/apps/dashboard-api/src/app.js +++ b/apps/dashboard-api/src/app.js @@ -75,15 +75,18 @@ const csrfProtection = csurf({ } }); +// AFTER app.use((req, res, next) => { // Exclude Razorpay webhook from CSRF protection since it's an external POST request - if (req.path === '/api/billing/webhook') { + // Exclude CLI routes — CLI authenticates via Bearer PAT, not cookies + const isCliRoute = + req.path === '/api/user/cli' || req.path.startsWith('/api/user/cli/'); + if (req.path === '/api/billing/webhook' || isCliRoute) { return next(); } csrfProtection(req, res, next); }); - if (process.env.NODE_ENV !== 'test') { garbageCollect(); storageGarbageCollect(); diff --git a/apps/dashboard-api/src/controllers/cli.controller.js b/apps/dashboard-api/src/controllers/cli.controller.js new file mode 100644 index 000000000..af1a9b1b1 --- /dev/null +++ b/apps/dashboard-api/src/controllers/cli.controller.js @@ -0,0 +1,28 @@ +const { Developer, ApiResponse, AppError } = require("@urbackend/common"); + +module.exports.getCLIProfile = async (req, res, next) => { + try { + const developer = await Developer.findById(req.user._id) + .select("email plan githubUsername avatarUrl"); + + if (!developer) { + return next(new AppError(404, "Developer not found")); + } + + return new ApiResponse({ + developer: { + id: developer._id, + email: developer.email, + plan: developer.plan, + githubUsername: developer.githubUsername, + avatarUrl: developer.avatarUrl, + }, + auth: { + scopes: req.cliScopes, + tokenType: req.cliTokenType, + }, + }).send(res); + } catch (err) { + next(err); + } +}; \ No newline at end of file diff --git a/apps/dashboard-api/src/controllers/pat.controller.js b/apps/dashboard-api/src/controllers/pat.controller.js index 60aebf8b1..d35492deb 100644 --- a/apps/dashboard-api/src/controllers/pat.controller.js +++ b/apps/dashboard-api/src/controllers/pat.controller.js @@ -93,3 +93,4 @@ exports.revokePAT = async (req, res, next) => { return next(new AppError(500, "An error occurred while revoking the token")); } }; + diff --git a/apps/dashboard-api/src/middlewares/authFlexible.js b/apps/dashboard-api/src/middlewares/authFlexible.js new file mode 100644 index 000000000..d1a9c702f --- /dev/null +++ b/apps/dashboard-api/src/middlewares/authFlexible.js @@ -0,0 +1,19 @@ +const authMiddleware = require('./authMiddleware'); +const authenticateCLI = require('./authenticateCLI'); + +/** + * Flexible auth middleware — accepts either: + * - Session cookie (browser dashboard) + * - Bearer PAT token (CLI) + */ +const authFlexible = (req, res, next) => { + const authHeader = req.headers.authorization; + + if (authHeader && authHeader.startsWith('Bearer ubpat_')) { + return authenticateCLI(req, res, next); + } + + return authMiddleware(req, res, next); +}; + +module.exports = authFlexible; \ No newline at end of file diff --git a/apps/dashboard-api/src/middlewares/authMiddleware.js b/apps/dashboard-api/src/middlewares/authMiddleware.js index bee883a65..7241914bd 100644 --- a/apps/dashboard-api/src/middlewares/authMiddleware.js +++ b/apps/dashboard-api/src/middlewares/authMiddleware.js @@ -31,11 +31,10 @@ module.exports = function (req, res, next) { // Proceed to the next middleware or route handler next(); - } catch (err) { - if (process.env.NODE_ENV !== 'test') { - console.error(err); - } - - return next(new AppError(401, 'Invalid Token')); + } catch (err) { + if (process.env.NODE_ENV !== 'test') { + console.error(err); } + return next(new AppError(401, "Invalid Token")); +} }; diff --git a/apps/dashboard-api/src/middlewares/authenticateCLI.js b/apps/dashboard-api/src/middlewares/authenticateCLI.js index 6b987aa05..676e3b99c 100644 --- a/apps/dashboard-api/src/middlewares/authenticateCLI.js +++ b/apps/dashboard-api/src/middlewares/authenticateCLI.js @@ -110,7 +110,7 @@ const authenticateCLI = async (req, res, next) => { ).catch(err => console.error("Failed to update PAT metadata:", err)); // Attach developer and PAT scopes for downstream controllers - req.user = { id: developer._id.toString() }; // Maintain compatibility with dashboard controllers + req.user = { _id: developer._id.toString(), id: developer._id.toString() }; // Maintain compatibility with dashboard controllers req.developer = developer; req.cliScopes = matchedPat.scopes; req.cliTokenType = matchedPat.type; diff --git a/apps/dashboard-api/src/routes/analytics.js b/apps/dashboard-api/src/routes/analytics.js index 6ad929e7f..fe5ce223b 100644 --- a/apps/dashboard-api/src/routes/analytics.js +++ b/apps/dashboard-api/src/routes/analytics.js @@ -2,9 +2,10 @@ const express = require("express"); const router = express.Router(); const analyticsController = require("../controllers/analytics.controller"); const authMiddleware = require("../middlewares/authMiddleware"); +const authFlexible = require("../middlewares/authFlexible"); -router.get("/stats", authMiddleware, analyticsController.getGlobalStats); -router.get("/activity", authMiddleware, analyticsController.getRecentActivity); +router.get("/stats", authFlexible, analyticsController.getGlobalStats); +router.get("/activity", authFlexible, analyticsController.getRecentActivity); // --- Metrics Stack --- router.get("/funnel", authMiddleware, analyticsController.getActivationFunnel); diff --git a/apps/dashboard-api/src/routes/cli.routes.ts b/apps/dashboard-api/src/routes/cli.routes.ts new file mode 100644 index 000000000..e69de29bb diff --git a/apps/dashboard-api/src/routes/projects.js b/apps/dashboard-api/src/routes/projects.js index b66377c13..7dc8988f4 100644 --- a/apps/dashboard-api/src/routes/projects.js +++ b/apps/dashboard-api/src/routes/projects.js @@ -6,6 +6,7 @@ const authorizeProject = require('../middlewares/authorizeProject'); const { verifyEmail, checkAuthEnabled, loadProjectForAdmin } = require('@urbackend/common'); const multer = require('multer'); const storage = multer.memoryStorage(); +const authFlexible = require('../middlewares/authFlexible'); const { createProject, @@ -61,15 +62,15 @@ const exportController = require('../controllers/dbExport.controller'); // POST REQ FOR CREATE PROJECT router.post('/', authMiddleware, planEnforcement.attachDeveloper, planEnforcement.checkProjectLimit, planEnforcement.checkDeveloperCapability('createProject'), createProject); -router.get('/', authMiddleware, getAllProject); -router.get('/:projectId', authMiddleware, authorizeProject(), getSingleProject); +router.get('/', authFlexible, getAllProject); +router.get('/:projectId', authFlexible, authorizeProject(), getSingleProject); router.post('/:projectId/api-key', authMiddleware, authorizeProject('admin'), verifyEmail, regenerateApiKey); router.post('/:projectId/reveal-secret-key', authMiddleware, authorizeProject('admin'), verifyEmail, revealSecretKey); router.post('/:projectId/collections', authMiddleware, authorizeProject('admin'), planEnforcement.attachDeveloper, planEnforcement.checkCollectionLimit, createCollection); // DELETE REQ FOR COLLECTION -router.delete('/:projectId/collections/:collectionName', authMiddleware, authorizeProject('admin'), verifyEmail, deleteCollection); +router.delete('/:projectId/collections/:collectionName', authFlexible, authorizeProject('admin'), verifyEmail, deleteCollection); // GET REQ FOR DATA router.get('/:projectId/collections/:collectionName/data', authMiddleware, authorizeProject(), getData); diff --git a/apps/dashboard-api/src/routes/user.js b/apps/dashboard-api/src/routes/user.js index 640e9338f..43ffe037b 100644 --- a/apps/dashboard-api/src/routes/user.js +++ b/apps/dashboard-api/src/routes/user.js @@ -3,7 +3,10 @@ const router = express.Router(); const authorization = require('../middlewares/authMiddleware'); const { getMe, updateOnboarding } = require('../controllers/auth.controller'); const { createPAT, listPATs, revokePAT } = require('../controllers/pat.controller'); +const authenticateCLI = require("../middlewares/authenticateCLI"); +const {getCLIProfile,} = require("../controllers/cli.controller"); +router.get("/cli/me", authenticateCLI, getCLIProfile); router.get('/me', authorization, getMe); router.patch('/onboarding', authorization, updateOnboarding); diff --git a/package-lock.json b/package-lock.json index b4ff0db88..a00ddc3e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1147,7 +1147,6 @@ "@dnd-kit/modifiers": "^9.0.0", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", - "@tailwindcss/oxide-linux-x64-gnu": "*", "@tailwindcss/vite": "^4.1.18", "@tanstack/react-table": "^8.21.3", "@vercel/analytics": "^2.0.1", @@ -2992,7 +2991,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3026,7 +3024,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3060,7 +3057,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3126,7 +3122,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5392,6 +5387,13 @@ "@types/ms": "*" } }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "license": "MIT" @@ -5747,6 +5749,10 @@ "win32" ] }, + "node_modules/@urbackend/cli": { + "resolved": "sdks/urbackend-cli", + "link": true + }, "node_modules/@urbackend/common": { "resolved": "packages/common", "link": true @@ -5778,6 +5784,219 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/@vitest/expect": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.1.tgz", + "integrity": "sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "1.6.1", + "@vitest/utils": "1.6.1", + "chai": "^4.3.10" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.1.tgz", + "integrity": "sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "1.6.1", + "p-limit": "^5.0.0", + "pathe": "^1.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner/node_modules/p-limit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", + "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/runner/node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/runner/node_modules/yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/snapshot": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.1.tgz", + "integrity": "sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot/node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@vitest/snapshot/node_modules/@sinclair/typebox": { + "version": "0.27.10", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/snapshot/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@vitest/snapshot/node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/snapshot/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@vitest/spy": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.1.tgz", + "integrity": "sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^2.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.1.tgz", + "integrity": "sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "diff-sequences": "^29.6.3", + "estree-walker": "^3.0.3", + "loupe": "^2.3.7", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils/node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@vitest/utils/node_modules/@sinclair/typebox": { + "version": "0.27.10", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/utils/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@vitest/utils/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/accepts": { "version": "2.0.0", "license": "MIT", @@ -5943,6 +6162,16 @@ "dev": true, "license": "MIT" }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/asynckit": { "version": "0.4.0", "license": "MIT" @@ -6382,6 +6611,35 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/chai": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", + "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chai/node_modules/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/chalk": { "version": "4.1.2", "dev": true, @@ -6450,6 +6708,8 @@ }, "node_modules/check-error": { "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", "dev": true, "license": "MIT", "dependencies": { @@ -7021,6 +7281,8 @@ }, "node_modules/deep-eql": { "version": "4.1.4", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", + "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", "dev": true, "license": "MIT", "dependencies": { @@ -7171,6 +7433,8 @@ }, "node_modules/diff-sequences": { "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", "dev": true, "license": "MIT", "engines": { @@ -7418,7 +7682,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7435,7 +7698,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7452,7 +7714,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7469,7 +7730,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7486,7 +7746,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7503,7 +7762,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7520,7 +7778,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7537,7 +7794,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7554,7 +7810,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7571,7 +7826,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7588,7 +7842,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7605,7 +7858,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7622,7 +7874,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7639,7 +7890,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7656,7 +7906,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7673,7 +7922,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7690,7 +7938,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7707,7 +7954,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7724,7 +7970,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7741,7 +7986,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7758,7 +8002,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7775,7 +8018,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -8041,6 +8283,8 @@ }, "node_modules/estree-walker": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, "license": "MIT", "dependencies": { @@ -8587,6 +8831,8 @@ }, "node_modules/get-func-name": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", "dev": true, "license": "MIT", "engines": { @@ -10662,6 +10908,8 @@ }, "node_modules/loupe": { "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", "dev": true, "license": "MIT", "dependencies": { @@ -12231,6 +12479,8 @@ }, "node_modules/pathval": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", "dev": true, "license": "MIT", "engines": { @@ -13543,6 +13793,13 @@ "node": ">= 0.8" } }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", "dev": true, @@ -13990,6 +14247,8 @@ }, "node_modules/tinyspy": { "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", + "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", "dev": true, "license": "MIT", "engines": { @@ -14694,159 +14953,479 @@ } } }, - "node_modules/w3c-xmlserializer": { - "version": "5.0.0", + "node_modules/vitest": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.1.tgz", + "integrity": "sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==", "dev": true, "license": "MIT", "dependencies": { - "xml-name-validator": "^5.0.0" + "@vitest/expect": "1.6.1", + "@vitest/runner": "1.6.1", + "@vitest/snapshot": "1.6.1", + "@vitest/spy": "1.6.1", + "@vitest/utils": "1.6.1", + "acorn-walk": "^8.3.2", + "chai": "^4.3.10", + "debug": "^4.3.4", + "execa": "^8.0.1", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "strip-literal": "^2.0.0", + "tinybench": "^2.5.1", + "tinypool": "^0.8.3", + "vite": "^5.0.0", + "vite-node": "1.6.1", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" }, "engines": { - "node": ">=18" + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "1.6.1", + "@vitest/ui": "1.6.1", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } } }, - "node_modules/walker": { - "version": "1.0.8", + "node_modules/vitest/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "makeerror": "1.0.12" - } - }, - "node_modules/web-dashboard": { - "resolved": "apps/web-dashboard", - "link": true - }, - "node_modules/webidl-conversions": { - "version": "7.0.0", - "license": "BSD-2-Clause", + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { "node": ">=12" } }, - "node_modules/whatwg-encoding": { - "version": "3.1.1", + "node_modules/vitest/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "dev": true, + "hasInstallScript": true, "license": "MIT", - "dependencies": { - "iconv-lite": "0.6.3" + "bin": { + "esbuild": "bin/esbuild" }, "engines": { - "node": ">=18" + "node": ">=12" + }, + "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" } }, - "node_modules/whatwg-encoding/node_modules/iconv-lite": { - "version": "0.6.3", + "node_modules/vitest/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", "dev": true, "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/whatwg-mimetype": { - "version": "4.0.0", + "node_modules/vitest/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", "dev": true, "license": "MIT", "engines": { - "node": ">=18" - } - }, - "node_modules/whatwg-url": { - "version": "14.2.0", - "license": "MIT", - "dependencies": { - "tr46": "^5.1.0", - "webidl-conversions": "^7.0.0" + "node": ">=16" }, - "engines": { - "node": ">=18" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/which": { - "version": "2.0.2", + "node_modules/vitest/node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, + "license": "Apache-2.0", "engines": { - "node": ">= 8" + "node": ">=16.17.0" } }, - "node_modules/which-boxed-primitive": { - "version": "1.1.1", + "node_modules/vitest/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", "dev": true, "license": "MIT", - "dependencies": { - "is-bigint": "^1.1.0", - "is-boolean-object": "^1.2.1", - "is-number-object": "^1.1.1", - "is-string": "^1.1.1", - "is-symbol": "^1.1.1" - }, "engines": { - "node": ">= 0.4" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/which-collection": { - "version": "1.0.2", + "node_modules/vitest/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", "dev": true, "license": "MIT", - "dependencies": { - "is-map": "^2.0.3", - "is-set": "^2.0.3", - "is-weakmap": "^2.0.2", - "is-weakset": "^2.0.3" - }, "engines": { - "node": ">= 0.4" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/which-typed-array": { - "version": "1.1.21", + "node_modules/vitest/node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", "dev": true, "license": "MIT", "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.9", - "call-bound": "^1.0.4", - "for-each": "^0.3.5", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2" + "path-key": "^4.0.0" }, "engines": { - "node": ">= 0.4" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/why-is-node-running": { - "version": "2.3.0", + "node_modules/vitest/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", "dev": true, "license": "MIT", "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" + "mimic-fn": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vitest/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vitest/node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vitest/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vitest/node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/web-dashboard": { + "resolved": "apps/web-dashboard", + "link": true + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "version": "2.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.21", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" } }, "node_modules/word-wrap": { @@ -15685,58 +16264,72 @@ "url": "https://github.com/sponsors/colinhacks" } }, - "sdks/urbackend-react": { - "name": "@urbackend/react", - "version": "0.2.4", + "sdks/urbackend-cli": { + "name": "@urbackend/cli", + "version": "0.1.0", + "license": "MIT", "dependencies": { - "@urbackend/sdk": "^0.4.4" + "commander": "^12.1.0" + }, + "bin": { + "ub": "bin/ub.js" }, "devDependencies": { - "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^14.0.0", - "@types/react": "^18.2.0", - "@types/react-dom": "^18.2.0", - "jsdom": "^24.0.0", - "tsup": "^8.0.0", - "typescript": "^5.0.0", - "vitest": "^1.6.0" + "@types/node": "^22.0.0", + "tsup": "^8.5.1", + "typescript": "^5.7.2" }, - "peerDependencies": { - "react": ">=17.0.0", - "react-dom": ">=17.0.0" + "engines": { + "node": ">=20.0.0" } }, - "sdks/urbackend-react/node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "cpu": [ - "x64" - ], + "sdks/urbackend-cli/node_modules/@types/node": { + "version": "22.20.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.0.tgz", + "integrity": "sha512-QWlFW2wf3nTjC13/DqRnBpR4ZO36VJH/JVBkA/vcnmbTBNQIlnObqyqZE1tUR7+Ni23Lda8R1BxMfbXRpCUx5g==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" + "dependencies": { + "undici-types": "~6.21.0" } }, - "sdks/urbackend-react/node_modules/@jest/schemas": { - "version": "29.6.3", - "dev": true, + "sdks/urbackend-cli/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=18" } }, - "sdks/urbackend-react/node_modules/@sinclair/typebox": { - "version": "0.27.10", + "sdks/urbackend-cli/node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "dev": true, "license": "MIT" }, + "sdks/urbackend-react": { + "name": "@urbackend/react", + "version": "0.2.4", + "dependencies": { + "@urbackend/sdk": "^0.4.4" + }, + "devDependencies": { + "@testing-library/jest-dom": "^6.0.0", + "@testing-library/react": "^14.0.0", + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", + "jsdom": "^24.0.0", + "tsup": "^8.0.0", + "typescript": "^5.0.0", + "vitest": "^1.6.0" + }, + "peerDependencies": { + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, "sdks/urbackend-react/node_modules/@types/react": { "version": "18.3.29", "dev": true, @@ -15754,923 +16347,243 @@ "@types/react": "^18.0.0" } }, - "sdks/urbackend-react/node_modules/@vitest/expect": { - "version": "1.6.1", - "dev": true, + "sdks/urbackend-sdk": { + "name": "@urbackend/sdk", + "version": "0.4.4", "license": "MIT", - "dependencies": { - "@vitest/spy": "1.6.1", - "@vitest/utils": "1.6.1", - "chai": "^4.3.10" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "devDependencies": { + "@eslint/js": "^10.0.1", + "@types/node": "^25.5.0", + "@typescript-eslint/eslint-plugin": "^8.57.2", + "@typescript-eslint/parser": "^8.57.2", + "@vitest/coverage-v8": "^1.6.0", + "eslint": "^10.1.0", + "prettier": "^3.8.1", + "tsup": "^8.5.1", + "typescript": "^5.9.3", + "typescript-eslint": "^8.57.2", + "vitest": "^1.6.0" } }, - "sdks/urbackend-react/node_modules/@vitest/runner": { - "version": "1.6.1", + "sdks/urbackend-sdk/node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "1.6.1", - "p-limit": "^5.0.0", - "pathe": "^1.1.1" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } + "license": "MIT" }, - "sdks/urbackend-react/node_modules/@vitest/snapshot": { - "version": "1.6.1", + "sdks/urbackend-sdk/node_modules/@eslint/config-array": { + "version": "0.23.3", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "magic-string": "^0.30.5", - "pathe": "^1.1.1", - "pretty-format": "^29.7.0" + "@eslint/object-schema": "^3.0.3", + "debug": "^4.3.1", + "minimatch": "^10.2.4" }, - "funding": { - "url": "https://opencollective.com/vitest" + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "sdks/urbackend-react/node_modules/@vitest/spy": { - "version": "1.6.1", + "sdks/urbackend-sdk/node_modules/@eslint/config-helpers": { + "version": "0.5.3", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "tinyspy": "^2.2.0" + "@eslint/core": "^1.1.1" }, - "funding": { - "url": "https://opencollective.com/vitest" + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "sdks/urbackend-react/node_modules/@vitest/utils": { - "version": "1.6.1", + "sdks/urbackend-sdk/node_modules/@eslint/core": { + "version": "1.1.1", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "diff-sequences": "^29.6.3", - "estree-walker": "^3.0.3", - "loupe": "^2.3.7", - "pretty-format": "^29.7.0" + "@types/json-schema": "^7.0.15" }, - "funding": { - "url": "https://opencollective.com/vitest" + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "sdks/urbackend-react/node_modules/ansi-styles": { - "version": "5.2.0", + "sdks/urbackend-sdk/node_modules/@eslint/js": { + "version": "10.0.1", "dev": true, "license": "MIT", "engines": { - "node": ">=10" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } } }, - "sdks/urbackend-react/node_modules/assertion-error": { - "version": "1.1.0", + "sdks/urbackend-sdk/node_modules/@eslint/object-schema": { + "version": "3.0.3", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "engines": { - "node": "*" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "sdks/urbackend-react/node_modules/chai": { - "version": "4.5.0", + "sdks/urbackend-sdk/node_modules/@eslint/plugin-kit": { + "version": "0.6.1", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.3", - "deep-eql": "^4.1.3", - "get-func-name": "^2.0.2", - "loupe": "^2.3.6", - "pathval": "^1.1.1", - "type-detect": "^4.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "sdks/urbackend-react/node_modules/esbuild": { - "version": "0.21.5", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" + "@eslint/core": "^1.1.1", + "levn": "^0.4.1" }, "engines": { - "node": ">=12" - }, - "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" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "sdks/urbackend-react/node_modules/execa": { - "version": "8.0.1", + "sdks/urbackend-sdk/node_modules/@vitest/coverage-v8": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.6.1.tgz", + "integrity": "sha512-6YeRZwuO4oTGKxD3bijok756oktHSIm3eczVVzNe3scqzuhLwltIF3S9ZL/vwOVIpURmU6SnZhziXXAfw8/Qlw==", "dev": true, "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": ">=16.17" + "@ampproject/remapping": "^2.2.1", + "@bcoe/v8-coverage": "^0.2.3", + "debug": "^4.3.4", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.4", + "istanbul-reports": "^3.1.6", + "magic-string": "^0.30.5", + "magicast": "^0.3.3", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "strip-literal": "^2.0.0", + "test-exclude": "^6.0.0" }, "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "sdks/urbackend-react/node_modules/get-stream": { - "version": "8.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" + "url": "https://opencollective.com/vitest" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "vitest": "1.6.1" } }, - "sdks/urbackend-react/node_modules/human-signals": { - "version": "5.0.0", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=16.17.0" - } - }, - "sdks/urbackend-react/node_modules/is-stream": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "sdks/urbackend-react/node_modules/mimic-fn": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "sdks/urbackend-react/node_modules/npm-run-path": { - "version": "5.3.0", + "sdks/urbackend-sdk/node_modules/eslint": { + "version": "10.1.0", "dev": true, "license": "MIT", "dependencies": { - "path-key": "^4.0.0" + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.3", + "@eslint/config-helpers": "^0.5.3", + "@eslint/core": "^1.1.1", + "@eslint/plugin-kit": "^0.6.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "sdks/urbackend-react/node_modules/onetime": { - "version": "6.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^4.0.0" + "url": "https://eslint.org/donate" }, - "engines": { - "node": ">=12" + "peerDependencies": { + "jiti": "*" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, - "sdks/urbackend-react/node_modules/p-limit": { - "version": "5.0.0", + "sdks/urbackend-sdk/node_modules/eslint-scope": { + "version": "9.1.2", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", "dependencies": { - "yocto-queue": "^1.0.0" + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" }, "engines": { - "node": ">=18" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/eslint" } }, - "sdks/urbackend-react/node_modules/path-key": { - "version": "4.0.0", + "sdks/urbackend-sdk/node_modules/eslint-visitor-keys": { + "version": "5.0.1", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "engines": { - "node": ">=12" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/eslint" } }, - "sdks/urbackend-react/node_modules/pathe": { - "version": "1.1.2", - "dev": true, - "license": "MIT" - }, - "sdks/urbackend-react/node_modules/pretty-format": { - "version": "29.7.0", + "sdks/urbackend-sdk/node_modules/espree": { + "version": "11.2.0", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "sdks/urbackend-react/node_modules/std-env": { - "version": "3.10.0", - "dev": true, - "license": "MIT" - }, - "sdks/urbackend-react/node_modules/strip-final-newline": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "sdks/urbackend-react/node_modules/type-detect": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" + "url": "https://opencollective.com/eslint" } }, - "sdks/urbackend-react/node_modules/vite": { - "version": "5.4.21", + "sdks/urbackend-sdk/node_modules/ignore": { + "version": "5.3.2", "dev": true, "license": "MIT", - "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" - }, - "bin": { - "vite": "bin/vite.js" - }, "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } + "node": ">= 4" } }, - "sdks/urbackend-react/node_modules/vitest": { - "version": "1.6.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/expect": "1.6.1", - "@vitest/runner": "1.6.1", - "@vitest/snapshot": "1.6.1", - "@vitest/spy": "1.6.1", - "@vitest/utils": "1.6.1", - "acorn-walk": "^8.3.2", - "chai": "^4.3.10", - "debug": "^4.3.4", - "execa": "^8.0.1", - "local-pkg": "^0.5.0", - "magic-string": "^0.30.5", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", - "std-env": "^3.5.0", - "strip-literal": "^2.0.0", - "tinybench": "^2.5.1", - "tinypool": "^0.8.3", - "vite": "^5.0.0", - "vite-node": "1.6.1", - "why-is-node-running": "^2.2.2" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "1.6.1", - "@vitest/ui": "1.6.1", - "happy-dom": "*", - "jsdom": "*" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - } - } - }, - "sdks/urbackend-react/node_modules/yocto-queue": { - "version": "1.2.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "sdks/urbackend-sdk": { - "name": "@urbackend/sdk", - "version": "0.4.4", - "license": "MIT", - "devDependencies": { - "@eslint/js": "^10.0.1", - "@types/node": "^25.5.0", - "@typescript-eslint/eslint-plugin": "^8.57.2", - "@typescript-eslint/parser": "^8.57.2", - "@vitest/coverage-v8": "^1.6.0", - "eslint": "^10.1.0", - "prettier": "^3.8.1", - "tsup": "^8.5.1", - "typescript": "^5.9.3", - "typescript-eslint": "^8.57.2", - "vitest": "^1.6.0" - } - }, - "sdks/urbackend-sdk/node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true, - "license": "MIT" - }, - "sdks/urbackend-sdk/node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "sdks/urbackend-sdk/node_modules/@eslint/config-array": { - "version": "0.23.3", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^3.0.3", - "debug": "^4.3.1", - "minimatch": "^10.2.4" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "sdks/urbackend-sdk/node_modules/@eslint/config-helpers": { - "version": "0.5.3", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^1.1.1" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "sdks/urbackend-sdk/node_modules/@eslint/core": { - "version": "1.1.1", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "sdks/urbackend-sdk/node_modules/@eslint/js": { - "version": "10.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "eslint": "^10.0.0" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } - } - }, - "sdks/urbackend-sdk/node_modules/@eslint/object-schema": { - "version": "3.0.3", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "sdks/urbackend-sdk/node_modules/@eslint/plugin-kit": { - "version": "0.6.1", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^1.1.1", - "levn": "^0.4.1" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "sdks/urbackend-sdk/node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "sdks/urbackend-sdk/node_modules/@sinclair/typebox": { - "version": "0.27.10", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", - "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", - "dev": true, - "license": "MIT" - }, - "sdks/urbackend-sdk/node_modules/@vitest/coverage-v8": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.6.1.tgz", - "integrity": "sha512-6YeRZwuO4oTGKxD3bijok756oktHSIm3eczVVzNe3scqzuhLwltIF3S9ZL/vwOVIpURmU6SnZhziXXAfw8/Qlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.2.1", - "@bcoe/v8-coverage": "^0.2.3", - "debug": "^4.3.4", - "istanbul-lib-coverage": "^3.2.2", - "istanbul-lib-report": "^3.0.1", - "istanbul-lib-source-maps": "^5.0.4", - "istanbul-reports": "^3.1.6", - "magic-string": "^0.30.5", - "magicast": "^0.3.3", - "picocolors": "^1.0.0", - "std-env": "^3.5.0", - "strip-literal": "^2.0.0", - "test-exclude": "^6.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "vitest": "1.6.1" - } - }, - "sdks/urbackend-sdk/node_modules/@vitest/expect": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.1.tgz", - "integrity": "sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "1.6.1", - "@vitest/utils": "1.6.1", - "chai": "^4.3.10" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "sdks/urbackend-sdk/node_modules/@vitest/runner": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.1.tgz", - "integrity": "sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "1.6.1", - "p-limit": "^5.0.0", - "pathe": "^1.1.1" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "sdks/urbackend-sdk/node_modules/@vitest/snapshot": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.1.tgz", - "integrity": "sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "magic-string": "^0.30.5", - "pathe": "^1.1.1", - "pretty-format": "^29.7.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "sdks/urbackend-sdk/node_modules/@vitest/spy": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.1.tgz", - "integrity": "sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyspy": "^2.2.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "sdks/urbackend-sdk/node_modules/@vitest/utils": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.1.tgz", - "integrity": "sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "diff-sequences": "^29.6.3", - "estree-walker": "^3.0.3", - "loupe": "^2.3.7", - "pretty-format": "^29.7.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "sdks/urbackend-sdk/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "sdks/urbackend-sdk/node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "sdks/urbackend-sdk/node_modules/chai": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", - "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.3", - "deep-eql": "^4.1.3", - "get-func-name": "^2.0.2", - "loupe": "^2.3.6", - "pathval": "^1.1.1", - "type-detect": "^4.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "sdks/urbackend-sdk/node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "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" - } - }, - "sdks/urbackend-sdk/node_modules/eslint": { - "version": "10.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.2", - "@eslint/config-array": "^0.23.3", - "@eslint/config-helpers": "^0.5.3", - "@eslint/core": "^1.1.1", - "@eslint/plugin-kit": "^0.6.1", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "ajv": "^6.14.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^9.1.2", - "eslint-visitor-keys": "^5.0.1", - "espree": "^11.2.0", - "esquery": "^1.7.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "minimatch": "^10.2.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "sdks/urbackend-sdk/node_modules/eslint-scope": { - "version": "9.1.2", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@types/esrecurse": "^4.3.1", - "@types/estree": "^1.0.8", - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "sdks/urbackend-sdk/node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "sdks/urbackend-sdk/node_modules/espree": { - "version": "11.2.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.16.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^5.0.1" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "sdks/urbackend-sdk/node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "sdks/urbackend-sdk/node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "sdks/urbackend-sdk/node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=16.17.0" - } - }, - "sdks/urbackend-sdk/node_modules/ignore": { - "version": "5.3.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "sdks/urbackend-sdk/node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "sdks/urbackend-sdk/node_modules/magicast": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", - "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "sdks/urbackend-sdk/node_modules/magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", "dev": true, "license": "MIT", "dependencies": { @@ -16678,271 +16591,6 @@ "@babel/types": "^7.25.4", "source-map-js": "^1.2.0" } - }, - "sdks/urbackend-sdk/node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "sdks/urbackend-sdk/node_modules/npm-run-path": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", - "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "sdks/urbackend-sdk/node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "sdks/urbackend-sdk/node_modules/p-limit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", - "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "sdks/urbackend-sdk/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "sdks/urbackend-sdk/node_modules/pathe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", - "dev": true, - "license": "MIT" - }, - "sdks/urbackend-sdk/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "sdks/urbackend-sdk/node_modules/std-env": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", - "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", - "dev": true, - "license": "MIT" - }, - "sdks/urbackend-sdk/node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "sdks/urbackend-sdk/node_modules/type-detect": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", - "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "sdks/urbackend-sdk/node_modules/vite": { - "version": "5.4.21", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", - "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, - "sdks/urbackend-sdk/node_modules/vitest": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.1.tgz", - "integrity": "sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/expect": "1.6.1", - "@vitest/runner": "1.6.1", - "@vitest/snapshot": "1.6.1", - "@vitest/spy": "1.6.1", - "@vitest/utils": "1.6.1", - "acorn-walk": "^8.3.2", - "chai": "^4.3.10", - "debug": "^4.3.4", - "execa": "^8.0.1", - "local-pkg": "^0.5.0", - "magic-string": "^0.30.5", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", - "std-env": "^3.5.0", - "strip-literal": "^2.0.0", - "tinybench": "^2.5.1", - "tinypool": "^0.8.3", - "vite": "^5.0.0", - "vite-node": "1.6.1", - "why-is-node-running": "^2.2.2" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "1.6.1", - "@vitest/ui": "1.6.1", - "happy-dom": "*", - "jsdom": "*" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - } - } - }, - "sdks/urbackend-sdk/node_modules/yocto-queue": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", - "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } } } } diff --git a/sdks/urbackend-cli/bin/ub.js b/sdks/urbackend-cli/bin/ub.js new file mode 100644 index 000000000..c5cc43369 --- /dev/null +++ b/sdks/urbackend-cli/bin/ub.js @@ -0,0 +1,7 @@ +#!/usr/bin/env node + +// Resolve the correct dist entry based on the module system +import("../dist/index.js").catch((err) => { + console.error("Failed to start urBackend CLI:", err.message); + process.exit(1); +}); diff --git a/sdks/urbackend-cli/dist/index.cjs b/sdks/urbackend-cli/dist/index.cjs new file mode 100644 index 000000000..8aeef2c1f --- /dev/null +++ b/sdks/urbackend-cli/dist/index.cjs @@ -0,0 +1,818 @@ +// @urbackend/cli — Official urBackend CLI +"use strict"; +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); + +// src/index.ts +var import_commander = require("commander"); + +// src/core/config.ts +var import_node_fs = __toESM(require("fs"), 1); +var import_node_path2 = __toESM(require("path"), 1); + +// src/core/constants.ts +var import_node_os = __toESM(require("os"), 1); +var import_node_path = __toESM(require("path"), 1); +var CONFIG_DIR = import_node_path.default.join(import_node_os.default.homedir(), ".ub"); +var CONFIG_PATH = import_node_path.default.join(CONFIG_DIR, "config.json"); +var DEFAULT_API_BASE = process.env.URBACKEND_API_URL ?? "https://api.urbackend.bitbros.in"; + +// src/core/config.ts +var DEFAULT_CONFIG = { + apiBase: DEFAULT_API_BASE +}; +function loadConfig() { + if (!import_node_fs.default.existsSync(CONFIG_PATH)) { + return { ...DEFAULT_CONFIG }; + } + try { + const raw = import_node_fs.default.readFileSync(CONFIG_PATH, "utf8"); + return { + ...DEFAULT_CONFIG, + ...JSON.parse(raw) + }; + } catch { + return { ...DEFAULT_CONFIG }; + } +} +function saveConfig(config) { + if (!import_node_fs.default.existsSync(CONFIG_DIR)) { + import_node_fs.default.mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 }); + } + const tmp = CONFIG_PATH + ".tmp"; + import_node_fs.default.writeFileSync(tmp, JSON.stringify(config, null, 2), { + encoding: "utf8", + mode: 384 + }); + import_node_fs.default.renameSync(tmp, CONFIG_PATH); + import_node_fs.default.chmodSync(CONFIG_DIR, 448); + import_node_fs.default.chmodSync(CONFIG_PATH, 384); +} +function saveToken(token) { + const config = loadConfig(); + const nextConfig = { ...config, pat: token }; + if (config.pat && config.pat !== token) { + delete nextConfig.currentProject; + } + saveConfig(nextConfig); +} +function clearToken() { + const config = loadConfig(); + delete config.pat; + delete config.currentProject; + saveConfig(config); +} +function getToken() { + return loadConfig().pat; +} +function saveCurrentProject(projectId) { + const config = loadConfig(); + saveConfig({ ...config, currentProject: projectId }); +} +function getCurrentProject() { + return loadConfig().currentProject; +} +function configFilePath() { + return CONFIG_PATH; +} + +// src/core/errors.ts +var APIError = class extends Error { + constructor(status, message) { + super(message); + this.status = status; + this.name = "APIError"; + } + status; +}; + +// src/core/api.ts +async function apiFetch(endpoint, options = {}) { + const config = loadConfig(); + const headers = new Headers(options.headers); + if (options.body && !headers.has("Content-Type")) { + headers.set("Content-Type", "application/json"); + } + const token = options.token ?? config.pat; + if (token) { + headers.set("Authorization", `Bearer ${token}`); + } + const base = config.apiBase.replace(/\/+$/, ""); + const apiPath = endpoint.startsWith("/") ? endpoint : `/${endpoint}`; + const url = `${base}/api${apiPath}`; + let response; + const controller = new AbortController(); + const signal = options.signal ?? controller.signal; + const timeoutId = options.signal ? void 0 : setTimeout(() => controller.abort(), 15e3); + try { + response = await fetch(url, { + ...options, + headers, + signal + }); + } catch (error) { + throw new APIError( + 0, + error instanceof Error && error.name === "AbortError" ? "The urBackend API request timed out." : "Unable to connect to the urBackend API. Is the server running?" + ); + } finally { + if (timeoutId) clearTimeout(timeoutId); + } + if (!response.ok) { + let message = response.statusText; + try { + const body = await response.json(); + message = body.message ?? body.error ?? message; + } catch { + } + throw new APIError(response.status, message); + } + if (response.status === 204) { + return void 0; + } + return await response.json(); +} + +// src/services/auth.service.ts +async function authenticate(token) { + const res = await apiFetch("/user/cli/me", { + method: "GET", + token + }); + return res.data; +} +async function getProfile() { + const res = await apiFetch("/user/cli/me", { + method: "GET" + }); + return res.data; +} + +// src/utils/token.ts +function isValidPAT(token) { + return typeof token === "string" && /^ubpat_\S{10,}$/.test(token); +} + +// src/utils/prompt.ts +var import_promises = require("readline/promises"); +var import_node_process = require("process"); +async function prompt(question) { + const rl = (0, import_promises.createInterface)({ input: import_node_process.stdin, output: import_node_process.stdout }); + const answer = (await rl.question(question)).trim(); + rl.close(); + return answer; +} +async function promptSecret(question) { + const rl = (0, import_promises.createInterface)({ + input: import_node_process.stdin, + output: import_node_process.stdout + }); + import_node_process.stdout.write(question); + const oldWrite = rl._writeToOutput; + rl._writeToOutput = function _writeToOutput(stringToWrite) { + if (stringToWrite === "\r" || stringToWrite === "\n" || stringToWrite === "\r\n") { + oldWrite.call(rl, stringToWrite); + } else if (stringToWrite === question) { + oldWrite.call(rl, stringToWrite); + } else { + } + }; + const answer = (await rl.question("")).trim(); + rl._writeToOutput = oldWrite; + rl.close(); + return answer; +} +async function confirm(question) { + const answer = await prompt(`${question} (y/n): `); + return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes"; +} + +// src/utils/format.ts +function formatBytes(bytes) { + if (bytes === 0) return "0 B"; + const units = ["B", "KB", "MB", "GB", "TB"]; + const i = Math.floor(Math.log(bytes) / Math.log(1024)); + const value = (bytes / Math.pow(1024, i)).toFixed(2); + return `${value} ${units[i]}`; +} +function label(text, width = 14) { + return text.padEnd(width); +} + +// src/core/logger.ts +var logger = { + success(message) { + console.log(`\u2713 ${message}`); + }, + error(message) { + console.error(`\u2716 ${message}`); + }, + warn(message) { + console.warn(`\u26A0 ${message}`); + }, + info(message) { + console.log(message); + } +}; + +// src/commands/auth/login.ts +async function loginCommand() { + console.log("Generate a Personal Access Token from the urBackend dashboard:"); + console.log(" Settings \u2192 Access Tokens \u2192 New Token\n"); + const token = await promptSecret("Paste your Personal Access Token: "); + if (!isValidPAT(token)) { + logger.error( + "Invalid token format. urBackend PATs start with 'ubpat_' followed by at least 10 characters." + ); + process.exitCode = 1; + return; + } + try { + const profile = await authenticate(token); + try { + saveToken(token); + } catch { + logger.error("Authenticated, but failed to persist the token locally."); + process.exitCode = 1; + return; + } + logger.success("Logged in successfully.\n"); + console.log(`${label("Email")} ${profile.developer.email}`); + console.log(`${label("Plan")} ${profile.developer.plan}`); + if (profile.developer.githubUsername) { + console.log(`${label("GitHub")} ${profile.developer.githubUsername}`); + } + if (profile.auth.scopes.length > 0) { + console.log(`${label("Scopes")} ${profile.auth.scopes.join(", ")}`); + } + } catch (error) { + if (error instanceof APIError) { + if (error.status === 401) { + logger.error("Invalid or expired token. Generate a new one from the dashboard."); + } else { + logger.error(error.message); + } + process.exitCode = 1; + return; + } + logger.error("Unable to connect to the urBackend API."); + process.exitCode = 1; + } +} + +// src/commands/auth/logout.ts +async function logoutCommand() { + clearToken(); + logger.success("Logged out. Your token has been removed from this machine."); +} + +// src/commands/auth/whoami.ts +async function whoamiCommand() { + const token = getToken(); + if (!token) { + logger.error("You are not logged in. Run 'ub login' first."); + process.exitCode = 1; + return; + } + try { + const profile = await getProfile(); + console.log(`${label("Email")} ${profile.developer.email}`); + console.log(`${label("Plan")} ${profile.developer.plan}`); + if (profile.developer.githubUsername) { + console.log(`${label("GitHub")} ${profile.developer.githubUsername}`); + } + console.log(`${label("Token type")} ${profile.auth.tokenType}`); + if (profile.auth.scopes.length > 0) { + console.log(`${label("Scopes")} ${profile.auth.scopes.join(", ")}`); + } + const currentProject = getCurrentProject(); + if (currentProject) { + console.log(`${label("Project")} ${currentProject}`); + } + } catch (error) { + if (error instanceof APIError) { + if (error.status === 401) { + logger.error("Token is invalid or expired. Run 'ub login' to re-authenticate."); + } else { + logger.error(error.message); + } + process.exitCode = 1; + return; + } + logger.error("Unable to connect to the urBackend API."); + process.exitCode = 1; + } +} + +// src/services/project.service.ts +async function listProjects() { + const res = await apiFetch( + "/projects", + { method: "GET" } + ); + if (Array.isArray(res)) return res; + return res.data ?? []; +} +async function getProject(projectId) { + const res = await apiFetch( + `/projects/${projectId}`, + { method: "GET" } + ); + if ("data" in res && res.data) return res.data; + return res; +} + +// src/commands/project/list.ts +async function projectListCommand() { + const token = getToken(); + if (!token) { + logger.error("You are not logged in. Run 'ub login' first."); + return; + } + try { + const projects = await listProjects(); + const currentProjectId = getCurrentProject(); + if (projects.length === 0) { + logger.info("No projects found. Create one at dashboard.urbackend.bitbros.in"); + return; + } + console.log(` +Found ${projects.length} project(s): +`); + for (const project of projects) { + const active = project._id === currentProjectId ? " (active)" : ""; + const db = formatBytes(project.databaseUsed ?? 0); + const colCount = project.collections?.length ?? 0; + console.log(` ${project.name}${active}`); + console.log(` ID ${project._id}`); + console.log(` Collections ${colCount}`); + console.log(` DB used ${db}`); + if (project.description) { + console.log(` Description ${project.description}`); + } + console.log(); + } + } catch (error) { + if (error instanceof APIError) { + if (error.status === 401) { + logger.error("Token is invalid or expired. Run 'ub login' to re-authenticate."); + } else { + logger.error(error.message); + } + return; + } + logger.error("Unable to connect to the urBackend API."); + } +} + +// src/commands/project/use.ts +async function projectUseCommand(projectIdOrName) { + const token = getToken(); + if (!token) { + logger.error("You are not logged in. Run 'ub login' first."); + return; + } + try { + const projects = await listProjects(); + if (projects.length === 0) { + logger.info("No projects found. Create one at dashboard.urbackend.bitbros.in"); + return; + } + let selectedId; + if (projectIdOrName) { + const match = projects.find( + (p) => p._id === projectIdOrName || p.name.toLowerCase() === projectIdOrName.toLowerCase() + ); + if (!match) { + logger.error(`No project found matching "${projectIdOrName}".`); + logger.info("Run 'ub project list' to see available projects."); + return; + } + selectedId = match._id; + } else { + console.log("\nAvailable projects:\n"); + projects.forEach((p, i) => { + console.log(` [${i + 1}] ${p.name} (${p._id})`); + }); + console.log(); + const answer = await prompt("Enter project number: "); + if (!/^\d+$/.test(answer)) { + logger.error("Invalid selection."); + return; + } + const index = Number(answer) - 1; + if (isNaN(index) || index < 0 || index >= projects.length) { + logger.error("Invalid selection."); + return; + } + selectedId = projects[index]._id; + } + saveCurrentProject(selectedId); + const project = projects.find((p) => p._id === selectedId); + logger.success(`Switched to project: ${project.name} (${selectedId})`); + } catch (error) { + if (error instanceof APIError) { + if (error.status === 401) { + logger.error("Token is invalid or expired. Run 'ub login' to re-authenticate."); + } else { + logger.error(error.message); + } + return; + } + logger.error("Unable to connect to the urBackend API."); + } +} + +// src/commands/project/info.ts +async function projectInfoCommand(projectId) { + const token = getToken(); + if (!token) { + logger.error("You are not logged in. Run 'ub login' first."); + return; + } + const id = projectId ?? getCurrentProject(); + if (!id) { + logger.error("No active project. Run 'ub project use' to select one."); + return; + } + try { + const project = await getProject(id); + console.log(` +${project.name} +`); + console.log(`${label("ID")} ${project._id}`); + if (project.description) { + console.log(`${label("Description")} ${project.description}`); + } + console.log(`${label("Auth")} ${project.isAuthEnabled ? "Enabled" : "Disabled"}`); + console.log(`${label("DB used")} ${formatBytes(project.databaseUsed ?? 0)}`); + console.log(`${label("Storage")} ${formatBytes(project.storageUsed ?? 0)}`); + console.log(`${label("Collections")} ${project.collections?.length ?? 0}`); + if (project.collections && project.collections.length > 0) { + console.log("\nCollections:"); + for (const col of project.collections) { + const fieldCount = col.model?.length ?? 0; + console.log(` \u2022 ${col.name} (${fieldCount} field${fieldCount !== 1 ? "s" : ""})`); + } + } + console.log(); + } catch (error) { + if (error instanceof APIError) { + if (error.status === 401) { + logger.error("Token is invalid or expired. Run 'ub login' to re-authenticate."); + } else if (error.status === 404) { + logger.error("Project not found."); + } else { + logger.error(error.message); + } + return; + } + logger.error("Unable to connect to the urBackend API."); + } +} + +// src/commands/collection/list.ts +async function collectionListCommand(projectId) { + const token = getToken(); + if (!token) { + logger.error("You are not logged in. Run 'ub login' first."); + return; + } + const id = projectId ?? getCurrentProject(); + if (!id) { + logger.error("No active project. Run 'ub project use' to select one."); + return; + } + try { + const project = await getProject(id); + const collections = project.collections ?? []; + if (collections.length === 0) { + logger.info(`No collections in project "${project.name}".`); + return; + } + console.log(` +Collections in "${project.name}" (${collections.length}): +`); + for (const col of collections) { + const fieldCount = col.model?.length ?? 0; + const rlsMode = col.rls?.enabled ? col.rls.mode : "disabled"; + console.log(` ${col.name}`); + console.log(` ${label("Fields", 10)} ${fieldCount}`); + console.log(` ${label("RLS", 10)} ${rlsMode}`); + if (col.model && col.model.length > 0) { + const fieldSummary = col.model.slice(0, 5).map((f) => `${f.key}: ${f.type}${f.required ? "*" : ""}`).join(", "); + const extra = col.model.length > 5 ? ` +${col.model.length - 5} more` : ""; + console.log(` ${label("Schema", 10)} ${fieldSummary}${extra}`); + } + console.log(); + } + console.log(" * = required field"); + console.log(); + } catch (error) { + if (error instanceof APIError) { + if (error.status === 401) { + logger.error("Token is invalid or expired. Run 'ub login' to re-authenticate."); + } else if (error.status === 404) { + logger.error("Project not found or access denied."); + } else { + logger.error(error.message); + } + return; + } + logger.error("Unable to connect to the urBackend API."); + } +} + +// src/services/collection.service.ts +async function deleteCollection(projectId, collectionName) { + const encodedName = encodeURIComponent(collectionName); + return apiFetch( + `/projects/${projectId}/collections/${encodedName}`, + { method: "DELETE" } + ); +} + +// src/commands/collection/delete.ts +async function collectionDeleteCommand(collectionName, options) { + const token = getToken(); + if (!token) { + logger.error("You are not logged in. Run 'ub login' first."); + return; + } + const projectId = options.project ?? getCurrentProject(); + if (!projectId) { + logger.error("No active project. Run 'ub project use' to select one."); + return; + } + try { + const project = await getProject(projectId); + const exists = project.collections?.some((c) => c.name === collectionName); + if (!exists) { + logger.error( + `Collection "${collectionName}" not found in project "${project.name}".` + ); + return; + } + if (!options.force) { + console.log( + ` +\u26A0 This will permanently delete the collection "${collectionName}" and ALL its data. +` + ); + const ok = await confirm("Are you sure?"); + if (!ok) { + logger.info("Aborted."); + return; + } + } + await deleteCollection(projectId, collectionName); + logger.success(`Collection "${collectionName}" deleted.`); + } catch (error) { + if (error instanceof APIError) { + if (error.status === 401) { + logger.error("Token is invalid or expired. Run 'ub login' to re-authenticate."); + } else if (error.status === 404) { + logger.error("Collection or project not found."); + } else { + logger.error(error.message); + } + return; + } + logger.error("Unable to connect to the urBackend API."); + } +} + +// src/services/analytics.service.ts +async function getGlobalStats() { + const res = await apiFetch("/analytics/stats", { + method: "GET" + }); + return res.data; +} +async function getRecentActivity() { + const res = await apiFetch( + "/analytics/activity", + { method: "GET" } + ); + if (!Array.isArray(res.data)) { + throw new Error("Invalid response from /analytics/activity"); + } + return res.data; +} + +// src/commands/status/index.ts +function statusIcon(status) { + if (status < 300) return "\u2713"; + if (status < 400) return "\u2192"; + if (status < 500) return "\u26A0"; + return "\u2716"; +} +async function statusCommand() { + const token = getToken(); + if (!token) { + logger.error("You are not logged in. Run 'ub login' first."); + return; + } + const currentProjectId = getCurrentProject(); + try { + const stats = await getGlobalStats(); + console.log("\n\u2500\u2500 Account \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"); + console.log(`${label("Plan")} ${stats.plan.toUpperCase()}`); + if (stats.planExpiresAt) { + console.log(`${label("Plan expires")} ${stats.planExpiresAt}`); + } + console.log("\n\u2500\u2500 Usage \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"); + console.log( + `${label("Projects")} ${stats.usage.totalProjects} / ${stats.limits.maxProjects}` + ); + console.log( + `${label("Collections")} ${stats.usage.totalCollections} / ${stats.limits.maxCollections}` + ); + console.log( + `${label("Database")} ${formatBytes(stats.usage.totalDatabaseUsed)} / ${formatBytes(stats.limits.mongoBytes)}` + ); + console.log( + `${label("Storage")} ${formatBytes(stats.usage.totalStorageUsed)} / ${formatBytes(stats.limits.storageBytes)}` + ); + console.log( + `${label("API requests")} ${stats.usage.totalRequests.toLocaleString()} / ${stats.limits.reqPerDay.toLocaleString()} today` + ); + console.log(`${label("Auth users")} ${stats.usage.totalUsers} / ${stats.limits.authUsersLimit}`); + console.log(`${label("Webhooks")} ${stats.usage.totalWebhooks}`); + if (currentProjectId) { + try { + const project = await getProject(currentProjectId); + console.log("\n\u2500\u2500 Active project \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"); + console.log(`${label("Name")} ${project.name}`); + console.log(`${label("ID")} ${project._id}`); + console.log(`${label("Collections")} ${project.collections?.length ?? 0}`); + console.log(`${label("Auth")} ${project.isAuthEnabled ? "Enabled" : "Disabled"}`); + } catch { + } + } else { + console.log("\nTip: Run 'ub project use' to select a project."); + } + try { + const activity = await getRecentActivity(); + if (activity.length > 0) { + console.log("\n\u2500\u2500 Recent activity (last 10) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"); + for (const log of activity.slice(0, 10)) { + const icon = statusIcon(log.status); + const time = new Date(log.timestamp).toLocaleTimeString(); + console.log( + ` ${icon} [${log.status}] ${log.method.padEnd(6)} ${log.path.padEnd(32)} ${time}` + ); + } + } + } catch { + } + console.log(); + } catch (error) { + if (error instanceof APIError) { + if (error.status === 401) { + logger.error("Token is invalid or expired. Run 'ub login' to re-authenticate."); + } else { + logger.error(error.message); + } + return; + } + logger.error("Unable to connect to the urBackend API."); + } +} + +// src/commands/doctor/index.ts +async function checkApiReachable(apiBase) { + const start = Date.now(); + try { + const response = await fetch(`${apiBase}/health`, { + signal: AbortSignal.timeout(5e3) + }); + if (!response.ok) { + return null; + } + return Date.now() - start; + } catch { + return null; + } +} +async function doctorCommand(options = {}) { + const config = loadConfig(); + const token = getToken(); + const currentProjectId = getCurrentProject(); + const result = { + configFound: false, + patValid: false, + projectSelected: !!currentProjectId, + projectAccessible: false, + dashboardApiReachable: false, + dashboardApiLatencyMs: null, + email: null, + plan: null, + projectName: null + }; + try { + const fs2 = await import("fs"); + result.configFound = fs2.existsSync(configFilePath()); + } catch { + result.configFound = false; + } + const latency = await checkApiReachable(config.apiBase); + result.dashboardApiReachable = latency !== null; + result.dashboardApiLatencyMs = latency; + if (token) { + try { + const profile = await getProfile(); + result.patValid = true; + result.email = profile.developer.email; + result.plan = profile.developer.plan; + } catch { + result.patValid = false; + } + } + if (currentProjectId && result.patValid) { + try { + const project = await getProject(currentProjectId); + result.projectAccessible = true; + result.projectName = project.name; + } catch { + result.projectAccessible = false; + } + } + if (options.json) { + console.log(JSON.stringify(result, null, 2)); + return; + } + console.log("\n\u2500\u2500 urBackend CLI Doctor \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"); + const ok = "\u2713"; + const fail = "\u2716"; + const warn = "\u26A0"; + console.log( + ` ${result.configFound ? ok : fail} Config file ${result.configFound ? configFilePath() : "Not found \u2014 run 'ub login'"}` + ); + console.log( + ` ${result.dashboardApiReachable ? ok : fail} Dashboard API ${result.dashboardApiReachable ? `Reachable (${result.dashboardApiLatencyMs}ms)` : `Unreachable \u2014 ${config.apiBase}`}` + ); + if (!token) { + console.log(` ${fail} PAT Not set \u2014 run 'ub login'`); + } else { + console.log( + ` ${result.patValid ? ok : fail} PAT ${result.patValid ? `Valid (${result.email}, ${result.plan})` : "Invalid or expired \u2014 run 'ub login'"}` + ); + } + if (!currentProjectId) { + console.log(` ${warn} Active project Not selected \u2014 run 'ub project use'`); + } else { + console.log( + ` ${result.projectAccessible ? ok : fail} Active project ${result.projectAccessible ? `${result.projectName} (${currentProjectId})` : `ID ${currentProjectId} \u2014 not found or access denied`}` + ); + } + console.log(); + const allGood = result.configFound && result.patValid && result.dashboardApiReachable && result.projectSelected && result.projectAccessible; + if (allGood) { + logger.success("Everything looks good."); + } else { + logger.warn("Some checks failed. Review the output above."); + } + console.log(); +} + +// src/index.ts +var program = new import_commander.Command(); +program.name("ub").description("Official urBackend CLI \u2014 manage projects, schemas, and more").version("0.1.0"); +program.command("login").description("Authenticate with a Personal Access Token").action(loginCommand); +program.command("logout").description("Remove stored credentials from this machine").action(logoutCommand); +program.command("whoami").description("Show the currently authenticated developer").action(whoamiCommand); +var projectCmd = program.command("project").description("Manage urBackend projects"); +projectCmd.command("list").alias("ls").description("List all accessible projects").action(projectListCommand); +projectCmd.command("use [projectIdOrName]").description("Set the active project for subsequent commands").action(projectUseCommand); +projectCmd.command("info [projectId]").description("Show details for the active (or specified) project").action(projectInfoCommand); +var collectionCmd = program.command("collection").alias("col").description("Manage collections inside the active project"); +collectionCmd.command("list").alias("ls").description("List all collections in the active project").option("-p, --project ", "Target a specific project ID").action((options) => collectionListCommand(options.project)); +collectionCmd.command("delete ").alias("rm").description("Delete a collection and all its data").option("-f, --force", "Skip confirmation prompt").option("-p, --project ", "Target a specific project ID").action((name, options) => collectionDeleteCommand(name, options)); +program.command("status").description("Show account usage and recent API activity").action(statusCommand); +program.command("doctor").description("Run diagnostic checks on your CLI setup").option("--json", "Output results as JSON (useful for CI/AI agents)").action((options) => doctorCommand(options)); +program.parseAsync(process.argv).catch((err) => { + console.error(`\u2716 ${err.message}`); + process.exit(1); +}); +//# sourceMappingURL=index.cjs.map \ No newline at end of file diff --git a/sdks/urbackend-cli/dist/index.cjs.map b/sdks/urbackend-cli/dist/index.cjs.map new file mode 100644 index 000000000..c0edc963d --- /dev/null +++ b/sdks/urbackend-cli/dist/index.cjs.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/index.ts","../src/core/config.ts","../src/core/constants.ts","../src/core/errors.ts","../src/core/api.ts","../src/services/auth.service.ts","../src/utils/token.ts","../src/utils/prompt.ts","../src/utils/format.ts","../src/core/logger.ts","../src/commands/auth/login.ts","../src/commands/auth/logout.ts","../src/commands/auth/whoami.ts","../src/services/project.service.ts","../src/commands/project/list.ts","../src/commands/project/use.ts","../src/commands/project/info.ts","../src/commands/collection/list.ts","../src/services/collection.service.ts","../src/commands/collection/delete.ts","../src/services/analytics.service.ts","../src/commands/status/index.ts","../src/commands/doctor/index.ts"],"sourcesContent":["import { Command } from \"commander\";\r\nimport { loginCommand } from \"./commands/auth/login.js\";\r\nimport { logoutCommand } from \"./commands/auth/logout.js\";\r\nimport { whoamiCommand } from \"./commands/auth/whoami.js\";\r\nimport { projectListCommand } from \"./commands/project/list.js\";\r\nimport { projectUseCommand } from \"./commands/project/use.js\";\r\nimport { projectInfoCommand } from \"./commands/project/info.js\";\r\nimport { collectionListCommand } from \"./commands/collection/list.js\";\r\nimport { collectionDeleteCommand } from \"./commands/collection/delete.js\";\r\nimport { statusCommand } from \"./commands/status/index.js\";\r\nimport { doctorCommand } from \"./commands/doctor/index.js\";\r\n\r\nconst program = new Command();\r\n\r\nprogram\r\n .name(\"ub\")\r\n .description(\"Official urBackend CLI — manage projects, schemas, and more\")\r\n .version(\"0.1.0\");\r\n\r\n// ── Authentication ──────────────────────────────────────────────────────────\r\n\r\nprogram\r\n .command(\"login\")\r\n .description(\"Authenticate with a Personal Access Token\")\r\n .action(loginCommand);\r\n\r\nprogram\r\n .command(\"logout\")\r\n .description(\"Remove stored credentials from this machine\")\r\n .action(logoutCommand);\r\n\r\nprogram\r\n .command(\"whoami\")\r\n .description(\"Show the currently authenticated developer\")\r\n .action(whoamiCommand);\r\n\r\n// ── Projects ─────────────────────────────────────────────────────────────────\r\n\r\nconst projectCmd = program\r\n .command(\"project\")\r\n .description(\"Manage urBackend projects\");\r\n\r\nprojectCmd\r\n .command(\"list\")\r\n .alias(\"ls\")\r\n .description(\"List all accessible projects\")\r\n .action(projectListCommand);\r\n\r\nprojectCmd\r\n .command(\"use [projectIdOrName]\")\r\n .description(\"Set the active project for subsequent commands\")\r\n .action(projectUseCommand);\r\n\r\nprojectCmd\r\n .command(\"info [projectId]\")\r\n .description(\"Show details for the active (or specified) project\")\r\n .action(projectInfoCommand);\r\n\r\n// ── Collections ──────────────────────────────────────────────────────────────\r\n\r\nconst collectionCmd = program\r\n .command(\"collection\")\r\n .alias(\"col\")\r\n .description(\"Manage collections inside the active project\");\r\n\r\ncollectionCmd\r\n .command(\"list\")\r\n .alias(\"ls\")\r\n .description(\"List all collections in the active project\")\r\n .option(\"-p, --project \", \"Target a specific project ID\")\r\n .action((options) => collectionListCommand(options.project));\r\n\r\ncollectionCmd\r\n .command(\"delete \")\r\n .alias(\"rm\")\r\n .description(\"Delete a collection and all its data\")\r\n .option(\"-f, --force\", \"Skip confirmation prompt\")\r\n .option(\"-p, --project \", \"Target a specific project ID\")\r\n .action((name, options) => collectionDeleteCommand(name, options));\r\n\r\n// ── Status ────────────────────────────────────────────────────────────────────\r\n\r\nprogram\r\n .command(\"status\")\r\n .description(\"Show account usage and recent API activity\")\r\n .action(statusCommand);\r\n\r\n// ── Doctor ────────────────────────────────────────────────────────────────────\r\n\r\nprogram\r\n .command(\"doctor\")\r\n .description(\"Run diagnostic checks on your CLI setup\")\r\n .option(\"--json\", \"Output results as JSON (useful for CI/AI agents)\")\r\n .action((options) => doctorCommand(options));\r\n\r\n// ── Parse ─────────────────────────────────────────────────────────────────────\r\n\r\nprogram.parseAsync(process.argv).catch((err: Error) => {\r\n console.error(`✖ ${err.message}`);\r\n process.exit(1);\r\n});\r\n","import fs from \"node:fs\";\r\nimport path from \"node:path\";\r\nimport type { CLIConfig } from \"../types/config.js\";\r\nimport { CONFIG_DIR, CONFIG_PATH, DEFAULT_API_BASE } from \"./constants.js\";\r\n\r\nconst DEFAULT_CONFIG: CLIConfig = {\r\n apiBase: DEFAULT_API_BASE,\r\n};\r\n\r\nexport function loadConfig(): CLIConfig {\r\n if (!fs.existsSync(CONFIG_PATH)) {\r\n return { ...DEFAULT_CONFIG };\r\n }\r\n\r\n try {\r\n const raw = fs.readFileSync(CONFIG_PATH, \"utf8\");\r\n return {\r\n ...DEFAULT_CONFIG,\r\n ...JSON.parse(raw),\r\n };\r\n } catch {\r\n return { ...DEFAULT_CONFIG };\r\n }\r\n}\r\n\r\nexport function saveConfig(config: CLIConfig): void {\r\n if (!fs.existsSync(CONFIG_DIR)) {\r\n fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });\r\n }\r\n\r\n // Atomic write: write to temp file then rename to avoid corruption on crash\r\n const tmp = CONFIG_PATH + \".tmp\";\r\n fs.writeFileSync(tmp, JSON.stringify(config, null, 2), {\r\n encoding: \"utf8\",\r\n mode: 0o600,\r\n });\r\n fs.renameSync(tmp, CONFIG_PATH);\r\n fs.chmodSync(CONFIG_DIR, 0o700);\r\n fs.chmodSync(CONFIG_PATH, 0o600);\r\n}\r\n\r\nexport function saveToken(token: string): void {\r\n const config = loadConfig();\r\n const nextConfig: CLIConfig = { ...config, pat: token };\r\n if (config.pat && config.pat !== token) {\r\n delete nextConfig.currentProject;\r\n }\r\n saveConfig(nextConfig);\r\n}\r\n\r\nexport function clearToken(): void {\r\n const config = loadConfig();\r\n delete config.pat;\r\n delete config.currentProject;\r\n saveConfig(config);\r\n}\r\n\r\nexport function getToken(): string | undefined {\r\n return loadConfig().pat;\r\n}\r\n\r\nexport function saveCurrentProject(projectId: string): void {\r\n const config = loadConfig();\r\n saveConfig({ ...config, currentProject: projectId });\r\n}\r\n\r\nexport function getCurrentProject(): string | undefined {\r\n return loadConfig().currentProject;\r\n}\r\n\r\nexport function configFilePath(): string {\r\n return CONFIG_PATH;\r\n}\r\n\r\nexport function configDirPath(): string {\r\n return path.dirname(CONFIG_PATH);\r\n}\r\n","import os from \"node:os\";\r\nimport path from \"node:path\";\r\n\r\nexport const CLI_NAME = \"ub\";\r\n\r\nexport const CONFIG_DIR = path.join(os.homedir(), \".ub\");\r\n\r\nexport const CONFIG_PATH = path.join(CONFIG_DIR, \"config.json\");\r\n\r\n/**\r\n * Default production API base URL.\r\n * Override via URBACKEND_API_URL env variable for local development.\r\n */\r\nexport const DEFAULT_API_BASE =\r\n process.env.URBACKEND_API_URL ?? \"https://api.urbackend.bitbros.in\";\r\n","export class APIError extends Error {\r\n constructor(\r\n public readonly status: number,\r\n message: string,\r\n ) {\r\n super(message);\r\n this.name = \"APIError\";\r\n }\r\n}\r\n","import { loadConfig } from \"./config.js\";\r\nimport { APIError } from \"./errors.js\";\r\n\r\nexport interface FetchOptions extends RequestInit {\r\n /** Override the PAT for this single request (e.g. during login verification) */\r\n token?: string;\r\n}\r\n\r\n/**\r\n * Central HTTP client for all CLI → dashboard-api communication.\r\n * Automatically injects the stored PAT as a Bearer token.\r\n *\r\n * All dashboard-api routes are mounted under /api — this client\r\n * prepends that prefix automatically so callers just write /user/cli/me.\r\n */\r\nexport async function apiFetch(\r\n endpoint: string,\r\n options: FetchOptions = {},\r\n): Promise {\r\n const config = loadConfig();\r\n\r\n const headers = new Headers(options.headers);\r\n\r\n if (options.body && !headers.has(\"Content-Type\")) {\r\n headers.set(\"Content-Type\", \"application/json\");\r\n }\r\n\r\n const token = options.token ?? config.pat;\r\n if (token) {\r\n headers.set(\"Authorization\", `Bearer ${token}`);\r\n }\r\n\r\n // All dashboard-api routes are under /api\r\n const base = config.apiBase.replace(/\\/+$/, \"\");\r\n const apiPath = endpoint.startsWith(\"/\") ? endpoint : `/${endpoint}`;\r\n const url = `${base}/api${apiPath}`;\r\n\r\n let response: Response;\r\n\r\n const controller = new AbortController();\r\n const signal = options.signal ?? controller.signal;\r\n const timeoutId =\r\n options.signal ? undefined : setTimeout(() => controller.abort(), 15_000);\r\n\r\n try {\r\n response = await fetch(url, {\r\n ...options,\r\n headers,\r\n signal,\r\n });\r\n } catch (error) {\r\n throw new APIError(\r\n 0,\r\n error instanceof Error && error.name === \"AbortError\"\r\n ? \"The urBackend API request timed out.\"\r\n : \"Unable to connect to the urBackend API. Is the server running?\",\r\n );\r\n } finally {\r\n if (timeoutId) clearTimeout(timeoutId);\r\n }\r\n\r\n if (!response.ok) {\r\n let message = response.statusText;\r\n\r\n try {\r\n const body = (await response.json()) as {\r\n message?: string;\r\n error?: string;\r\n };\r\n message = body.message ?? body.error ?? message;\r\n } catch {\r\n // ignore — use statusText\r\n }\r\n\r\n throw new APIError(response.status, message);\r\n }\r\n\r\n if (response.status === 204) {\r\n return undefined as T;\r\n }\r\n\r\n return (await response.json()) as T;\r\n}","import { apiFetch } from \"../core/api.js\";\r\nimport type { CLIProfile } from \"../types/auth.js\";\r\n\r\ninterface APIResponse {\r\n success: boolean;\r\n data: T;\r\n message: string;\r\n}\r\n\r\nexport async function authenticate(token: string): Promise {\r\n const res = await apiFetch>(\"/user/cli/me\", {\r\n method: \"GET\",\r\n token,\r\n });\r\n \r\n return res.data;\r\n}\r\n\r\nexport async function getProfile(): Promise {\r\n const res = await apiFetch>(\"/user/cli/me\", {\r\n method: \"GET\",\r\n });\r\n return res.data;\r\n}","/**\r\n * Validates the format of a Personal Access Token.\r\n * urBackend PATs always start with \"ubpat_\" followed by at least 10 characters.\r\n */\r\nexport function isValidPAT(token: string): boolean {\r\n return typeof token === \"string\" && /^ubpat_\\S{10,}$/.test(token);\r\n}\r\n","import { createInterface } from \"node:readline/promises\";\r\nimport { stdin, stdout } from \"node:process\";\r\n\r\n/**\r\n * Prompts the user for a single line of input.\r\n * Automatically closes the readline interface after reading.\r\n */\r\nexport async function prompt(question: string): Promise {\r\n const rl = createInterface({ input: stdin, output: stdout });\r\n const answer = (await rl.question(question)).trim();\r\n rl.close();\r\n return answer;\r\n}\r\n\r\n/**\r\n * Prompts the user for secret input (like a password/token) without echoing characters.\r\n */\r\nexport async function promptSecret(question: string): Promise {\r\n const rl = createInterface({\r\n input: stdin,\r\n output: stdout,\r\n });\r\n\r\n // Write the question first\r\n stdout.write(question);\r\n\r\n // Standard Node.js readline secret pattern using internal _writeToOutput override:\r\n const oldWrite = (rl as any)._writeToOutput;\r\n (rl as any)._writeToOutput = function _writeToOutput(stringToWrite: string) {\r\n if (stringToWrite === \"\\r\" || stringToWrite === \"\\n\" || stringToWrite === \"\\r\\n\") {\r\n oldWrite.call(rl, stringToWrite);\r\n } else if (stringToWrite === question) {\r\n oldWrite.call(rl, stringToWrite);\r\n } else {\r\n // Echo nothing or a mask (nothing is standard for non-echoing Unix style secret prompts)\r\n }\r\n };\r\n\r\n const answer = (await rl.question(\"\")).trim();\r\n (rl as any)._writeToOutput = oldWrite;\r\n rl.close();\r\n return answer;\r\n}\r\n\r\n/**\r\n * Prompts for a yes/no confirmation.\r\n * Returns true for \"y\" or \"yes\" (case-insensitive), false otherwise.\r\n */\r\nexport async function confirm(question: string): Promise {\r\n const answer = await prompt(`${question} (y/n): `);\r\n return answer.toLowerCase() === \"y\" || answer.toLowerCase() === \"yes\";\r\n}\r\n","/**\r\n * Formats bytes into a human-readable string (KB, MB, GB).\r\n */\r\nexport function formatBytes(bytes: number): string {\r\n if (bytes === 0) return \"0 B\";\r\n const units = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\"];\r\n const i = Math.floor(Math.log(bytes) / Math.log(1024));\r\n const value = (bytes / Math.pow(1024, i)).toFixed(2);\r\n return `${value} ${units[i]}`;\r\n}\r\n\r\n/**\r\n * Formats an ISO date string to a locale-friendly short format.\r\n */\r\nexport function formatDate(iso: string | null | undefined): string {\r\n if (!iso) return \"Never\";\r\n return new Date(iso).toLocaleDateString(\"en-US\", {\r\n year: \"numeric\",\r\n month: \"short\",\r\n day: \"numeric\",\r\n });\r\n}\r\n\r\n/**\r\n * Pads a string label to a fixed width for aligned terminal output.\r\n */\r\nexport function label(text: string, width = 14): string {\r\n return text.padEnd(width);\r\n}\r\n","export const logger = {\r\n success(message: string): void {\r\n console.log(`✓ ${message}`);\r\n },\r\n\r\n error(message: string): void {\r\n console.error(`✖ ${message}`);\r\n },\r\n\r\n warn(message: string): void {\r\n console.warn(`⚠ ${message}`);\r\n },\r\n\r\n info(message: string): void {\r\n console.log(message);\r\n },\r\n};\r\n","import { authenticate } from \"../../services/auth.service.js\";\r\nimport { saveToken } from \"../../core/config.js\";\r\nimport { isValidPAT } from \"../../utils/token.js\";\r\nimport { promptSecret } from \"../../utils/prompt.js\";\r\nimport { label } from \"../../utils/format.js\";\r\nimport { APIError } from \"../../core/errors.js\";\r\nimport { logger } from \"../../core/logger.js\";\r\n\r\nexport async function loginCommand(): Promise {\r\n console.log(\"Generate a Personal Access Token from the urBackend dashboard:\");\r\n console.log(\" Settings → Access Tokens → New Token\\n\");\r\n\r\n const token = await promptSecret(\"Paste your Personal Access Token: \");\r\n\r\n if (!isValidPAT(token)) {\r\n logger.error(\r\n \"Invalid token format. urBackend PATs start with 'ubpat_' followed by at least 10 characters.\",\r\n );\r\n process.exitCode = 1;\r\n return;\r\n }\r\n\r\n try {\r\n const profile = await authenticate(token);\r\n\r\n try {\r\n saveToken(token);\r\n } catch {\r\n logger.error(\"Authenticated, but failed to persist the token locally.\");\r\n process.exitCode = 1;\r\n return;\r\n }\r\n\r\n logger.success(\"Logged in successfully.\\n\");\r\n console.log(`${label(\"Email\")} ${profile.developer.email}`);\r\n console.log(`${label(\"Plan\")} ${profile.developer.plan}`);\r\n\r\n if (profile.developer.githubUsername) {\r\n console.log(`${label(\"GitHub\")} ${profile.developer.githubUsername}`);\r\n }\r\n\r\n if (profile.auth.scopes.length > 0) {\r\n console.log(`${label(\"Scopes\")} ${profile.auth.scopes.join(\", \")}`);\r\n }\r\n } catch (error) {\r\n if (error instanceof APIError) {\r\n if (error.status === 401) {\r\n logger.error(\"Invalid or expired token. Generate a new one from the dashboard.\");\r\n } else {\r\n logger.error(error.message);\r\n }\r\n process.exitCode = 1;\r\n return;\r\n }\r\n logger.error(\"Unable to connect to the urBackend API.\");\r\n process.exitCode = 1;\r\n }\r\n}\r\n","import { clearToken } from \"../../core/config.js\";\r\nimport { logger } from \"../../core/logger.js\";\r\n\r\nexport async function logoutCommand(): Promise {\r\n clearToken();\r\n logger.success(\"Logged out. Your token has been removed from this machine.\");\r\n}\r\n","import { getProfile } from \"../../services/auth.service.js\";\r\nimport { getToken, getCurrentProject } from \"../../core/config.js\";\r\nimport { label, formatDate } from \"../../utils/format.js\";\r\nimport { APIError } from \"../../core/errors.js\";\r\nimport { logger } from \"../../core/logger.js\";\r\n\r\nexport async function whoamiCommand(): Promise {\r\n const token = getToken();\r\n\r\n if (!token) {\r\n logger.error(\"You are not logged in. Run 'ub login' first.\");\r\n process.exitCode = 1;\r\n return;\r\n }\r\n\r\n try {\r\n const profile = await getProfile();\r\n\r\n console.log(`${label(\"Email\")} ${profile.developer.email}`);\r\n console.log(`${label(\"Plan\")} ${profile.developer.plan}`);\r\n\r\n if (profile.developer.githubUsername) {\r\n console.log(`${label(\"GitHub\")} ${profile.developer.githubUsername}`);\r\n }\r\n\r\n console.log(`${label(\"Token type\")} ${profile.auth.tokenType}`);\r\n\r\n if (profile.auth.scopes.length > 0) {\r\n console.log(`${label(\"Scopes\")} ${profile.auth.scopes.join(\", \")}`);\r\n }\r\n\r\n const currentProject = getCurrentProject();\r\n if (currentProject) {\r\n console.log(`${label(\"Project\")} ${currentProject}`);\r\n }\r\n } catch (error) {\r\n if (error instanceof APIError) {\r\n if (error.status === 401) {\r\n logger.error(\"Token is invalid or expired. Run 'ub login' to re-authenticate.\");\r\n } else {\r\n logger.error(error.message);\r\n }\r\n process.exitCode = 1;\r\n return;\r\n }\r\n logger.error(\"Unable to connect to the urBackend API.\");\r\n process.exitCode = 1;\r\n }\r\n}\r\n","import { apiFetch } from \"../core/api.js\";\r\nimport type { Project, ProjectListItem } from \"../types/project.js\";\r\n\r\ninterface APIResponse {\r\n success: boolean;\r\n data: T;\r\n message: string;\r\n}\r\n\r\nexport async function listProjects(): Promise {\r\n const res = await apiFetch>(\r\n \"/projects\",\r\n { method: \"GET\" },\r\n );\r\n // Backend returns raw array, not wrapped in { data: [...] }\r\n if (Array.isArray(res)) return res;\r\n return (res as APIResponse).data ?? [];\r\n}\r\n\r\nexport async function getProject(projectId: string): Promise {\r\n const res = await apiFetch>(\r\n `/projects/${projectId}`,\r\n { method: \"GET\" },\r\n );\r\n if (\"data\" in res && res.data) return (res as APIResponse).data;\r\n return res as Project;\r\n}\r\n\r\nexport async function createProject(payload: {\r\n name: string;\r\n description?: string;\r\n}): Promise {\r\n const res = await apiFetch>(\"/projects\", {\r\n method: \"POST\",\r\n body: JSON.stringify(payload),\r\n });\r\n if (\"data\" in res && res.data) return (res as APIResponse).data;\r\n return res as Project;\r\n}\r\n\r\nexport async function deleteProject(projectId: string): Promise {\r\n return apiFetch(`/projects/${projectId}`, {\r\n method: \"DELETE\",\r\n });\r\n}","import { listProjects } from \"../../services/project.service.js\";\r\nimport { getCurrentProject, getToken } from \"../../core/config.js\";\r\nimport { formatBytes } from \"../../utils/format.js\";\r\nimport { APIError } from \"../../core/errors.js\";\r\nimport { logger } from \"../../core/logger.js\";\r\n\r\nexport async function projectListCommand(): Promise {\r\n const token = getToken();\r\n if (!token) {\r\n logger.error(\"You are not logged in. Run 'ub login' first.\");\r\n return;\r\n }\r\n\r\n try {\r\n const projects = await listProjects();\r\n const currentProjectId = getCurrentProject();\r\n\r\n if (projects.length === 0) {\r\n logger.info(\"No projects found. Create one at dashboard.urbackend.bitbros.in\");\r\n return;\r\n }\r\n\r\n console.log(`\\nFound ${projects.length} project(s):\\n`);\r\n\r\n for (const project of projects) {\r\n const active = project._id === currentProjectId ? \" (active)\" : \"\";\r\n const db = formatBytes(project.databaseUsed ?? 0);\r\n const colCount = project.collections?.length ?? 0;\r\n\r\n console.log(` ${project.name}${active}`);\r\n console.log(` ID ${project._id}`);\r\n console.log(` Collections ${colCount}`);\r\n console.log(` DB used ${db}`);\r\n\r\n if (project.description) {\r\n console.log(` Description ${project.description}`);\r\n }\r\n\r\n console.log();\r\n }\r\n } catch (error) {\r\n if (error instanceof APIError) {\r\n if (error.status === 401) {\r\n logger.error(\"Token is invalid or expired. Run 'ub login' to re-authenticate.\");\r\n } else {\r\n logger.error(error.message);\r\n }\r\n return;\r\n }\r\n logger.error(\"Unable to connect to the urBackend API.\");\r\n }\r\n}\r\n","import { listProjects, getProject } from \"../../services/project.service.js\";\r\nimport { saveCurrentProject, getToken } from \"../../core/config.js\";\r\nimport { prompt } from \"../../utils/prompt.js\";\r\nimport { APIError } from \"../../core/errors.js\";\r\nimport { logger } from \"../../core/logger.js\";\r\n\r\nexport async function projectUseCommand(projectIdOrName?: string): Promise {\r\n const token = getToken();\r\n if (!token) {\r\n logger.error(\"You are not logged in. Run 'ub login' first.\");\r\n return;\r\n }\r\n\r\n try {\r\n const projects = await listProjects();\r\n\r\n if (projects.length === 0) {\r\n logger.info(\"No projects found. Create one at dashboard.urbackend.bitbros.in\");\r\n return;\r\n }\r\n\r\n let selectedId: string | undefined;\r\n\r\n if (projectIdOrName) {\r\n // Try matching by ID or name\r\n const match = projects.find(\r\n (p) =>\r\n p._id === projectIdOrName ||\r\n p.name.toLowerCase() === projectIdOrName.toLowerCase(),\r\n );\r\n if (!match) {\r\n logger.error(`No project found matching \"${projectIdOrName}\".`);\r\n logger.info(\"Run 'ub project list' to see available projects.\");\r\n return;\r\n }\r\n selectedId = match._id;\r\n } else {\r\n // Interactive selection\r\n console.log(\"\\nAvailable projects:\\n\");\r\n projects.forEach((p, i) => {\r\n console.log(` [${i + 1}] ${p.name} (${p._id})`);\r\n });\r\n console.log();\r\n\r\n const answer = await prompt(\"Enter project number: \");\r\n if (!/^\\d+$/.test(answer)) {\r\n logger.error(\"Invalid selection.\");\r\n return;\r\n }\r\n const index = Number(answer) - 1;\r\n if (isNaN(index) || index < 0 || index >= projects.length) {\r\n logger.error(\"Invalid selection.\");\r\n return;\r\n }\r\n\r\n selectedId = projects[index]._id;\r\n }\r\n\r\n saveCurrentProject(selectedId);\r\n\r\n const project = projects.find((p) => p._id === selectedId)!;\r\n logger.success(`Switched to project: ${project.name} (${selectedId})`);\r\n } catch (error) {\r\n if (error instanceof APIError) {\r\n if (error.status === 401) {\r\n logger.error(\"Token is invalid or expired. Run 'ub login' to re-authenticate.\");\r\n } else {\r\n logger.error(error.message);\r\n }\r\n return;\r\n }\r\n logger.error(\"Unable to connect to the urBackend API.\");\r\n }\r\n}\r\n","import { getProject } from \"../../services/project.service.js\";\r\nimport { getCurrentProject, getToken } from \"../../core/config.js\";\r\nimport { formatBytes, label } from \"../../utils/format.js\";\r\nimport { APIError } from \"../../core/errors.js\";\r\nimport { logger } from \"../../core/logger.js\";\r\n\r\nexport async function projectInfoCommand(projectId?: string): Promise {\r\n const token = getToken();\r\n if (!token) {\r\n logger.error(\"You are not logged in. Run 'ub login' first.\");\r\n return;\r\n }\r\n\r\n const id = projectId ?? getCurrentProject();\r\n\r\n if (!id) {\r\n logger.error(\"No active project. Run 'ub project use' to select one.\");\r\n return;\r\n }\r\n\r\n try {\r\n const project = await getProject(id);\r\n\r\n console.log(`\\n${project.name}\\n`);\r\n console.log(`${label(\"ID\")} ${project._id}`);\r\n\r\n if (project.description) {\r\n console.log(`${label(\"Description\")} ${project.description}`);\r\n }\r\n\r\n console.log(`${label(\"Auth\")} ${project.isAuthEnabled ? \"Enabled\" : \"Disabled\"}`);\r\n console.log(`${label(\"DB used\")} ${formatBytes(project.databaseUsed ?? 0)}`);\r\n console.log(`${label(\"Storage\")} ${formatBytes(project.storageUsed ?? 0)}`);\r\n console.log(`${label(\"Collections\")} ${project.collections?.length ?? 0}`);\r\n\r\n if (project.collections && project.collections.length > 0) {\r\n console.log(\"\\nCollections:\");\r\n for (const col of project.collections) {\r\n const fieldCount = col.model?.length ?? 0;\r\n console.log(` • ${col.name} (${fieldCount} field${fieldCount !== 1 ? \"s\" : \"\"})`);\r\n }\r\n }\r\n\r\n console.log();\r\n } catch (error) {\r\n if (error instanceof APIError) {\r\n if (error.status === 401) {\r\n logger.error(\"Token is invalid or expired. Run 'ub login' to re-authenticate.\");\r\n } else if (error.status === 404) {\r\n logger.error(\"Project not found.\");\r\n } else {\r\n logger.error(error.message);\r\n }\r\n return;\r\n }\r\n logger.error(\"Unable to connect to the urBackend API.\");\r\n }\r\n}\r\n","import { getProject } from \"../../services/project.service.js\";\r\nimport { getToken, getCurrentProject } from \"../../core/config.js\";\r\nimport { label } from \"../../utils/format.js\";\r\nimport { APIError } from \"../../core/errors.js\";\r\nimport { logger } from \"../../core/logger.js\";\r\n\r\nexport async function collectionListCommand(projectId?: string): Promise {\r\n const token = getToken();\r\n if (!token) {\r\n logger.error(\"You are not logged in. Run 'ub login' first.\");\r\n return;\r\n }\r\n\r\n const id = projectId ?? getCurrentProject();\r\n if (!id) {\r\n logger.error(\"No active project. Run 'ub project use' to select one.\");\r\n return;\r\n }\r\n\r\n try {\r\n const project = await getProject(id);\r\n const collections = project.collections ?? [];\r\n\r\n if (collections.length === 0) {\r\n logger.info(`No collections in project \"${project.name}\".`);\r\n return;\r\n }\r\n\r\n console.log(`\\nCollections in \"${project.name}\" (${collections.length}):\\n`);\r\n\r\n for (const col of collections) {\r\n const fieldCount = col.model?.length ?? 0;\r\n const rlsMode = col.rls?.enabled ? col.rls.mode : \"disabled\";\r\n\r\n console.log(` ${col.name}`);\r\n console.log(` ${label(\"Fields\", 10)} ${fieldCount}`);\r\n console.log(` ${label(\"RLS\", 10)} ${rlsMode}`);\r\n\r\n if (col.model && col.model.length > 0) {\r\n const fieldSummary = col.model\r\n .slice(0, 5)\r\n .map((f) => `${f.key}: ${f.type}${f.required ? \"*\" : \"\"}`)\r\n .join(\", \");\r\n const extra = col.model.length > 5 ? ` +${col.model.length - 5} more` : \"\";\r\n console.log(` ${label(\"Schema\", 10)} ${fieldSummary}${extra}`);\r\n }\r\n\r\n console.log();\r\n }\r\n\r\n console.log(\" * = required field\");\r\n console.log();\r\n } catch (error) {\r\n if (error instanceof APIError) {\r\n if (error.status === 401) {\r\n logger.error(\"Token is invalid or expired. Run 'ub login' to re-authenticate.\");\r\n } else if (error.status === 404) {\r\n logger.error(\"Project not found or access denied.\");\r\n } else {\r\n logger.error(error.message);\r\n }\r\n return;\r\n }\r\n logger.error(\"Unable to connect to the urBackend API.\");\r\n }\r\n}\r\n","import { apiFetch } from \"../core/api.js\";\r\nimport type { Collection } from \"../types/project.js\";\r\n\r\ninterface CollectionResponse {\r\n data: Collection;\r\n message: string;\r\n}\r\n\r\n/**\r\n * Creates a new collection inside a project.\r\n * Endpoint: POST /projects/:projectId/collections\r\n */\r\nexport async function createCollection(\r\n projectId: string,\r\n payload: { name: string; model: Collection[\"model\"] },\r\n): Promise {\r\n const res = await apiFetch(\r\n `/projects/${projectId}/collections`,\r\n {\r\n method: \"POST\",\r\n body: JSON.stringify(payload),\r\n },\r\n );\r\n return res.data;\r\n}\r\n\r\n/**\r\n * Deletes a collection from a project.\r\n * Endpoint: DELETE /projects/:projectId/collections/:collectionName\r\n */\r\nexport async function deleteCollection(\r\n projectId: string,\r\n collectionName: string,\r\n): Promise {\r\n const encodedName = encodeURIComponent(collectionName);\r\n return apiFetch(\r\n `/projects/${projectId}/collections/${encodedName}`,\r\n { method: \"DELETE\" },\r\n );\r\n}\r\n","import { deleteCollection } from \"../../services/collection.service.js\";\r\nimport { getProject } from \"../../services/project.service.js\";\r\nimport { getToken, getCurrentProject } from \"../../core/config.js\";\r\nimport { confirm } from \"../../utils/prompt.js\";\r\nimport { APIError } from \"../../core/errors.js\";\r\nimport { logger } from \"../../core/logger.js\";\r\n\r\nexport interface CollectionDeleteOptions {\r\n force?: boolean;\r\n project?: string;\r\n}\r\n\r\nexport async function collectionDeleteCommand(\r\n collectionName: string,\r\n options: CollectionDeleteOptions,\r\n): Promise {\r\n const token = getToken();\r\n if (!token) {\r\n logger.error(\"You are not logged in. Run 'ub login' first.\");\r\n return;\r\n }\r\n\r\n const projectId = options.project ?? getCurrentProject();\r\n if (!projectId) {\r\n logger.error(\"No active project. Run 'ub project use' to select one.\");\r\n return;\r\n }\r\n\r\n try {\r\n // Verify the collection actually exists before asking for confirmation\r\n const project = await getProject(projectId);\r\n const exists = project.collections?.some((c) => c.name === collectionName);\r\n\r\n if (!exists) {\r\n logger.error(\r\n `Collection \"${collectionName}\" not found in project \"${project.name}\".`,\r\n );\r\n return;\r\n }\r\n\r\n if (!options.force) {\r\n console.log(\r\n `\\n⚠ This will permanently delete the collection \"${collectionName}\" and ALL its data.\\n`,\r\n );\r\n const ok = await confirm(\"Are you sure?\");\r\n if (!ok) {\r\n logger.info(\"Aborted.\");\r\n return;\r\n }\r\n }\r\n\r\n await deleteCollection(projectId, collectionName);\r\n logger.success(`Collection \"${collectionName}\" deleted.`);\r\n } catch (error) {\r\n if (error instanceof APIError) {\r\n if (error.status === 401) {\r\n logger.error(\"Token is invalid or expired. Run 'ub login' to re-authenticate.\");\r\n } else if (error.status === 404) {\r\n logger.error(\"Collection or project not found.\");\r\n } else {\r\n logger.error(error.message);\r\n }\r\n return;\r\n }\r\n logger.error(\"Unable to connect to the urBackend API.\");\r\n }\r\n}\r\n","import { apiFetch } from \"../core/api.js\";\r\n\r\ninterface APIResponse {\r\n success: boolean;\r\n data: T;\r\n message: string;\r\n}\r\n\r\nexport interface GlobalStats {\r\n plan: string;\r\n planExpiresAt: string | null;\r\n limits: {\r\n maxProjects: number;\r\n maxCollections: number;\r\n reqPerDay: number;\r\n storageBytes: number;\r\n mongoBytes: number;\r\n authUsersLimit: number;\r\n };\r\n usage: {\r\n totalProjects: number;\r\n totalCollections: number;\r\n totalStorageUsed: number;\r\n totalDatabaseUsed: number;\r\n totalRequests: number;\r\n totalWebhooks: number;\r\n totalUsers: number;\r\n };\r\n}\r\n\r\nexport interface RecentActivityLog {\r\n id: string;\r\n projectName: string;\r\n projectId: string;\r\n method: string;\r\n path: string;\r\n status: number;\r\n timestamp: string;\r\n}\r\n\r\nexport async function getGlobalStats(): Promise {\r\n const res = await apiFetch>(\"/analytics/stats\", {\r\n method: \"GET\",\r\n });\r\n return res.data;\r\n}\r\n\r\nexport async function getRecentActivity(): Promise {\r\n const res = await apiFetch>(\r\n \"/analytics/activity\",\r\n { method: \"GET\" },\r\n );\r\n if (!Array.isArray(res.data)) {\r\n throw new Error(\"Invalid response from /analytics/activity\");\r\n }\r\n return res.data;\r\n}","import { getGlobalStats, getRecentActivity } from \"../../services/analytics.service.js\";\r\nimport { getProject } from \"../../services/project.service.js\";\r\nimport { getToken, getCurrentProject } from \"../../core/config.js\";\r\nimport { formatBytes, label } from \"../../utils/format.js\";\r\nimport { APIError } from \"../../core/errors.js\";\r\nimport { logger } from \"../../core/logger.js\";\r\n\r\nfunction statusIcon(status: number): string {\r\n if (status < 300) return \"✓\";\r\n if (status < 400) return \"→\";\r\n if (status < 500) return \"⚠\";\r\n return \"✖\";\r\n}\r\n\r\nexport async function statusCommand(): Promise {\r\n const token = getToken();\r\n if (!token) {\r\n logger.error(\"You are not logged in. Run 'ub login' first.\");\r\n return;\r\n }\r\n\r\n const currentProjectId = getCurrentProject();\r\n\r\n try {\r\n const stats = await getGlobalStats();\r\n\r\n console.log(\"\\n── Account ─────────────────────────────────────\");\r\n console.log(`${label(\"Plan\")} ${stats.plan.toUpperCase()}`);\r\n\r\n if (stats.planExpiresAt) {\r\n console.log(`${label(\"Plan expires\")} ${stats.planExpiresAt}`);\r\n }\r\n\r\n console.log(\"\\n── Usage ────────────────────────────────────────\");\r\n console.log(\r\n `${label(\"Projects\")} ${stats.usage.totalProjects} / ${stats.limits.maxProjects}`,\r\n );\r\n console.log(\r\n `${label(\"Collections\")} ${stats.usage.totalCollections} / ${stats.limits.maxCollections}`,\r\n );\r\n console.log(\r\n `${label(\"Database\")} ${formatBytes(stats.usage.totalDatabaseUsed)} / ${formatBytes(stats.limits.mongoBytes)}`,\r\n );\r\n console.log(\r\n `${label(\"Storage\")} ${formatBytes(stats.usage.totalStorageUsed)} / ${formatBytes(stats.limits.storageBytes)}`,\r\n );\r\n console.log(\r\n `${label(\"API requests\")} ${stats.usage.totalRequests.toLocaleString()} / ${stats.limits.reqPerDay.toLocaleString()} today`,\r\n );\r\n console.log(`${label(\"Auth users\")} ${stats.usage.totalUsers} / ${stats.limits.authUsersLimit}`);\r\n console.log(`${label(\"Webhooks\")} ${stats.usage.totalWebhooks}`);\r\n\r\n if (currentProjectId) {\r\n try {\r\n const project = await getProject(currentProjectId);\r\n console.log(\"\\n── Active project ───────────────────────────────\");\r\n console.log(`${label(\"Name\")} ${project.name}`);\r\n console.log(`${label(\"ID\")} ${project._id}`);\r\n console.log(`${label(\"Collections\")} ${project.collections?.length ?? 0}`);\r\n console.log(`${label(\"Auth\")} ${project.isAuthEnabled ? \"Enabled\" : \"Disabled\"}`);\r\n } catch {\r\n // non-fatal\r\n }\r\n } else {\r\n console.log(\"\\nTip: Run 'ub project use' to select a project.\");\r\n }\r\n\r\n try {\r\n const activity = await getRecentActivity();\r\n if (activity.length > 0) {\r\n console.log(\"\\n── Recent activity (last 10) ────────────────────\");\r\n for (const log of activity.slice(0, 10)) {\r\n const icon = statusIcon(log.status);\r\n const time = new Date(log.timestamp).toLocaleTimeString();\r\n console.log(\r\n ` ${icon} [${log.status}] ${log.method.padEnd(6)} ${log.path.padEnd(32)} ${time}`,\r\n );\r\n }\r\n }\r\n } catch {\r\n // non-fatal\r\n }\r\n\r\n console.log();\r\n } catch (error) {\r\n if (error instanceof APIError) {\r\n if (error.status === 401) {\r\n logger.error(\"Token is invalid or expired. Run 'ub login' to re-authenticate.\");\r\n } else {\r\n logger.error(error.message);\r\n }\r\n return;\r\n }\r\n logger.error(\"Unable to connect to the urBackend API.\");\r\n }\r\n}","import { getProfile } from \"../../services/auth.service.js\";\r\nimport { getProject } from \"../../services/project.service.js\";\r\nimport {\r\n getToken,\r\n getCurrentProject,\r\n loadConfig,\r\n configFilePath,\r\n} from \"../../core/config.js\";\r\nimport { APIError } from \"../../core/errors.js\";\r\nimport { logger } from \"../../core/logger.js\";\r\n\r\ninterface DoctorResult {\r\n configFound: boolean;\r\n patValid: boolean;\r\n projectSelected: boolean;\r\n projectAccessible: boolean;\r\n dashboardApiReachable: boolean;\r\n dashboardApiLatencyMs: number | null;\r\n email: string | null;\r\n plan: string | null;\r\n projectName: string | null;\r\n}\r\n\r\nexport interface DoctorOptions {\r\n json?: boolean;\r\n}\r\n\r\nasync function checkApiReachable(apiBase: string): Promise {\r\n const start = Date.now();\r\n try {\r\n const response = await fetch(`${apiBase}/health`, {\r\n signal: AbortSignal.timeout(5000),\r\n });\r\n if (!response.ok) {\r\n return null;\r\n }\r\n return Date.now() - start;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\nexport async function doctorCommand(options: DoctorOptions = {}): Promise {\r\n const config = loadConfig();\r\n const token = getToken();\r\n const currentProjectId = getCurrentProject();\r\n\r\n const result: DoctorResult = {\r\n configFound: false,\r\n patValid: false,\r\n projectSelected: !!currentProjectId,\r\n projectAccessible: false,\r\n dashboardApiReachable: false,\r\n dashboardApiLatencyMs: null,\r\n email: null,\r\n plan: null,\r\n projectName: null,\r\n };\r\n\r\n // 1. Config file\r\n try {\r\n const fs = await import(\"node:fs\");\r\n result.configFound = fs.existsSync(configFilePath());\r\n } catch {\r\n result.configFound = false;\r\n }\r\n\r\n // 2. API reachability (independent of auth)\r\n const latency = await checkApiReachable(config.apiBase);\r\n result.dashboardApiReachable = latency !== null;\r\n result.dashboardApiLatencyMs = latency;\r\n\r\n // 3. PAT validity\r\n if (token) {\r\n try {\r\n const profile = await getProfile();\r\n result.patValid = true;\r\n result.email = profile.developer.email;\r\n result.plan = profile.developer.plan;\r\n } catch {\r\n result.patValid = false;\r\n }\r\n }\r\n\r\n // 4. Project accessibility\r\n if (currentProjectId && result.patValid) {\r\n try {\r\n const project = await getProject(currentProjectId);\r\n result.projectAccessible = true;\r\n result.projectName = project.name;\r\n } catch {\r\n result.projectAccessible = false;\r\n }\r\n }\r\n\r\n // Output\r\n if (options.json) {\r\n console.log(JSON.stringify(result, null, 2));\r\n return;\r\n }\r\n\r\n console.log(\"\\n── urBackend CLI Doctor ─────────────────────────\\n\");\r\n\r\n const ok = \"✓\";\r\n const fail = \"✖\";\r\n const warn = \"⚠\";\r\n\r\n console.log(\r\n ` ${result.configFound ? ok : fail} Config file ${result.configFound ? configFilePath() : \"Not found — run 'ub login'\"}`,\r\n );\r\n\r\n console.log(\r\n ` ${result.dashboardApiReachable ? ok : fail} Dashboard API ${\r\n result.dashboardApiReachable\r\n ? `Reachable (${result.dashboardApiLatencyMs}ms)`\r\n : `Unreachable — ${config.apiBase}`\r\n }`,\r\n );\r\n\r\n if (!token) {\r\n console.log(` ${fail} PAT Not set — run 'ub login'`);\r\n } else {\r\n console.log(\r\n ` ${result.patValid ? ok : fail} PAT ${\r\n result.patValid\r\n ? `Valid (${result.email}, ${result.plan})`\r\n : \"Invalid or expired — run 'ub login'\"\r\n }`,\r\n );\r\n }\r\n\r\n if (!currentProjectId) {\r\n console.log(` ${warn} Active project Not selected — run 'ub project use'`);\r\n } else {\r\n console.log(\r\n ` ${result.projectAccessible ? ok : fail} Active project ${\r\n result.projectAccessible\r\n ? `${result.projectName} (${currentProjectId})`\r\n : `ID ${currentProjectId} — not found or access denied`\r\n }`,\r\n );\r\n }\r\n\r\n console.log();\r\n\r\n // Summary\r\n const allGood =\r\n result.configFound &&\r\n result.patValid &&\r\n result.dashboardApiReachable &&\r\n result.projectSelected &&\r\n result.projectAccessible;\r\n\r\n if (allGood) {\r\n logger.success(\"Everything looks good.\");\r\n } else {\r\n logger.warn(\"Some checks failed. Review the output above.\");\r\n }\r\n\r\n console.log();\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,uBAAwB;;;ACAxB,qBAAe;AACf,IAAAA,oBAAiB;;;ACDjB,qBAAe;AACf,uBAAiB;AAIV,IAAM,aAAa,iBAAAC,QAAK,KAAK,eAAAC,QAAG,QAAQ,GAAG,KAAK;AAEhD,IAAM,cAAc,iBAAAD,QAAK,KAAK,YAAY,aAAa;AAMvD,IAAM,mBACX,QAAQ,IAAI,qBAAqB;;;ADTnC,IAAM,iBAA4B;AAAA,EAChC,SAAS;AACX;AAEO,SAAS,aAAwB;AACtC,MAAI,CAAC,eAAAE,QAAG,WAAW,WAAW,GAAG;AAC/B,WAAO,EAAE,GAAG,eAAe;AAAA,EAC7B;AAEA,MAAI;AACF,UAAM,MAAM,eAAAA,QAAG,aAAa,aAAa,MAAM;AAC/C,WAAO;AAAA,MACL,GAAG;AAAA,MACH,GAAG,KAAK,MAAM,GAAG;AAAA,IACnB;AAAA,EACF,QAAQ;AACN,WAAO,EAAE,GAAG,eAAe;AAAA,EAC7B;AACF;AAEO,SAAS,WAAW,QAAyB;AAClD,MAAI,CAAC,eAAAA,QAAG,WAAW,UAAU,GAAG;AAC/B,mBAAAA,QAAG,UAAU,YAAY,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AAAA,EAC1D;AAGA,QAAM,MAAM,cAAc;AAC3B,iBAAAA,QAAG,cAAc,KAAK,KAAK,UAAU,QAAQ,MAAM,CAAC,GAAG;AAAA,IACpD,UAAU;AAAA,IACV,MAAM;AAAA,EACR,CAAC;AACD,iBAAAA,QAAG,WAAW,KAAK,WAAW;AAC9B,iBAAAA,QAAG,UAAU,YAAY,GAAK;AAC9B,iBAAAA,QAAG,UAAU,aAAa,GAAK;AACjC;AAEO,SAAS,UAAU,OAAqB;AAC7C,QAAM,SAAS,WAAW;AACzB,QAAM,aAAwB,EAAE,GAAG,QAAQ,KAAK,MAAM;AACvD,MAAI,OAAO,OAAO,OAAO,QAAQ,OAAO;AACtC,WAAO,WAAW;AAAA,EACpB;AACA,aAAW,UAAU;AACvB;AAEO,SAAS,aAAmB;AACjC,QAAM,SAAS,WAAW;AAC1B,SAAO,OAAO;AACd,SAAO,OAAO;AACd,aAAW,MAAM;AACnB;AAEO,SAAS,WAA+B;AAC7C,SAAO,WAAW,EAAE;AACtB;AAEO,SAAS,mBAAmB,WAAyB;AAC1D,QAAM,SAAS,WAAW;AAC1B,aAAW,EAAE,GAAG,QAAQ,gBAAgB,UAAU,CAAC;AACrD;AAEO,SAAS,oBAAwC;AACtD,SAAO,WAAW,EAAE;AACtB;AAEO,SAAS,iBAAyB;AACvC,SAAO;AACT;;;AExEO,IAAM,WAAN,cAAuB,MAAM;AAAA,EAClC,YACkB,QAChB,SACA;AACA,UAAM,OAAO;AAHG;AAIhB,SAAK,OAAO;AAAA,EACd;AAAA,EALkB;AAMpB;;;ACOA,eAAsB,SACpB,UACA,UAAwB,CAAC,GACb;AACZ,QAAM,SAAS,WAAW;AAE1B,QAAM,UAAU,IAAI,QAAQ,QAAQ,OAAO;AAE3C,MAAI,QAAQ,QAAQ,CAAC,QAAQ,IAAI,cAAc,GAAG;AAChD,YAAQ,IAAI,gBAAgB,kBAAkB;AAAA,EAChD;AAEA,QAAM,QAAQ,QAAQ,SAAS,OAAO;AACtC,MAAI,OAAO;AACT,YAAQ,IAAI,iBAAiB,UAAU,KAAK,EAAE;AAAA,EAChD;AAGA,QAAM,OAAO,OAAO,QAAQ,QAAQ,QAAQ,EAAE;AAC9C,QAAM,UAAU,SAAS,WAAW,GAAG,IAAI,WAAW,IAAI,QAAQ;AAClE,QAAM,MAAM,GAAG,IAAI,OAAO,OAAO;AAEjC,MAAI;AAEJ,QAAM,aAAa,IAAI,gBAAgB;AACvC,QAAM,SAAS,QAAQ,UAAU,WAAW;AAC5C,QAAM,YACN,QAAQ,SAAS,SAAY,WAAW,MAAM,WAAW,MAAM,GAAG,IAAM;AAExE,MAAI;AACF,eAAW,MAAM,MAAM,KAAK;AAAA,MAC1B,GAAG;AAAA,MACH;AAAA,MACE;AAAA,IACJ,CAAC;AAAA,EACH,SAAS,OAAO;AACd,UAAM,IAAI;AAAA,MACR;AAAA,MACC,iBAAiB,SAAS,MAAM,SAAS,eACtC,yCACA;AAAA,IACN;AAAA,EACC,UAAE;AACH,QAAI,UAAW,cAAa,SAAS;AAAA,EACvC;AAEA,MAAI,CAAC,SAAS,IAAI;AAChB,QAAI,UAAU,SAAS;AAEvB,QAAI;AACF,YAAM,OAAQ,MAAM,SAAS,KAAK;AAIlC,gBAAU,KAAK,WAAW,KAAK,SAAS;AAAA,IAC1C,QAAQ;AAAA,IAER;AAEA,UAAM,IAAI,SAAS,SAAS,QAAQ,OAAO;AAAA,EAC7C;AAEA,MAAI,SAAS,WAAW,KAAK;AAC3B,WAAO;AAAA,EACT;AAEA,SAAQ,MAAM,SAAS,KAAK;AAC9B;;;ACzEA,eAAsB,aAAa,OAAoC;AACrE,QAAM,MAAM,MAAM,SAAkC,gBAAgB;AAAA,IAClE,QAAQ;AAAA,IACR;AAAA,EACF,CAAC;AAED,SAAO,IAAI;AACb;AAEA,eAAsB,aAAkC;AACtD,QAAM,MAAM,MAAM,SAAkC,gBAAgB;AAAA,IAClE,QAAQ;AAAA,EACV,CAAC;AACD,SAAO,IAAI;AACb;;;ACnBO,SAAS,WAAW,OAAwB;AACjD,SAAO,OAAO,UAAU,YAAY,kBAAkB,KAAK,KAAK;AAClE;;;ACNA,sBAAgC;AAChC,0BAA8B;AAM9B,eAAsB,OAAO,UAAmC;AAC9D,QAAM,SAAK,iCAAgB,EAAE,OAAO,2BAAO,QAAQ,2BAAO,CAAC;AAC3D,QAAM,UAAU,MAAM,GAAG,SAAS,QAAQ,GAAG,KAAK;AAClD,KAAG,MAAM;AACT,SAAO;AACT;AAKA,eAAsB,aAAa,UAAmC;AACpE,QAAM,SAAK,iCAAgB;AAAA,IACzB,OAAO;AAAA,IACP,QAAQ;AAAA,EACV,CAAC;AAGD,6BAAO,MAAM,QAAQ;AAGrB,QAAM,WAAY,GAAW;AAC7B,EAAC,GAAW,iBAAiB,SAAS,eAAe,eAAuB;AAC1E,QAAI,kBAAkB,QAAQ,kBAAkB,QAAQ,kBAAkB,QAAQ;AAChF,eAAS,KAAK,IAAI,aAAa;AAAA,IACjC,WAAW,kBAAkB,UAAU;AACrC,eAAS,KAAK,IAAI,aAAa;AAAA,IACjC,OAAO;AAAA,IAEP;AAAA,EACF;AAEA,QAAM,UAAU,MAAM,GAAG,SAAS,EAAE,GAAG,KAAK;AAC5C,EAAC,GAAW,iBAAiB;AAC7B,KAAG,MAAM;AACT,SAAO;AACT;AAMA,eAAsB,QAAQ,UAAoC;AAChE,QAAM,SAAS,MAAM,OAAO,GAAG,QAAQ,UAAU;AACjD,SAAO,OAAO,YAAY,MAAM,OAAO,OAAO,YAAY,MAAM;AAClE;;;AChDO,SAAS,YAAY,OAAuB;AACjD,MAAI,UAAU,EAAG,QAAO;AACxB,QAAM,QAAQ,CAAC,KAAK,MAAM,MAAM,MAAM,IAAI;AAC1C,QAAM,IAAI,KAAK,MAAM,KAAK,IAAI,KAAK,IAAI,KAAK,IAAI,IAAI,CAAC;AACrD,QAAM,SAAS,QAAQ,KAAK,IAAI,MAAM,CAAC,GAAG,QAAQ,CAAC;AACnD,SAAO,GAAG,KAAK,IAAI,MAAM,CAAC,CAAC;AAC7B;AAiBO,SAAS,MAAM,MAAc,QAAQ,IAAY;AACtD,SAAO,KAAK,OAAO,KAAK;AAC1B;;;AC5BO,IAAM,SAAS;AAAA,EACpB,QAAQ,SAAuB;AAC7B,YAAQ,IAAI,UAAK,OAAO,EAAE;AAAA,EAC5B;AAAA,EAEA,MAAM,SAAuB;AAC3B,YAAQ,MAAM,UAAK,OAAO,EAAE;AAAA,EAC9B;AAAA,EAEA,KAAK,SAAuB;AAC1B,YAAQ,KAAK,UAAK,OAAO,EAAE;AAAA,EAC7B;AAAA,EAEA,KAAK,SAAuB;AAC1B,YAAQ,IAAI,OAAO;AAAA,EACrB;AACF;;;ACRA,eAAsB,eAA8B;AAClD,UAAQ,IAAI,gEAAgE;AAC5E,UAAQ,IAAI,oDAA0C;AAEtD,QAAM,QAAQ,MAAM,aAAa,oCAAoC;AAErE,MAAI,CAAC,WAAW,KAAK,GAAG;AACtB,WAAO;AAAA,MACL;AAAA,IACF;AACE,YAAQ,WAAW;AACrB;AAAA,EACF;AAEA,MAAI;AACF,UAAM,UAAU,MAAM,aAAa,KAAK;AAEvC,QAAI;AACH,gBAAU,KAAK;AAAA,IACjB,QAAQ;AACN,aAAO,MAAM,yDAAyD;AACtE,cAAQ,WAAW;AACpB;AAAA,IACF;AAEC,WAAO,QAAQ,2BAA2B;AAC1C,YAAQ,IAAI,GAAG,MAAM,OAAO,CAAC,IAAI,QAAQ,UAAU,KAAK,EAAE;AAC1D,YAAQ,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,QAAQ,UAAU,IAAI,EAAE;AAExD,QAAI,QAAQ,UAAU,gBAAgB;AACpC,cAAQ,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,QAAQ,UAAU,cAAc,EAAE;AAAA,IACtE;AAEA,QAAI,QAAQ,KAAK,OAAO,SAAS,GAAG;AAClC,cAAQ,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,QAAQ,KAAK,OAAO,KAAK,IAAI,CAAC,EAAE;AAAA,IACpE;AAAA,EACF,SAAS,OAAO;AACd,QAAI,iBAAiB,UAAU;AAC7B,UAAI,MAAM,WAAW,KAAK;AACxB,eAAO,MAAM,kEAAkE;AAAA,MACjF,OAAO;AACL,eAAO,MAAM,MAAM,OAAO;AAAA,MAC5B;AACG,cAAQ,WAAW;AACtB;AAAA,IACF;AACA,WAAO,MAAM,yCAAyC;AACrD,YAAQ,WAAW;AAAA,EACtB;AACF;;;ACtDA,eAAsB,gBAA+B;AACnD,aAAW;AACX,SAAO,QAAQ,4DAA4D;AAC7E;;;ACAA,eAAsB,gBAA+B;AACnD,QAAM,QAAQ,SAAS;AAEvB,MAAI,CAAC,OAAO;AACV,WAAO,MAAM,8CAA8C;AAC1D,YAAQ,WAAW;AACpB;AAAA,EACF;AAEA,MAAI;AACF,UAAM,UAAU,MAAM,WAAW;AAEjC,YAAQ,IAAI,GAAG,MAAM,OAAO,CAAC,IAAI,QAAQ,UAAU,KAAK,EAAE;AAC1D,YAAQ,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,QAAQ,UAAU,IAAI,EAAE;AAExD,QAAI,QAAQ,UAAU,gBAAgB;AACpC,cAAQ,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,QAAQ,UAAU,cAAc,EAAE;AAAA,IACtE;AAEA,YAAQ,IAAI,GAAG,MAAM,YAAY,CAAC,IAAI,QAAQ,KAAK,SAAS,EAAE;AAE9D,QAAI,QAAQ,KAAK,OAAO,SAAS,GAAG;AAClC,cAAQ,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,QAAQ,KAAK,OAAO,KAAK,IAAI,CAAC,EAAE;AAAA,IACpE;AAEA,UAAM,iBAAiB,kBAAkB;AACzC,QAAI,gBAAgB;AAClB,cAAQ,IAAI,GAAG,MAAM,SAAS,CAAC,IAAI,cAAc,EAAE;AAAA,IACrD;AAAA,EACF,SAAS,OAAO;AACd,QAAI,iBAAiB,UAAU;AAC7B,UAAI,MAAM,WAAW,KAAK;AACxB,eAAO,MAAM,iEAAiE;AAAA,MAChF,OAAO;AACL,eAAO,MAAM,MAAM,OAAO;AAAA,MAC5B;AACC,cAAQ,WAAW;AACpB;AAAA,IACF;AACA,WAAO,MAAM,yCAAyC;AACrD,YAAQ,WAAW;AAAA,EACtB;AACF;;;ACvCA,eAAsB,eAA2C;AAC/D,QAAM,MAAM,MAAM;AAAA,IAChB;AAAA,IACA,EAAE,QAAQ,MAAM;AAAA,EAClB;AAEA,MAAI,MAAM,QAAQ,GAAG,EAAG,QAAO;AAC/B,SAAQ,IAAuC,QAAQ,CAAC;AAC1D;AAEA,eAAsB,WAAW,WAAqC;AACpE,QAAM,MAAM,MAAM;AAAA,IAChB,aAAa,SAAS;AAAA,IACtB,EAAE,QAAQ,MAAM;AAAA,EAClB;AACA,MAAI,UAAU,OAAO,IAAI,KAAM,QAAQ,IAA6B;AACpE,SAAO;AACT;;;ACpBA,eAAsB,qBAAoC;AACxD,QAAM,QAAQ,SAAS;AACvB,MAAI,CAAC,OAAO;AACV,WAAO,MAAM,8CAA8C;AAC3D;AAAA,EACF;AAEA,MAAI;AACF,UAAM,WAAW,MAAM,aAAa;AACpC,UAAM,mBAAmB,kBAAkB;AAE3C,QAAI,SAAS,WAAW,GAAG;AACzB,aAAO,KAAK,iEAAiE;AAC7E;AAAA,IACF;AAEA,YAAQ,IAAI;AAAA,QAAW,SAAS,MAAM;AAAA,CAAgB;AAEtD,eAAW,WAAW,UAAU;AAC9B,YAAM,SAAS,QAAQ,QAAQ,mBAAmB,cAAc;AAChE,YAAM,KAAK,YAAY,QAAQ,gBAAgB,CAAC;AAChD,YAAM,WAAW,QAAQ,aAAa,UAAU;AAEhD,cAAQ,IAAI,KAAK,QAAQ,IAAI,GAAG,MAAM,EAAE;AACxC,cAAQ,IAAI,mBAAmB,QAAQ,GAAG,EAAE;AAC5C,cAAQ,IAAI,mBAAmB,QAAQ,EAAE;AACzC,cAAQ,IAAI,mBAAmB,EAAE,EAAE;AAEnC,UAAI,QAAQ,aAAa;AACvB,gBAAQ,IAAI,mBAAmB,QAAQ,WAAW,EAAE;AAAA,MACtD;AAEA,cAAQ,IAAI;AAAA,IACd;AAAA,EACF,SAAS,OAAO;AACd,QAAI,iBAAiB,UAAU;AAC7B,UAAI,MAAM,WAAW,KAAK;AACxB,eAAO,MAAM,iEAAiE;AAAA,MAChF,OAAO;AACL,eAAO,MAAM,MAAM,OAAO;AAAA,MAC5B;AACA;AAAA,IACF;AACA,WAAO,MAAM,yCAAyC;AAAA,EACxD;AACF;;;AC7CA,eAAsB,kBAAkB,iBAAyC;AAC/E,QAAM,QAAQ,SAAS;AACvB,MAAI,CAAC,OAAO;AACV,WAAO,MAAM,8CAA8C;AAC3D;AAAA,EACF;AAEA,MAAI;AACF,UAAM,WAAW,MAAM,aAAa;AAEpC,QAAI,SAAS,WAAW,GAAG;AACzB,aAAO,KAAK,iEAAiE;AAC7E;AAAA,IACF;AAEA,QAAI;AAEJ,QAAI,iBAAiB;AAEnB,YAAM,QAAQ,SAAS;AAAA,QACrB,CAAC,MACC,EAAE,QAAQ,mBACV,EAAE,KAAK,YAAY,MAAM,gBAAgB,YAAY;AAAA,MACzD;AACA,UAAI,CAAC,OAAO;AACV,eAAO,MAAM,8BAA8B,eAAe,IAAI;AAC9D,eAAO,KAAK,kDAAkD;AAC9D;AAAA,MACF;AACA,mBAAa,MAAM;AAAA,IACrB,OAAO;AAEL,cAAQ,IAAI,yBAAyB;AACrC,eAAS,QAAQ,CAAC,GAAG,MAAM;AACzB,gBAAQ,IAAI,MAAM,IAAI,CAAC,KAAK,EAAE,IAAI,KAAK,EAAE,GAAG,GAAG;AAAA,MACjD,CAAC;AACD,cAAQ,IAAI;AAEZ,YAAM,SAAS,MAAM,OAAO,wBAAwB;AACpD,UAAI,CAAC,QAAQ,KAAK,MAAM,GAAG;AACzB,eAAO,MAAM,oBAAoB;AACjC;AAAA,MACF;AACA,YAAM,QAAQ,OAAO,MAAM,IAAI;AAC/B,UAAI,MAAM,KAAK,KAAK,QAAQ,KAAK,SAAS,SAAS,QAAQ;AACzD,eAAO,MAAM,oBAAoB;AACjC;AAAA,MACF;AAEA,mBAAa,SAAS,KAAK,EAAE;AAAA,IAC/B;AAEA,uBAAmB,UAAU;AAE7B,UAAM,UAAU,SAAS,KAAK,CAAC,MAAM,EAAE,QAAQ,UAAU;AACzD,WAAO,QAAQ,wBAAwB,QAAQ,IAAI,KAAK,UAAU,GAAG;AAAA,EACvE,SAAS,OAAO;AACd,QAAI,iBAAiB,UAAU;AAC7B,UAAI,MAAM,WAAW,KAAK;AACxB,eAAO,MAAM,iEAAiE;AAAA,MAChF,OAAO;AACL,eAAO,MAAM,MAAM,OAAO;AAAA,MAC5B;AACA;AAAA,IACF;AACA,WAAO,MAAM,yCAAyC;AAAA,EACxD;AACF;;;ACnEA,eAAsB,mBAAmB,WAAmC;AAC1E,QAAM,QAAQ,SAAS;AACvB,MAAI,CAAC,OAAO;AACV,WAAO,MAAM,8CAA8C;AAC3D;AAAA,EACF;AAEA,QAAM,KAAK,aAAa,kBAAkB;AAE1C,MAAI,CAAC,IAAI;AACP,WAAO,MAAM,wDAAwD;AACrE;AAAA,EACF;AAEA,MAAI;AACF,UAAM,UAAU,MAAM,WAAW,EAAE;AAEnC,YAAQ,IAAI;AAAA,EAAK,QAAQ,IAAI;AAAA,CAAI;AACjC,YAAQ,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,QAAQ,GAAG,EAAE;AAE3C,QAAI,QAAQ,aAAa;AACvB,cAAQ,IAAI,GAAG,MAAM,aAAa,CAAC,IAAI,QAAQ,WAAW,EAAE;AAAA,IAC9D;AAEA,YAAQ,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,QAAQ,gBAAgB,YAAY,UAAU,EAAE;AAChF,YAAQ,IAAI,GAAG,MAAM,SAAS,CAAC,IAAI,YAAY,QAAQ,gBAAgB,CAAC,CAAC,EAAE;AAC3E,YAAQ,IAAI,GAAG,MAAM,SAAS,CAAC,IAAI,YAAY,QAAQ,eAAe,CAAC,CAAC,EAAE;AAC1E,YAAQ,IAAI,GAAG,MAAM,aAAa,CAAC,IAAI,QAAQ,aAAa,UAAU,CAAC,EAAE;AAEzE,QAAI,QAAQ,eAAe,QAAQ,YAAY,SAAS,GAAG;AACzD,cAAQ,IAAI,gBAAgB;AAC5B,iBAAW,OAAO,QAAQ,aAAa;AACrC,cAAM,aAAa,IAAI,OAAO,UAAU;AACxC,gBAAQ,IAAI,YAAO,IAAI,IAAI,KAAK,UAAU,SAAS,eAAe,IAAI,MAAM,EAAE,GAAG;AAAA,MACnF;AAAA,IACF;AAEA,YAAQ,IAAI;AAAA,EACd,SAAS,OAAO;AACd,QAAI,iBAAiB,UAAU;AAC7B,UAAI,MAAM,WAAW,KAAK;AACxB,eAAO,MAAM,iEAAiE;AAAA,MAChF,WAAW,MAAM,WAAW,KAAK;AAC/B,eAAO,MAAM,oBAAoB;AAAA,MACnC,OAAO;AACL,eAAO,MAAM,MAAM,OAAO;AAAA,MAC5B;AACA;AAAA,IACF;AACA,WAAO,MAAM,yCAAyC;AAAA,EACxD;AACF;;;ACnDA,eAAsB,sBAAsB,WAAmC;AAC7E,QAAM,QAAQ,SAAS;AACvB,MAAI,CAAC,OAAO;AACV,WAAO,MAAM,8CAA8C;AAC3D;AAAA,EACF;AAEA,QAAM,KAAK,aAAa,kBAAkB;AAC1C,MAAI,CAAC,IAAI;AACP,WAAO,MAAM,wDAAwD;AACrE;AAAA,EACF;AAEA,MAAI;AACF,UAAM,UAAU,MAAM,WAAW,EAAE;AACnC,UAAM,cAAc,QAAQ,eAAe,CAAC;AAE5C,QAAI,YAAY,WAAW,GAAG;AAC5B,aAAO,KAAK,8BAA8B,QAAQ,IAAI,IAAI;AAC1D;AAAA,IACF;AAEA,YAAQ,IAAI;AAAA,kBAAqB,QAAQ,IAAI,MAAM,YAAY,MAAM;AAAA,CAAM;AAE3E,eAAW,OAAO,aAAa;AAC7B,YAAM,aAAa,IAAI,OAAO,UAAU;AACxC,YAAM,UAAU,IAAI,KAAK,UAAU,IAAI,IAAI,OAAO;AAElD,cAAQ,IAAI,KAAK,IAAI,IAAI,EAAE;AAC3B,cAAQ,IAAI,OAAO,MAAM,UAAU,EAAE,CAAC,IAAI,UAAU,EAAE;AACtD,cAAQ,IAAI,OAAO,MAAM,OAAO,EAAE,CAAC,IAAI,OAAO,EAAE;AAEhD,UAAI,IAAI,SAAS,IAAI,MAAM,SAAS,GAAG;AACrC,cAAM,eAAe,IAAI,MACtB,MAAM,GAAG,CAAC,EACV,IAAI,CAAC,MAAM,GAAG,EAAE,GAAG,KAAK,EAAE,IAAI,GAAG,EAAE,WAAW,MAAM,EAAE,EAAE,EACxD,KAAK,IAAI;AACZ,cAAM,QAAQ,IAAI,MAAM,SAAS,IAAI,KAAK,IAAI,MAAM,SAAS,CAAC,UAAU;AACxE,gBAAQ,IAAI,OAAO,MAAM,UAAU,EAAE,CAAC,IAAI,YAAY,GAAG,KAAK,EAAE;AAAA,MAClE;AAEA,cAAQ,IAAI;AAAA,IACd;AAEA,YAAQ,IAAI,sBAAsB;AAClC,YAAQ,IAAI;AAAA,EACd,SAAS,OAAO;AACd,QAAI,iBAAiB,UAAU;AAC7B,UAAI,MAAM,WAAW,KAAK;AACxB,eAAO,MAAM,iEAAiE;AAAA,MAChF,WAAW,MAAM,WAAW,KAAK;AAC/B,eAAO,MAAM,qCAAqC;AAAA,MACpD,OAAO;AACL,eAAO,MAAM,MAAM,OAAO;AAAA,MAC5B;AACA;AAAA,IACF;AACA,WAAO,MAAM,yCAAyC;AAAA,EACxD;AACF;;;ACnCA,eAAsB,iBACpB,WACA,gBACe;AACf,QAAM,cAAc,mBAAmB,cAAc;AACrD,SAAO;AAAA,IACL,aAAa,SAAS,gBAAgB,WAAW;AAAA,IACjD,EAAE,QAAQ,SAAS;AAAA,EACrB;AACF;;;AC3BA,eAAsB,wBACpB,gBACA,SACe;AACf,QAAM,QAAQ,SAAS;AACvB,MAAI,CAAC,OAAO;AACV,WAAO,MAAM,8CAA8C;AAC3D;AAAA,EACF;AAEA,QAAM,YAAY,QAAQ,WAAW,kBAAkB;AACvD,MAAI,CAAC,WAAW;AACd,WAAO,MAAM,wDAAwD;AACrE;AAAA,EACF;AAEA,MAAI;AAEF,UAAM,UAAU,MAAM,WAAW,SAAS;AAC1C,UAAM,SAAS,QAAQ,aAAa,KAAK,CAAC,MAAM,EAAE,SAAS,cAAc;AAEzE,QAAI,CAAC,QAAQ;AACX,aAAO;AAAA,QACL,eAAe,cAAc,2BAA2B,QAAQ,IAAI;AAAA,MACtE;AACA;AAAA,IACF;AAEA,QAAI,CAAC,QAAQ,OAAO;AAClB,cAAQ;AAAA,QACN;AAAA,uDAAqD,cAAc;AAAA;AAAA,MACrE;AACA,YAAM,KAAK,MAAM,QAAQ,eAAe;AACxC,UAAI,CAAC,IAAI;AACP,eAAO,KAAK,UAAU;AACtB;AAAA,MACF;AAAA,IACF;AAEA,UAAM,iBAAiB,WAAW,cAAc;AAChD,WAAO,QAAQ,eAAe,cAAc,YAAY;AAAA,EAC1D,SAAS,OAAO;AACd,QAAI,iBAAiB,UAAU;AAC7B,UAAI,MAAM,WAAW,KAAK;AACxB,eAAO,MAAM,iEAAiE;AAAA,MAChF,WAAW,MAAM,WAAW,KAAK;AAC/B,eAAO,MAAM,kCAAkC;AAAA,MACjD,OAAO;AACL,eAAO,MAAM,MAAM,OAAO;AAAA,MAC5B;AACA;AAAA,IACF;AACA,WAAO,MAAM,yCAAyC;AAAA,EACxD;AACF;;;AC1BA,eAAsB,iBAAuC;AAC3D,QAAM,MAAM,MAAM,SAAmC,oBAAoB;AAAA,IACvE,QAAQ;AAAA,EACV,CAAC;AACD,SAAO,IAAI;AACb;AAEA,eAAsB,oBAAkD;AACtE,QAAM,MAAM,MAAM;AAAA,IAChB;AAAA,IACA,EAAE,QAAQ,MAAM;AAAA,EAClB;AACA,MAAI,CAAC,MAAM,QAAQ,IAAI,IAAI,GAAG;AAC5B,UAAM,IAAI,MAAM,2CAA2C;AAAA,EAC7D;AACA,SAAO,IAAI;AACb;;;ACjDA,SAAS,WAAW,QAAwB;AAC1C,MAAI,SAAS,IAAK,QAAO;AACzB,MAAI,SAAS,IAAK,QAAO;AACzB,MAAI,SAAS,IAAK,QAAO;AACzB,SAAO;AACT;AAEA,eAAsB,gBAA+B;AACnD,QAAM,QAAQ,SAAS;AACvB,MAAI,CAAC,OAAO;AACV,WAAO,MAAM,8CAA8C;AAC3D;AAAA,EACF;AAEA,QAAM,mBAAmB,kBAAkB;AAE3C,MAAI;AACF,UAAM,QAAQ,MAAM,eAAe;AAEnC,YAAQ,IAAI,uPAAoD;AAChE,YAAQ,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,MAAM,KAAK,YAAY,CAAC,EAAE;AAE1D,QAAI,MAAM,eAAe;AACvB,cAAQ,IAAI,GAAG,MAAM,cAAc,CAAC,IAAI,MAAM,aAAa,EAAE;AAAA,IAC/D;AAEA,YAAQ,IAAI,uQAAqD;AACjE,YAAQ;AAAA,MACN,GAAG,MAAM,UAAU,CAAC,IAAI,MAAM,MAAM,aAAa,MAAM,MAAM,OAAO,WAAW;AAAA,IACjF;AACA,YAAQ;AAAA,MACN,GAAG,MAAM,aAAa,CAAC,IAAI,MAAM,MAAM,gBAAgB,MAAM,MAAM,OAAO,cAAc;AAAA,IAC1F;AACA,YAAQ;AAAA,MACN,GAAG,MAAM,UAAU,CAAC,IAAI,YAAY,MAAM,MAAM,iBAAiB,CAAC,MAAM,YAAY,MAAM,OAAO,UAAU,CAAC;AAAA,IAC9G;AACA,YAAQ;AAAA,MACN,GAAG,MAAM,SAAS,CAAC,IAAI,YAAY,MAAM,MAAM,gBAAgB,CAAC,MAAM,YAAY,MAAM,OAAO,YAAY,CAAC;AAAA,IAC9G;AACA,YAAQ;AAAA,MACN,GAAG,MAAM,cAAc,CAAC,IAAI,MAAM,MAAM,cAAc,eAAe,CAAC,MAAM,MAAM,OAAO,UAAU,eAAe,CAAC;AAAA,IACrH;AACA,YAAQ,IAAI,GAAG,MAAM,YAAY,CAAC,IAAI,MAAM,MAAM,UAAU,MAAM,MAAM,OAAO,cAAc,EAAE;AAC/F,YAAQ,IAAI,GAAG,MAAM,UAAU,CAAC,IAAI,MAAM,MAAM,aAAa,EAAE;AAE/D,QAAI,kBAAkB;AACpB,UAAI;AACF,cAAM,UAAU,MAAM,WAAW,gBAAgB;AACjD,gBAAQ,IAAI,0NAAqD;AACjE,gBAAQ,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,QAAQ,IAAI,EAAE;AAC9C,gBAAQ,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,QAAQ,GAAG,EAAE;AAC3C,gBAAQ,IAAI,GAAG,MAAM,aAAa,CAAC,IAAI,QAAQ,aAAa,UAAU,CAAC,EAAE;AACzE,gBAAQ,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,QAAQ,gBAAgB,YAAY,UAAU,EAAE;AAAA,MAClF,QAAQ;AAAA,MAER;AAAA,IACF,OAAO;AACL,cAAQ,IAAI,kDAAkD;AAAA,IAChE;AAEA,QAAI;AACF,YAAM,WAAW,MAAM,kBAAkB;AACzC,UAAI,SAAS,SAAS,GAAG;AACvB,gBAAQ,IAAI,mKAAqD;AACjE,mBAAW,OAAO,SAAS,MAAM,GAAG,EAAE,GAAG;AACvC,gBAAM,OAAO,WAAW,IAAI,MAAM;AAClC,gBAAM,OAAO,IAAI,KAAK,IAAI,SAAS,EAAE,mBAAmB;AACxD,kBAAQ;AAAA,YACN,KAAK,IAAI,KAAK,IAAI,MAAM,KAAK,IAAI,OAAO,OAAO,CAAC,CAAC,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC,IAAI,IAAI;AAAA,UAClF;AAAA,QACF;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAER;AAEA,YAAQ,IAAI;AAAA,EACd,SAAS,OAAO;AACd,QAAI,iBAAiB,UAAU;AAC7B,UAAI,MAAM,WAAW,KAAK;AACxB,eAAO,MAAM,iEAAiE;AAAA,MAChF,OAAO;AACL,eAAO,MAAM,MAAM,OAAO;AAAA,MAC5B;AACA;AAAA,IACF;AACA,WAAO,MAAM,yCAAyC;AAAA,EACxD;AACF;;;ACpEA,eAAe,kBAAkB,SAAyC;AACxE,QAAM,QAAQ,KAAK,IAAI;AACvB,MAAI;AACH,UAAM,WAAW,MAAM,MAAM,GAAG,OAAO,WAAW;AAAA,MAC/C,QAAQ,YAAY,QAAQ,GAAI;AAAA,IAClC,CAAC;AACD,QAAI,CAAC,SAAS,IAAI;AAChB,aAAO;AAAA,IACT;AACA,WAAO,KAAK,IAAI,IAAI;AAAA,EACtB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,cAAc,UAAyB,CAAC,GAAkB;AAC9E,QAAM,SAAS,WAAW;AAC1B,QAAM,QAAQ,SAAS;AACvB,QAAM,mBAAmB,kBAAkB;AAE3C,QAAM,SAAuB;AAAA,IAC3B,aAAa;AAAA,IACb,UAAU;AAAA,IACV,iBAAiB,CAAC,CAAC;AAAA,IACnB,mBAAmB;AAAA,IACnB,uBAAuB;AAAA,IACvB,uBAAuB;AAAA,IACvB,OAAO;AAAA,IACP,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AAGA,MAAI;AACF,UAAMC,MAAK,MAAM,OAAO,IAAS;AACjC,WAAO,cAAcA,IAAG,WAAW,eAAe,CAAC;AAAA,EACrD,QAAQ;AACN,WAAO,cAAc;AAAA,EACvB;AAGA,QAAM,UAAU,MAAM,kBAAkB,OAAO,OAAO;AACtD,SAAO,wBAAwB,YAAY;AAC3C,SAAO,wBAAwB;AAG/B,MAAI,OAAO;AACT,QAAI;AACF,YAAM,UAAU,MAAM,WAAW;AACjC,aAAO,WAAW;AAClB,aAAO,QAAQ,QAAQ,UAAU;AACjC,aAAO,OAAO,QAAQ,UAAU;AAAA,IAClC,QAAQ;AACN,aAAO,WAAW;AAAA,IACpB;AAAA,EACF;AAGA,MAAI,oBAAoB,OAAO,UAAU;AACvC,QAAI;AACF,YAAM,UAAU,MAAM,WAAW,gBAAgB;AACjD,aAAO,oBAAoB;AAC3B,aAAO,cAAc,QAAQ;AAAA,IAC/B,QAAQ;AACN,aAAO,oBAAoB;AAAA,IAC7B;AAAA,EACF;AAGA,MAAI,QAAQ,MAAM;AAChB,YAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAC3C;AAAA,EACF;AAEA,UAAQ,IAAI,8LAAuD;AAEnE,QAAM,KAAK;AACX,QAAM,OAAO;AACb,QAAM,OAAO;AAEb,UAAQ;AAAA,IACN,KAAK,OAAO,cAAc,KAAK,IAAI,2BAA2B,OAAO,cAAc,eAAe,IAAI,iCAA4B;AAAA,EACpI;AAEA,UAAQ;AAAA,IACN,KAAK,OAAO,wBAAwB,KAAK,IAAI,2BAC3C,OAAO,wBACH,cAAc,OAAO,qBAAqB,QAC1C,sBAAiB,OAAO,OAAO,EACrC;AAAA,EACF;AAEA,MAAI,CAAC,OAAO;AACV,YAAQ,IAAI,KAAK,IAAI,uDAAkD;AAAA,EACzE,OAAO;AACL,YAAQ;AAAA,MACN,KAAK,OAAO,WAAW,KAAK,IAAI,2BAC9B,OAAO,WACH,UAAU,OAAO,KAAK,KAAK,OAAO,IAAI,MACtC,0CACN;AAAA,IACF;AAAA,EACF;AAEA,MAAI,CAAC,kBAAkB;AACrB,YAAQ,IAAI,KAAK,IAAI,kEAA6D;AAAA,EACpF,OAAO;AACL,YAAQ;AAAA,MACN,KAAK,OAAO,oBAAoB,KAAK,IAAI,2BACvC,OAAO,oBACH,GAAG,OAAO,WAAW,KAAK,gBAAgB,MAC1C,MAAM,gBAAgB,oCAC5B;AAAA,IACF;AAAA,EACF;AAEA,UAAQ,IAAI;AAGZ,QAAM,UACJ,OAAO,eACP,OAAO,YACP,OAAO,yBACP,OAAO,mBACP,OAAO;AAET,MAAI,SAAS;AACX,WAAO,QAAQ,wBAAwB;AAAA,EACzC,OAAO;AACL,WAAO,KAAK,8CAA8C;AAAA,EAC5D;AAEA,UAAQ,IAAI;AACd;;;AtBpJA,IAAM,UAAU,IAAI,yBAAQ;AAE5B,QACG,KAAK,IAAI,EACT,YAAY,kEAA6D,EACzE,QAAQ,OAAO;AAIlB,QACG,QAAQ,OAAO,EACf,YAAY,2CAA2C,EACvD,OAAO,YAAY;AAEtB,QACG,QAAQ,QAAQ,EAChB,YAAY,6CAA6C,EACzD,OAAO,aAAa;AAEvB,QACG,QAAQ,QAAQ,EAChB,YAAY,4CAA4C,EACxD,OAAO,aAAa;AAIvB,IAAM,aAAa,QAChB,QAAQ,SAAS,EACjB,YAAY,2BAA2B;AAE1C,WACG,QAAQ,MAAM,EACd,MAAM,IAAI,EACV,YAAY,8BAA8B,EAC1C,OAAO,kBAAkB;AAE5B,WACG,QAAQ,uBAAuB,EAC/B,YAAY,gDAAgD,EAC5D,OAAO,iBAAiB;AAE3B,WACG,QAAQ,kBAAkB,EAC1B,YAAY,oDAAoD,EAChE,OAAO,kBAAkB;AAI5B,IAAM,gBAAgB,QACnB,QAAQ,YAAY,EACpB,MAAM,KAAK,EACX,YAAY,8CAA8C;AAE7D,cACG,QAAQ,MAAM,EACd,MAAM,IAAI,EACV,YAAY,4CAA4C,EACxD,OAAO,6BAA6B,8BAA8B,EAClE,OAAO,CAAC,YAAY,sBAAsB,QAAQ,OAAO,CAAC;AAE7D,cACG,QAAQ,yBAAyB,EACjC,MAAM,IAAI,EACV,YAAY,sCAAsC,EAClD,OAAO,eAAe,0BAA0B,EAChD,OAAO,6BAA6B,8BAA8B,EAClE,OAAO,CAAC,MAAM,YAAY,wBAAwB,MAAM,OAAO,CAAC;AAInE,QACG,QAAQ,QAAQ,EAChB,YAAY,4CAA4C,EACxD,OAAO,aAAa;AAIvB,QACG,QAAQ,QAAQ,EAChB,YAAY,yCAAyC,EACrD,OAAO,UAAU,kDAAkD,EACnE,OAAO,CAAC,YAAY,cAAc,OAAO,CAAC;AAI7C,QAAQ,WAAW,QAAQ,IAAI,EAAE,MAAM,CAAC,QAAe;AACrD,UAAQ,MAAM,UAAK,IAAI,OAAO,EAAE;AAChC,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":["import_node_path","path","os","fs","fs"]} \ No newline at end of file diff --git a/sdks/urbackend-cli/dist/index.d.cts b/sdks/urbackend-cli/dist/index.d.cts new file mode 100644 index 000000000..223e65e89 --- /dev/null +++ b/sdks/urbackend-cli/dist/index.d.cts @@ -0,0 +1,2 @@ + +export { } diff --git a/sdks/urbackend-cli/dist/index.d.ts b/sdks/urbackend-cli/dist/index.d.ts new file mode 100644 index 000000000..223e65e89 --- /dev/null +++ b/sdks/urbackend-cli/dist/index.d.ts @@ -0,0 +1,2 @@ + +export { } diff --git a/sdks/urbackend-cli/dist/index.js b/sdks/urbackend-cli/dist/index.js new file mode 100644 index 000000000..08dc5be52 --- /dev/null +++ b/sdks/urbackend-cli/dist/index.js @@ -0,0 +1,795 @@ +// @urbackend/cli — Official urBackend CLI + +// src/index.ts +import { Command } from "commander"; + +// src/core/config.ts +import fs from "fs"; +import path2 from "path"; + +// src/core/constants.ts +import os from "os"; +import path from "path"; +var CONFIG_DIR = path.join(os.homedir(), ".ub"); +var CONFIG_PATH = path.join(CONFIG_DIR, "config.json"); +var DEFAULT_API_BASE = process.env.URBACKEND_API_URL ?? "https://api.urbackend.bitbros.in"; + +// src/core/config.ts +var DEFAULT_CONFIG = { + apiBase: DEFAULT_API_BASE +}; +function loadConfig() { + if (!fs.existsSync(CONFIG_PATH)) { + return { ...DEFAULT_CONFIG }; + } + try { + const raw = fs.readFileSync(CONFIG_PATH, "utf8"); + return { + ...DEFAULT_CONFIG, + ...JSON.parse(raw) + }; + } catch { + return { ...DEFAULT_CONFIG }; + } +} +function saveConfig(config) { + if (!fs.existsSync(CONFIG_DIR)) { + fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 }); + } + const tmp = CONFIG_PATH + ".tmp"; + fs.writeFileSync(tmp, JSON.stringify(config, null, 2), { + encoding: "utf8", + mode: 384 + }); + fs.renameSync(tmp, CONFIG_PATH); + fs.chmodSync(CONFIG_DIR, 448); + fs.chmodSync(CONFIG_PATH, 384); +} +function saveToken(token) { + const config = loadConfig(); + const nextConfig = { ...config, pat: token }; + if (config.pat && config.pat !== token) { + delete nextConfig.currentProject; + } + saveConfig(nextConfig); +} +function clearToken() { + const config = loadConfig(); + delete config.pat; + delete config.currentProject; + saveConfig(config); +} +function getToken() { + return loadConfig().pat; +} +function saveCurrentProject(projectId) { + const config = loadConfig(); + saveConfig({ ...config, currentProject: projectId }); +} +function getCurrentProject() { + return loadConfig().currentProject; +} +function configFilePath() { + return CONFIG_PATH; +} + +// src/core/errors.ts +var APIError = class extends Error { + constructor(status, message) { + super(message); + this.status = status; + this.name = "APIError"; + } + status; +}; + +// src/core/api.ts +async function apiFetch(endpoint, options = {}) { + const config = loadConfig(); + const headers = new Headers(options.headers); + if (options.body && !headers.has("Content-Type")) { + headers.set("Content-Type", "application/json"); + } + const token = options.token ?? config.pat; + if (token) { + headers.set("Authorization", `Bearer ${token}`); + } + const base = config.apiBase.replace(/\/+$/, ""); + const apiPath = endpoint.startsWith("/") ? endpoint : `/${endpoint}`; + const url = `${base}/api${apiPath}`; + let response; + const controller = new AbortController(); + const signal = options.signal ?? controller.signal; + const timeoutId = options.signal ? void 0 : setTimeout(() => controller.abort(), 15e3); + try { + response = await fetch(url, { + ...options, + headers, + signal + }); + } catch (error) { + throw new APIError( + 0, + error instanceof Error && error.name === "AbortError" ? "The urBackend API request timed out." : "Unable to connect to the urBackend API. Is the server running?" + ); + } finally { + if (timeoutId) clearTimeout(timeoutId); + } + if (!response.ok) { + let message = response.statusText; + try { + const body = await response.json(); + message = body.message ?? body.error ?? message; + } catch { + } + throw new APIError(response.status, message); + } + if (response.status === 204) { + return void 0; + } + return await response.json(); +} + +// src/services/auth.service.ts +async function authenticate(token) { + const res = await apiFetch("/user/cli/me", { + method: "GET", + token + }); + return res.data; +} +async function getProfile() { + const res = await apiFetch("/user/cli/me", { + method: "GET" + }); + return res.data; +} + +// src/utils/token.ts +function isValidPAT(token) { + return typeof token === "string" && /^ubpat_\S{10,}$/.test(token); +} + +// src/utils/prompt.ts +import { createInterface } from "readline/promises"; +import { stdin, stdout } from "process"; +async function prompt(question) { + const rl = createInterface({ input: stdin, output: stdout }); + const answer = (await rl.question(question)).trim(); + rl.close(); + return answer; +} +async function promptSecret(question) { + const rl = createInterface({ + input: stdin, + output: stdout + }); + stdout.write(question); + const oldWrite = rl._writeToOutput; + rl._writeToOutput = function _writeToOutput(stringToWrite) { + if (stringToWrite === "\r" || stringToWrite === "\n" || stringToWrite === "\r\n") { + oldWrite.call(rl, stringToWrite); + } else if (stringToWrite === question) { + oldWrite.call(rl, stringToWrite); + } else { + } + }; + const answer = (await rl.question("")).trim(); + rl._writeToOutput = oldWrite; + rl.close(); + return answer; +} +async function confirm(question) { + const answer = await prompt(`${question} (y/n): `); + return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes"; +} + +// src/utils/format.ts +function formatBytes(bytes) { + if (bytes === 0) return "0 B"; + const units = ["B", "KB", "MB", "GB", "TB"]; + const i = Math.floor(Math.log(bytes) / Math.log(1024)); + const value = (bytes / Math.pow(1024, i)).toFixed(2); + return `${value} ${units[i]}`; +} +function label(text, width = 14) { + return text.padEnd(width); +} + +// src/core/logger.ts +var logger = { + success(message) { + console.log(`\u2713 ${message}`); + }, + error(message) { + console.error(`\u2716 ${message}`); + }, + warn(message) { + console.warn(`\u26A0 ${message}`); + }, + info(message) { + console.log(message); + } +}; + +// src/commands/auth/login.ts +async function loginCommand() { + console.log("Generate a Personal Access Token from the urBackend dashboard:"); + console.log(" Settings \u2192 Access Tokens \u2192 New Token\n"); + const token = await promptSecret("Paste your Personal Access Token: "); + if (!isValidPAT(token)) { + logger.error( + "Invalid token format. urBackend PATs start with 'ubpat_' followed by at least 10 characters." + ); + process.exitCode = 1; + return; + } + try { + const profile = await authenticate(token); + try { + saveToken(token); + } catch { + logger.error("Authenticated, but failed to persist the token locally."); + process.exitCode = 1; + return; + } + logger.success("Logged in successfully.\n"); + console.log(`${label("Email")} ${profile.developer.email}`); + console.log(`${label("Plan")} ${profile.developer.plan}`); + if (profile.developer.githubUsername) { + console.log(`${label("GitHub")} ${profile.developer.githubUsername}`); + } + if (profile.auth.scopes.length > 0) { + console.log(`${label("Scopes")} ${profile.auth.scopes.join(", ")}`); + } + } catch (error) { + if (error instanceof APIError) { + if (error.status === 401) { + logger.error("Invalid or expired token. Generate a new one from the dashboard."); + } else { + logger.error(error.message); + } + process.exitCode = 1; + return; + } + logger.error("Unable to connect to the urBackend API."); + process.exitCode = 1; + } +} + +// src/commands/auth/logout.ts +async function logoutCommand() { + clearToken(); + logger.success("Logged out. Your token has been removed from this machine."); +} + +// src/commands/auth/whoami.ts +async function whoamiCommand() { + const token = getToken(); + if (!token) { + logger.error("You are not logged in. Run 'ub login' first."); + process.exitCode = 1; + return; + } + try { + const profile = await getProfile(); + console.log(`${label("Email")} ${profile.developer.email}`); + console.log(`${label("Plan")} ${profile.developer.plan}`); + if (profile.developer.githubUsername) { + console.log(`${label("GitHub")} ${profile.developer.githubUsername}`); + } + console.log(`${label("Token type")} ${profile.auth.tokenType}`); + if (profile.auth.scopes.length > 0) { + console.log(`${label("Scopes")} ${profile.auth.scopes.join(", ")}`); + } + const currentProject = getCurrentProject(); + if (currentProject) { + console.log(`${label("Project")} ${currentProject}`); + } + } catch (error) { + if (error instanceof APIError) { + if (error.status === 401) { + logger.error("Token is invalid or expired. Run 'ub login' to re-authenticate."); + } else { + logger.error(error.message); + } + process.exitCode = 1; + return; + } + logger.error("Unable to connect to the urBackend API."); + process.exitCode = 1; + } +} + +// src/services/project.service.ts +async function listProjects() { + const res = await apiFetch( + "/projects", + { method: "GET" } + ); + if (Array.isArray(res)) return res; + return res.data ?? []; +} +async function getProject(projectId) { + const res = await apiFetch( + `/projects/${projectId}`, + { method: "GET" } + ); + if ("data" in res && res.data) return res.data; + return res; +} + +// src/commands/project/list.ts +async function projectListCommand() { + const token = getToken(); + if (!token) { + logger.error("You are not logged in. Run 'ub login' first."); + return; + } + try { + const projects = await listProjects(); + const currentProjectId = getCurrentProject(); + if (projects.length === 0) { + logger.info("No projects found. Create one at dashboard.urbackend.bitbros.in"); + return; + } + console.log(` +Found ${projects.length} project(s): +`); + for (const project of projects) { + const active = project._id === currentProjectId ? " (active)" : ""; + const db = formatBytes(project.databaseUsed ?? 0); + const colCount = project.collections?.length ?? 0; + console.log(` ${project.name}${active}`); + console.log(` ID ${project._id}`); + console.log(` Collections ${colCount}`); + console.log(` DB used ${db}`); + if (project.description) { + console.log(` Description ${project.description}`); + } + console.log(); + } + } catch (error) { + if (error instanceof APIError) { + if (error.status === 401) { + logger.error("Token is invalid or expired. Run 'ub login' to re-authenticate."); + } else { + logger.error(error.message); + } + return; + } + logger.error("Unable to connect to the urBackend API."); + } +} + +// src/commands/project/use.ts +async function projectUseCommand(projectIdOrName) { + const token = getToken(); + if (!token) { + logger.error("You are not logged in. Run 'ub login' first."); + return; + } + try { + const projects = await listProjects(); + if (projects.length === 0) { + logger.info("No projects found. Create one at dashboard.urbackend.bitbros.in"); + return; + } + let selectedId; + if (projectIdOrName) { + const match = projects.find( + (p) => p._id === projectIdOrName || p.name.toLowerCase() === projectIdOrName.toLowerCase() + ); + if (!match) { + logger.error(`No project found matching "${projectIdOrName}".`); + logger.info("Run 'ub project list' to see available projects."); + return; + } + selectedId = match._id; + } else { + console.log("\nAvailable projects:\n"); + projects.forEach((p, i) => { + console.log(` [${i + 1}] ${p.name} (${p._id})`); + }); + console.log(); + const answer = await prompt("Enter project number: "); + if (!/^\d+$/.test(answer)) { + logger.error("Invalid selection."); + return; + } + const index = Number(answer) - 1; + if (isNaN(index) || index < 0 || index >= projects.length) { + logger.error("Invalid selection."); + return; + } + selectedId = projects[index]._id; + } + saveCurrentProject(selectedId); + const project = projects.find((p) => p._id === selectedId); + logger.success(`Switched to project: ${project.name} (${selectedId})`); + } catch (error) { + if (error instanceof APIError) { + if (error.status === 401) { + logger.error("Token is invalid or expired. Run 'ub login' to re-authenticate."); + } else { + logger.error(error.message); + } + return; + } + logger.error("Unable to connect to the urBackend API."); + } +} + +// src/commands/project/info.ts +async function projectInfoCommand(projectId) { + const token = getToken(); + if (!token) { + logger.error("You are not logged in. Run 'ub login' first."); + return; + } + const id = projectId ?? getCurrentProject(); + if (!id) { + logger.error("No active project. Run 'ub project use' to select one."); + return; + } + try { + const project = await getProject(id); + console.log(` +${project.name} +`); + console.log(`${label("ID")} ${project._id}`); + if (project.description) { + console.log(`${label("Description")} ${project.description}`); + } + console.log(`${label("Auth")} ${project.isAuthEnabled ? "Enabled" : "Disabled"}`); + console.log(`${label("DB used")} ${formatBytes(project.databaseUsed ?? 0)}`); + console.log(`${label("Storage")} ${formatBytes(project.storageUsed ?? 0)}`); + console.log(`${label("Collections")} ${project.collections?.length ?? 0}`); + if (project.collections && project.collections.length > 0) { + console.log("\nCollections:"); + for (const col of project.collections) { + const fieldCount = col.model?.length ?? 0; + console.log(` \u2022 ${col.name} (${fieldCount} field${fieldCount !== 1 ? "s" : ""})`); + } + } + console.log(); + } catch (error) { + if (error instanceof APIError) { + if (error.status === 401) { + logger.error("Token is invalid or expired. Run 'ub login' to re-authenticate."); + } else if (error.status === 404) { + logger.error("Project not found."); + } else { + logger.error(error.message); + } + return; + } + logger.error("Unable to connect to the urBackend API."); + } +} + +// src/commands/collection/list.ts +async function collectionListCommand(projectId) { + const token = getToken(); + if (!token) { + logger.error("You are not logged in. Run 'ub login' first."); + return; + } + const id = projectId ?? getCurrentProject(); + if (!id) { + logger.error("No active project. Run 'ub project use' to select one."); + return; + } + try { + const project = await getProject(id); + const collections = project.collections ?? []; + if (collections.length === 0) { + logger.info(`No collections in project "${project.name}".`); + return; + } + console.log(` +Collections in "${project.name}" (${collections.length}): +`); + for (const col of collections) { + const fieldCount = col.model?.length ?? 0; + const rlsMode = col.rls?.enabled ? col.rls.mode : "disabled"; + console.log(` ${col.name}`); + console.log(` ${label("Fields", 10)} ${fieldCount}`); + console.log(` ${label("RLS", 10)} ${rlsMode}`); + if (col.model && col.model.length > 0) { + const fieldSummary = col.model.slice(0, 5).map((f) => `${f.key}: ${f.type}${f.required ? "*" : ""}`).join(", "); + const extra = col.model.length > 5 ? ` +${col.model.length - 5} more` : ""; + console.log(` ${label("Schema", 10)} ${fieldSummary}${extra}`); + } + console.log(); + } + console.log(" * = required field"); + console.log(); + } catch (error) { + if (error instanceof APIError) { + if (error.status === 401) { + logger.error("Token is invalid or expired. Run 'ub login' to re-authenticate."); + } else if (error.status === 404) { + logger.error("Project not found or access denied."); + } else { + logger.error(error.message); + } + return; + } + logger.error("Unable to connect to the urBackend API."); + } +} + +// src/services/collection.service.ts +async function deleteCollection(projectId, collectionName) { + const encodedName = encodeURIComponent(collectionName); + return apiFetch( + `/projects/${projectId}/collections/${encodedName}`, + { method: "DELETE" } + ); +} + +// src/commands/collection/delete.ts +async function collectionDeleteCommand(collectionName, options) { + const token = getToken(); + if (!token) { + logger.error("You are not logged in. Run 'ub login' first."); + return; + } + const projectId = options.project ?? getCurrentProject(); + if (!projectId) { + logger.error("No active project. Run 'ub project use' to select one."); + return; + } + try { + const project = await getProject(projectId); + const exists = project.collections?.some((c) => c.name === collectionName); + if (!exists) { + logger.error( + `Collection "${collectionName}" not found in project "${project.name}".` + ); + return; + } + if (!options.force) { + console.log( + ` +\u26A0 This will permanently delete the collection "${collectionName}" and ALL its data. +` + ); + const ok = await confirm("Are you sure?"); + if (!ok) { + logger.info("Aborted."); + return; + } + } + await deleteCollection(projectId, collectionName); + logger.success(`Collection "${collectionName}" deleted.`); + } catch (error) { + if (error instanceof APIError) { + if (error.status === 401) { + logger.error("Token is invalid or expired. Run 'ub login' to re-authenticate."); + } else if (error.status === 404) { + logger.error("Collection or project not found."); + } else { + logger.error(error.message); + } + return; + } + logger.error("Unable to connect to the urBackend API."); + } +} + +// src/services/analytics.service.ts +async function getGlobalStats() { + const res = await apiFetch("/analytics/stats", { + method: "GET" + }); + return res.data; +} +async function getRecentActivity() { + const res = await apiFetch( + "/analytics/activity", + { method: "GET" } + ); + if (!Array.isArray(res.data)) { + throw new Error("Invalid response from /analytics/activity"); + } + return res.data; +} + +// src/commands/status/index.ts +function statusIcon(status) { + if (status < 300) return "\u2713"; + if (status < 400) return "\u2192"; + if (status < 500) return "\u26A0"; + return "\u2716"; +} +async function statusCommand() { + const token = getToken(); + if (!token) { + logger.error("You are not logged in. Run 'ub login' first."); + return; + } + const currentProjectId = getCurrentProject(); + try { + const stats = await getGlobalStats(); + console.log("\n\u2500\u2500 Account \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"); + console.log(`${label("Plan")} ${stats.plan.toUpperCase()}`); + if (stats.planExpiresAt) { + console.log(`${label("Plan expires")} ${stats.planExpiresAt}`); + } + console.log("\n\u2500\u2500 Usage \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"); + console.log( + `${label("Projects")} ${stats.usage.totalProjects} / ${stats.limits.maxProjects}` + ); + console.log( + `${label("Collections")} ${stats.usage.totalCollections} / ${stats.limits.maxCollections}` + ); + console.log( + `${label("Database")} ${formatBytes(stats.usage.totalDatabaseUsed)} / ${formatBytes(stats.limits.mongoBytes)}` + ); + console.log( + `${label("Storage")} ${formatBytes(stats.usage.totalStorageUsed)} / ${formatBytes(stats.limits.storageBytes)}` + ); + console.log( + `${label("API requests")} ${stats.usage.totalRequests.toLocaleString()} / ${stats.limits.reqPerDay.toLocaleString()} today` + ); + console.log(`${label("Auth users")} ${stats.usage.totalUsers} / ${stats.limits.authUsersLimit}`); + console.log(`${label("Webhooks")} ${stats.usage.totalWebhooks}`); + if (currentProjectId) { + try { + const project = await getProject(currentProjectId); + console.log("\n\u2500\u2500 Active project \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"); + console.log(`${label("Name")} ${project.name}`); + console.log(`${label("ID")} ${project._id}`); + console.log(`${label("Collections")} ${project.collections?.length ?? 0}`); + console.log(`${label("Auth")} ${project.isAuthEnabled ? "Enabled" : "Disabled"}`); + } catch { + } + } else { + console.log("\nTip: Run 'ub project use' to select a project."); + } + try { + const activity = await getRecentActivity(); + if (activity.length > 0) { + console.log("\n\u2500\u2500 Recent activity (last 10) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"); + for (const log of activity.slice(0, 10)) { + const icon = statusIcon(log.status); + const time = new Date(log.timestamp).toLocaleTimeString(); + console.log( + ` ${icon} [${log.status}] ${log.method.padEnd(6)} ${log.path.padEnd(32)} ${time}` + ); + } + } + } catch { + } + console.log(); + } catch (error) { + if (error instanceof APIError) { + if (error.status === 401) { + logger.error("Token is invalid or expired. Run 'ub login' to re-authenticate."); + } else { + logger.error(error.message); + } + return; + } + logger.error("Unable to connect to the urBackend API."); + } +} + +// src/commands/doctor/index.ts +async function checkApiReachable(apiBase) { + const start = Date.now(); + try { + const response = await fetch(`${apiBase}/health`, { + signal: AbortSignal.timeout(5e3) + }); + if (!response.ok) { + return null; + } + return Date.now() - start; + } catch { + return null; + } +} +async function doctorCommand(options = {}) { + const config = loadConfig(); + const token = getToken(); + const currentProjectId = getCurrentProject(); + const result = { + configFound: false, + patValid: false, + projectSelected: !!currentProjectId, + projectAccessible: false, + dashboardApiReachable: false, + dashboardApiLatencyMs: null, + email: null, + plan: null, + projectName: null + }; + try { + const fs2 = await import("fs"); + result.configFound = fs2.existsSync(configFilePath()); + } catch { + result.configFound = false; + } + const latency = await checkApiReachable(config.apiBase); + result.dashboardApiReachable = latency !== null; + result.dashboardApiLatencyMs = latency; + if (token) { + try { + const profile = await getProfile(); + result.patValid = true; + result.email = profile.developer.email; + result.plan = profile.developer.plan; + } catch { + result.patValid = false; + } + } + if (currentProjectId && result.patValid) { + try { + const project = await getProject(currentProjectId); + result.projectAccessible = true; + result.projectName = project.name; + } catch { + result.projectAccessible = false; + } + } + if (options.json) { + console.log(JSON.stringify(result, null, 2)); + return; + } + console.log("\n\u2500\u2500 urBackend CLI Doctor \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"); + const ok = "\u2713"; + const fail = "\u2716"; + const warn = "\u26A0"; + console.log( + ` ${result.configFound ? ok : fail} Config file ${result.configFound ? configFilePath() : "Not found \u2014 run 'ub login'"}` + ); + console.log( + ` ${result.dashboardApiReachable ? ok : fail} Dashboard API ${result.dashboardApiReachable ? `Reachable (${result.dashboardApiLatencyMs}ms)` : `Unreachable \u2014 ${config.apiBase}`}` + ); + if (!token) { + console.log(` ${fail} PAT Not set \u2014 run 'ub login'`); + } else { + console.log( + ` ${result.patValid ? ok : fail} PAT ${result.patValid ? `Valid (${result.email}, ${result.plan})` : "Invalid or expired \u2014 run 'ub login'"}` + ); + } + if (!currentProjectId) { + console.log(` ${warn} Active project Not selected \u2014 run 'ub project use'`); + } else { + console.log( + ` ${result.projectAccessible ? ok : fail} Active project ${result.projectAccessible ? `${result.projectName} (${currentProjectId})` : `ID ${currentProjectId} \u2014 not found or access denied`}` + ); + } + console.log(); + const allGood = result.configFound && result.patValid && result.dashboardApiReachable && result.projectSelected && result.projectAccessible; + if (allGood) { + logger.success("Everything looks good."); + } else { + logger.warn("Some checks failed. Review the output above."); + } + console.log(); +} + +// src/index.ts +var program = new Command(); +program.name("ub").description("Official urBackend CLI \u2014 manage projects, schemas, and more").version("0.1.0"); +program.command("login").description("Authenticate with a Personal Access Token").action(loginCommand); +program.command("logout").description("Remove stored credentials from this machine").action(logoutCommand); +program.command("whoami").description("Show the currently authenticated developer").action(whoamiCommand); +var projectCmd = program.command("project").description("Manage urBackend projects"); +projectCmd.command("list").alias("ls").description("List all accessible projects").action(projectListCommand); +projectCmd.command("use [projectIdOrName]").description("Set the active project for subsequent commands").action(projectUseCommand); +projectCmd.command("info [projectId]").description("Show details for the active (or specified) project").action(projectInfoCommand); +var collectionCmd = program.command("collection").alias("col").description("Manage collections inside the active project"); +collectionCmd.command("list").alias("ls").description("List all collections in the active project").option("-p, --project ", "Target a specific project ID").action((options) => collectionListCommand(options.project)); +collectionCmd.command("delete ").alias("rm").description("Delete a collection and all its data").option("-f, --force", "Skip confirmation prompt").option("-p, --project ", "Target a specific project ID").action((name, options) => collectionDeleteCommand(name, options)); +program.command("status").description("Show account usage and recent API activity").action(statusCommand); +program.command("doctor").description("Run diagnostic checks on your CLI setup").option("--json", "Output results as JSON (useful for CI/AI agents)").action((options) => doctorCommand(options)); +program.parseAsync(process.argv).catch((err) => { + console.error(`\u2716 ${err.message}`); + process.exit(1); +}); +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/sdks/urbackend-cli/dist/index.js.map b/sdks/urbackend-cli/dist/index.js.map new file mode 100644 index 000000000..33770149e --- /dev/null +++ b/sdks/urbackend-cli/dist/index.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/index.ts","../src/core/config.ts","../src/core/constants.ts","../src/core/errors.ts","../src/core/api.ts","../src/services/auth.service.ts","../src/utils/token.ts","../src/utils/prompt.ts","../src/utils/format.ts","../src/core/logger.ts","../src/commands/auth/login.ts","../src/commands/auth/logout.ts","../src/commands/auth/whoami.ts","../src/services/project.service.ts","../src/commands/project/list.ts","../src/commands/project/use.ts","../src/commands/project/info.ts","../src/commands/collection/list.ts","../src/services/collection.service.ts","../src/commands/collection/delete.ts","../src/services/analytics.service.ts","../src/commands/status/index.ts","../src/commands/doctor/index.ts"],"sourcesContent":["import { Command } from \"commander\";\r\nimport { loginCommand } from \"./commands/auth/login.js\";\r\nimport { logoutCommand } from \"./commands/auth/logout.js\";\r\nimport { whoamiCommand } from \"./commands/auth/whoami.js\";\r\nimport { projectListCommand } from \"./commands/project/list.js\";\r\nimport { projectUseCommand } from \"./commands/project/use.js\";\r\nimport { projectInfoCommand } from \"./commands/project/info.js\";\r\nimport { collectionListCommand } from \"./commands/collection/list.js\";\r\nimport { collectionDeleteCommand } from \"./commands/collection/delete.js\";\r\nimport { statusCommand } from \"./commands/status/index.js\";\r\nimport { doctorCommand } from \"./commands/doctor/index.js\";\r\n\r\nconst program = new Command();\r\n\r\nprogram\r\n .name(\"ub\")\r\n .description(\"Official urBackend CLI — manage projects, schemas, and more\")\r\n .version(\"0.1.0\");\r\n\r\n// ── Authentication ──────────────────────────────────────────────────────────\r\n\r\nprogram\r\n .command(\"login\")\r\n .description(\"Authenticate with a Personal Access Token\")\r\n .action(loginCommand);\r\n\r\nprogram\r\n .command(\"logout\")\r\n .description(\"Remove stored credentials from this machine\")\r\n .action(logoutCommand);\r\n\r\nprogram\r\n .command(\"whoami\")\r\n .description(\"Show the currently authenticated developer\")\r\n .action(whoamiCommand);\r\n\r\n// ── Projects ─────────────────────────────────────────────────────────────────\r\n\r\nconst projectCmd = program\r\n .command(\"project\")\r\n .description(\"Manage urBackend projects\");\r\n\r\nprojectCmd\r\n .command(\"list\")\r\n .alias(\"ls\")\r\n .description(\"List all accessible projects\")\r\n .action(projectListCommand);\r\n\r\nprojectCmd\r\n .command(\"use [projectIdOrName]\")\r\n .description(\"Set the active project for subsequent commands\")\r\n .action(projectUseCommand);\r\n\r\nprojectCmd\r\n .command(\"info [projectId]\")\r\n .description(\"Show details for the active (or specified) project\")\r\n .action(projectInfoCommand);\r\n\r\n// ── Collections ──────────────────────────────────────────────────────────────\r\n\r\nconst collectionCmd = program\r\n .command(\"collection\")\r\n .alias(\"col\")\r\n .description(\"Manage collections inside the active project\");\r\n\r\ncollectionCmd\r\n .command(\"list\")\r\n .alias(\"ls\")\r\n .description(\"List all collections in the active project\")\r\n .option(\"-p, --project \", \"Target a specific project ID\")\r\n .action((options) => collectionListCommand(options.project));\r\n\r\ncollectionCmd\r\n .command(\"delete \")\r\n .alias(\"rm\")\r\n .description(\"Delete a collection and all its data\")\r\n .option(\"-f, --force\", \"Skip confirmation prompt\")\r\n .option(\"-p, --project \", \"Target a specific project ID\")\r\n .action((name, options) => collectionDeleteCommand(name, options));\r\n\r\n// ── Status ────────────────────────────────────────────────────────────────────\r\n\r\nprogram\r\n .command(\"status\")\r\n .description(\"Show account usage and recent API activity\")\r\n .action(statusCommand);\r\n\r\n// ── Doctor ────────────────────────────────────────────────────────────────────\r\n\r\nprogram\r\n .command(\"doctor\")\r\n .description(\"Run diagnostic checks on your CLI setup\")\r\n .option(\"--json\", \"Output results as JSON (useful for CI/AI agents)\")\r\n .action((options) => doctorCommand(options));\r\n\r\n// ── Parse ─────────────────────────────────────────────────────────────────────\r\n\r\nprogram.parseAsync(process.argv).catch((err: Error) => {\r\n console.error(`✖ ${err.message}`);\r\n process.exit(1);\r\n});\r\n","import fs from \"node:fs\";\r\nimport path from \"node:path\";\r\nimport type { CLIConfig } from \"../types/config.js\";\r\nimport { CONFIG_DIR, CONFIG_PATH, DEFAULT_API_BASE } from \"./constants.js\";\r\n\r\nconst DEFAULT_CONFIG: CLIConfig = {\r\n apiBase: DEFAULT_API_BASE,\r\n};\r\n\r\nexport function loadConfig(): CLIConfig {\r\n if (!fs.existsSync(CONFIG_PATH)) {\r\n return { ...DEFAULT_CONFIG };\r\n }\r\n\r\n try {\r\n const raw = fs.readFileSync(CONFIG_PATH, \"utf8\");\r\n return {\r\n ...DEFAULT_CONFIG,\r\n ...JSON.parse(raw),\r\n };\r\n } catch {\r\n return { ...DEFAULT_CONFIG };\r\n }\r\n}\r\n\r\nexport function saveConfig(config: CLIConfig): void {\r\n if (!fs.existsSync(CONFIG_DIR)) {\r\n fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });\r\n }\r\n\r\n // Atomic write: write to temp file then rename to avoid corruption on crash\r\n const tmp = CONFIG_PATH + \".tmp\";\r\n fs.writeFileSync(tmp, JSON.stringify(config, null, 2), {\r\n encoding: \"utf8\",\r\n mode: 0o600,\r\n });\r\n fs.renameSync(tmp, CONFIG_PATH);\r\n fs.chmodSync(CONFIG_DIR, 0o700);\r\n fs.chmodSync(CONFIG_PATH, 0o600);\r\n}\r\n\r\nexport function saveToken(token: string): void {\r\n const config = loadConfig();\r\n const nextConfig: CLIConfig = { ...config, pat: token };\r\n if (config.pat && config.pat !== token) {\r\n delete nextConfig.currentProject;\r\n }\r\n saveConfig(nextConfig);\r\n}\r\n\r\nexport function clearToken(): void {\r\n const config = loadConfig();\r\n delete config.pat;\r\n delete config.currentProject;\r\n saveConfig(config);\r\n}\r\n\r\nexport function getToken(): string | undefined {\r\n return loadConfig().pat;\r\n}\r\n\r\nexport function saveCurrentProject(projectId: string): void {\r\n const config = loadConfig();\r\n saveConfig({ ...config, currentProject: projectId });\r\n}\r\n\r\nexport function getCurrentProject(): string | undefined {\r\n return loadConfig().currentProject;\r\n}\r\n\r\nexport function configFilePath(): string {\r\n return CONFIG_PATH;\r\n}\r\n\r\nexport function configDirPath(): string {\r\n return path.dirname(CONFIG_PATH);\r\n}\r\n","import os from \"node:os\";\r\nimport path from \"node:path\";\r\n\r\nexport const CLI_NAME = \"ub\";\r\n\r\nexport const CONFIG_DIR = path.join(os.homedir(), \".ub\");\r\n\r\nexport const CONFIG_PATH = path.join(CONFIG_DIR, \"config.json\");\r\n\r\n/**\r\n * Default production API base URL.\r\n * Override via URBACKEND_API_URL env variable for local development.\r\n */\r\nexport const DEFAULT_API_BASE =\r\n process.env.URBACKEND_API_URL ?? \"https://api.urbackend.bitbros.in\";\r\n","export class APIError extends Error {\r\n constructor(\r\n public readonly status: number,\r\n message: string,\r\n ) {\r\n super(message);\r\n this.name = \"APIError\";\r\n }\r\n}\r\n","import { loadConfig } from \"./config.js\";\r\nimport { APIError } from \"./errors.js\";\r\n\r\nexport interface FetchOptions extends RequestInit {\r\n /** Override the PAT for this single request (e.g. during login verification) */\r\n token?: string;\r\n}\r\n\r\n/**\r\n * Central HTTP client for all CLI → dashboard-api communication.\r\n * Automatically injects the stored PAT as a Bearer token.\r\n *\r\n * All dashboard-api routes are mounted under /api — this client\r\n * prepends that prefix automatically so callers just write /user/cli/me.\r\n */\r\nexport async function apiFetch(\r\n endpoint: string,\r\n options: FetchOptions = {},\r\n): Promise {\r\n const config = loadConfig();\r\n\r\n const headers = new Headers(options.headers);\r\n\r\n if (options.body && !headers.has(\"Content-Type\")) {\r\n headers.set(\"Content-Type\", \"application/json\");\r\n }\r\n\r\n const token = options.token ?? config.pat;\r\n if (token) {\r\n headers.set(\"Authorization\", `Bearer ${token}`);\r\n }\r\n\r\n // All dashboard-api routes are under /api\r\n const base = config.apiBase.replace(/\\/+$/, \"\");\r\n const apiPath = endpoint.startsWith(\"/\") ? endpoint : `/${endpoint}`;\r\n const url = `${base}/api${apiPath}`;\r\n\r\n let response: Response;\r\n\r\n const controller = new AbortController();\r\n const signal = options.signal ?? controller.signal;\r\n const timeoutId =\r\n options.signal ? undefined : setTimeout(() => controller.abort(), 15_000);\r\n\r\n try {\r\n response = await fetch(url, {\r\n ...options,\r\n headers,\r\n signal,\r\n });\r\n } catch (error) {\r\n throw new APIError(\r\n 0,\r\n error instanceof Error && error.name === \"AbortError\"\r\n ? \"The urBackend API request timed out.\"\r\n : \"Unable to connect to the urBackend API. Is the server running?\",\r\n );\r\n } finally {\r\n if (timeoutId) clearTimeout(timeoutId);\r\n }\r\n\r\n if (!response.ok) {\r\n let message = response.statusText;\r\n\r\n try {\r\n const body = (await response.json()) as {\r\n message?: string;\r\n error?: string;\r\n };\r\n message = body.message ?? body.error ?? message;\r\n } catch {\r\n // ignore — use statusText\r\n }\r\n\r\n throw new APIError(response.status, message);\r\n }\r\n\r\n if (response.status === 204) {\r\n return undefined as T;\r\n }\r\n\r\n return (await response.json()) as T;\r\n}","import { apiFetch } from \"../core/api.js\";\r\nimport type { CLIProfile } from \"../types/auth.js\";\r\n\r\ninterface APIResponse {\r\n success: boolean;\r\n data: T;\r\n message: string;\r\n}\r\n\r\nexport async function authenticate(token: string): Promise {\r\n const res = await apiFetch>(\"/user/cli/me\", {\r\n method: \"GET\",\r\n token,\r\n });\r\n \r\n return res.data;\r\n}\r\n\r\nexport async function getProfile(): Promise {\r\n const res = await apiFetch>(\"/user/cli/me\", {\r\n method: \"GET\",\r\n });\r\n return res.data;\r\n}","/**\r\n * Validates the format of a Personal Access Token.\r\n * urBackend PATs always start with \"ubpat_\" followed by at least 10 characters.\r\n */\r\nexport function isValidPAT(token: string): boolean {\r\n return typeof token === \"string\" && /^ubpat_\\S{10,}$/.test(token);\r\n}\r\n","import { createInterface } from \"node:readline/promises\";\r\nimport { stdin, stdout } from \"node:process\";\r\n\r\n/**\r\n * Prompts the user for a single line of input.\r\n * Automatically closes the readline interface after reading.\r\n */\r\nexport async function prompt(question: string): Promise {\r\n const rl = createInterface({ input: stdin, output: stdout });\r\n const answer = (await rl.question(question)).trim();\r\n rl.close();\r\n return answer;\r\n}\r\n\r\n/**\r\n * Prompts the user for secret input (like a password/token) without echoing characters.\r\n */\r\nexport async function promptSecret(question: string): Promise {\r\n const rl = createInterface({\r\n input: stdin,\r\n output: stdout,\r\n });\r\n\r\n // Write the question first\r\n stdout.write(question);\r\n\r\n // Standard Node.js readline secret pattern using internal _writeToOutput override:\r\n const oldWrite = (rl as any)._writeToOutput;\r\n (rl as any)._writeToOutput = function _writeToOutput(stringToWrite: string) {\r\n if (stringToWrite === \"\\r\" || stringToWrite === \"\\n\" || stringToWrite === \"\\r\\n\") {\r\n oldWrite.call(rl, stringToWrite);\r\n } else if (stringToWrite === question) {\r\n oldWrite.call(rl, stringToWrite);\r\n } else {\r\n // Echo nothing or a mask (nothing is standard for non-echoing Unix style secret prompts)\r\n }\r\n };\r\n\r\n const answer = (await rl.question(\"\")).trim();\r\n (rl as any)._writeToOutput = oldWrite;\r\n rl.close();\r\n return answer;\r\n}\r\n\r\n/**\r\n * Prompts for a yes/no confirmation.\r\n * Returns true for \"y\" or \"yes\" (case-insensitive), false otherwise.\r\n */\r\nexport async function confirm(question: string): Promise {\r\n const answer = await prompt(`${question} (y/n): `);\r\n return answer.toLowerCase() === \"y\" || answer.toLowerCase() === \"yes\";\r\n}\r\n","/**\r\n * Formats bytes into a human-readable string (KB, MB, GB).\r\n */\r\nexport function formatBytes(bytes: number): string {\r\n if (bytes === 0) return \"0 B\";\r\n const units = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\"];\r\n const i = Math.floor(Math.log(bytes) / Math.log(1024));\r\n const value = (bytes / Math.pow(1024, i)).toFixed(2);\r\n return `${value} ${units[i]}`;\r\n}\r\n\r\n/**\r\n * Formats an ISO date string to a locale-friendly short format.\r\n */\r\nexport function formatDate(iso: string | null | undefined): string {\r\n if (!iso) return \"Never\";\r\n return new Date(iso).toLocaleDateString(\"en-US\", {\r\n year: \"numeric\",\r\n month: \"short\",\r\n day: \"numeric\",\r\n });\r\n}\r\n\r\n/**\r\n * Pads a string label to a fixed width for aligned terminal output.\r\n */\r\nexport function label(text: string, width = 14): string {\r\n return text.padEnd(width);\r\n}\r\n","export const logger = {\r\n success(message: string): void {\r\n console.log(`✓ ${message}`);\r\n },\r\n\r\n error(message: string): void {\r\n console.error(`✖ ${message}`);\r\n },\r\n\r\n warn(message: string): void {\r\n console.warn(`⚠ ${message}`);\r\n },\r\n\r\n info(message: string): void {\r\n console.log(message);\r\n },\r\n};\r\n","import { authenticate } from \"../../services/auth.service.js\";\r\nimport { saveToken } from \"../../core/config.js\";\r\nimport { isValidPAT } from \"../../utils/token.js\";\r\nimport { promptSecret } from \"../../utils/prompt.js\";\r\nimport { label } from \"../../utils/format.js\";\r\nimport { APIError } from \"../../core/errors.js\";\r\nimport { logger } from \"../../core/logger.js\";\r\n\r\nexport async function loginCommand(): Promise {\r\n console.log(\"Generate a Personal Access Token from the urBackend dashboard:\");\r\n console.log(\" Settings → Access Tokens → New Token\\n\");\r\n\r\n const token = await promptSecret(\"Paste your Personal Access Token: \");\r\n\r\n if (!isValidPAT(token)) {\r\n logger.error(\r\n \"Invalid token format. urBackend PATs start with 'ubpat_' followed by at least 10 characters.\",\r\n );\r\n process.exitCode = 1;\r\n return;\r\n }\r\n\r\n try {\r\n const profile = await authenticate(token);\r\n\r\n try {\r\n saveToken(token);\r\n } catch {\r\n logger.error(\"Authenticated, but failed to persist the token locally.\");\r\n process.exitCode = 1;\r\n return;\r\n }\r\n\r\n logger.success(\"Logged in successfully.\\n\");\r\n console.log(`${label(\"Email\")} ${profile.developer.email}`);\r\n console.log(`${label(\"Plan\")} ${profile.developer.plan}`);\r\n\r\n if (profile.developer.githubUsername) {\r\n console.log(`${label(\"GitHub\")} ${profile.developer.githubUsername}`);\r\n }\r\n\r\n if (profile.auth.scopes.length > 0) {\r\n console.log(`${label(\"Scopes\")} ${profile.auth.scopes.join(\", \")}`);\r\n }\r\n } catch (error) {\r\n if (error instanceof APIError) {\r\n if (error.status === 401) {\r\n logger.error(\"Invalid or expired token. Generate a new one from the dashboard.\");\r\n } else {\r\n logger.error(error.message);\r\n }\r\n process.exitCode = 1;\r\n return;\r\n }\r\n logger.error(\"Unable to connect to the urBackend API.\");\r\n process.exitCode = 1;\r\n }\r\n}\r\n","import { clearToken } from \"../../core/config.js\";\r\nimport { logger } from \"../../core/logger.js\";\r\n\r\nexport async function logoutCommand(): Promise {\r\n clearToken();\r\n logger.success(\"Logged out. Your token has been removed from this machine.\");\r\n}\r\n","import { getProfile } from \"../../services/auth.service.js\";\r\nimport { getToken, getCurrentProject } from \"../../core/config.js\";\r\nimport { label, formatDate } from \"../../utils/format.js\";\r\nimport { APIError } from \"../../core/errors.js\";\r\nimport { logger } from \"../../core/logger.js\";\r\n\r\nexport async function whoamiCommand(): Promise {\r\n const token = getToken();\r\n\r\n if (!token) {\r\n logger.error(\"You are not logged in. Run 'ub login' first.\");\r\n process.exitCode = 1;\r\n return;\r\n }\r\n\r\n try {\r\n const profile = await getProfile();\r\n\r\n console.log(`${label(\"Email\")} ${profile.developer.email}`);\r\n console.log(`${label(\"Plan\")} ${profile.developer.plan}`);\r\n\r\n if (profile.developer.githubUsername) {\r\n console.log(`${label(\"GitHub\")} ${profile.developer.githubUsername}`);\r\n }\r\n\r\n console.log(`${label(\"Token type\")} ${profile.auth.tokenType}`);\r\n\r\n if (profile.auth.scopes.length > 0) {\r\n console.log(`${label(\"Scopes\")} ${profile.auth.scopes.join(\", \")}`);\r\n }\r\n\r\n const currentProject = getCurrentProject();\r\n if (currentProject) {\r\n console.log(`${label(\"Project\")} ${currentProject}`);\r\n }\r\n } catch (error) {\r\n if (error instanceof APIError) {\r\n if (error.status === 401) {\r\n logger.error(\"Token is invalid or expired. Run 'ub login' to re-authenticate.\");\r\n } else {\r\n logger.error(error.message);\r\n }\r\n process.exitCode = 1;\r\n return;\r\n }\r\n logger.error(\"Unable to connect to the urBackend API.\");\r\n process.exitCode = 1;\r\n }\r\n}\r\n","import { apiFetch } from \"../core/api.js\";\r\nimport type { Project, ProjectListItem } from \"../types/project.js\";\r\n\r\ninterface APIResponse {\r\n success: boolean;\r\n data: T;\r\n message: string;\r\n}\r\n\r\nexport async function listProjects(): Promise {\r\n const res = await apiFetch>(\r\n \"/projects\",\r\n { method: \"GET\" },\r\n );\r\n // Backend returns raw array, not wrapped in { data: [...] }\r\n if (Array.isArray(res)) return res;\r\n return (res as APIResponse).data ?? [];\r\n}\r\n\r\nexport async function getProject(projectId: string): Promise {\r\n const res = await apiFetch>(\r\n `/projects/${projectId}`,\r\n { method: \"GET\" },\r\n );\r\n if (\"data\" in res && res.data) return (res as APIResponse).data;\r\n return res as Project;\r\n}\r\n\r\nexport async function createProject(payload: {\r\n name: string;\r\n description?: string;\r\n}): Promise {\r\n const res = await apiFetch>(\"/projects\", {\r\n method: \"POST\",\r\n body: JSON.stringify(payload),\r\n });\r\n if (\"data\" in res && res.data) return (res as APIResponse).data;\r\n return res as Project;\r\n}\r\n\r\nexport async function deleteProject(projectId: string): Promise {\r\n return apiFetch(`/projects/${projectId}`, {\r\n method: \"DELETE\",\r\n });\r\n}","import { listProjects } from \"../../services/project.service.js\";\r\nimport { getCurrentProject, getToken } from \"../../core/config.js\";\r\nimport { formatBytes } from \"../../utils/format.js\";\r\nimport { APIError } from \"../../core/errors.js\";\r\nimport { logger } from \"../../core/logger.js\";\r\n\r\nexport async function projectListCommand(): Promise {\r\n const token = getToken();\r\n if (!token) {\r\n logger.error(\"You are not logged in. Run 'ub login' first.\");\r\n return;\r\n }\r\n\r\n try {\r\n const projects = await listProjects();\r\n const currentProjectId = getCurrentProject();\r\n\r\n if (projects.length === 0) {\r\n logger.info(\"No projects found. Create one at dashboard.urbackend.bitbros.in\");\r\n return;\r\n }\r\n\r\n console.log(`\\nFound ${projects.length} project(s):\\n`);\r\n\r\n for (const project of projects) {\r\n const active = project._id === currentProjectId ? \" (active)\" : \"\";\r\n const db = formatBytes(project.databaseUsed ?? 0);\r\n const colCount = project.collections?.length ?? 0;\r\n\r\n console.log(` ${project.name}${active}`);\r\n console.log(` ID ${project._id}`);\r\n console.log(` Collections ${colCount}`);\r\n console.log(` DB used ${db}`);\r\n\r\n if (project.description) {\r\n console.log(` Description ${project.description}`);\r\n }\r\n\r\n console.log();\r\n }\r\n } catch (error) {\r\n if (error instanceof APIError) {\r\n if (error.status === 401) {\r\n logger.error(\"Token is invalid or expired. Run 'ub login' to re-authenticate.\");\r\n } else {\r\n logger.error(error.message);\r\n }\r\n return;\r\n }\r\n logger.error(\"Unable to connect to the urBackend API.\");\r\n }\r\n}\r\n","import { listProjects, getProject } from \"../../services/project.service.js\";\r\nimport { saveCurrentProject, getToken } from \"../../core/config.js\";\r\nimport { prompt } from \"../../utils/prompt.js\";\r\nimport { APIError } from \"../../core/errors.js\";\r\nimport { logger } from \"../../core/logger.js\";\r\n\r\nexport async function projectUseCommand(projectIdOrName?: string): Promise {\r\n const token = getToken();\r\n if (!token) {\r\n logger.error(\"You are not logged in. Run 'ub login' first.\");\r\n return;\r\n }\r\n\r\n try {\r\n const projects = await listProjects();\r\n\r\n if (projects.length === 0) {\r\n logger.info(\"No projects found. Create one at dashboard.urbackend.bitbros.in\");\r\n return;\r\n }\r\n\r\n let selectedId: string | undefined;\r\n\r\n if (projectIdOrName) {\r\n // Try matching by ID or name\r\n const match = projects.find(\r\n (p) =>\r\n p._id === projectIdOrName ||\r\n p.name.toLowerCase() === projectIdOrName.toLowerCase(),\r\n );\r\n if (!match) {\r\n logger.error(`No project found matching \"${projectIdOrName}\".`);\r\n logger.info(\"Run 'ub project list' to see available projects.\");\r\n return;\r\n }\r\n selectedId = match._id;\r\n } else {\r\n // Interactive selection\r\n console.log(\"\\nAvailable projects:\\n\");\r\n projects.forEach((p, i) => {\r\n console.log(` [${i + 1}] ${p.name} (${p._id})`);\r\n });\r\n console.log();\r\n\r\n const answer = await prompt(\"Enter project number: \");\r\n if (!/^\\d+$/.test(answer)) {\r\n logger.error(\"Invalid selection.\");\r\n return;\r\n }\r\n const index = Number(answer) - 1;\r\n if (isNaN(index) || index < 0 || index >= projects.length) {\r\n logger.error(\"Invalid selection.\");\r\n return;\r\n }\r\n\r\n selectedId = projects[index]._id;\r\n }\r\n\r\n saveCurrentProject(selectedId);\r\n\r\n const project = projects.find((p) => p._id === selectedId)!;\r\n logger.success(`Switched to project: ${project.name} (${selectedId})`);\r\n } catch (error) {\r\n if (error instanceof APIError) {\r\n if (error.status === 401) {\r\n logger.error(\"Token is invalid or expired. Run 'ub login' to re-authenticate.\");\r\n } else {\r\n logger.error(error.message);\r\n }\r\n return;\r\n }\r\n logger.error(\"Unable to connect to the urBackend API.\");\r\n }\r\n}\r\n","import { getProject } from \"../../services/project.service.js\";\r\nimport { getCurrentProject, getToken } from \"../../core/config.js\";\r\nimport { formatBytes, label } from \"../../utils/format.js\";\r\nimport { APIError } from \"../../core/errors.js\";\r\nimport { logger } from \"../../core/logger.js\";\r\n\r\nexport async function projectInfoCommand(projectId?: string): Promise {\r\n const token = getToken();\r\n if (!token) {\r\n logger.error(\"You are not logged in. Run 'ub login' first.\");\r\n return;\r\n }\r\n\r\n const id = projectId ?? getCurrentProject();\r\n\r\n if (!id) {\r\n logger.error(\"No active project. Run 'ub project use' to select one.\");\r\n return;\r\n }\r\n\r\n try {\r\n const project = await getProject(id);\r\n\r\n console.log(`\\n${project.name}\\n`);\r\n console.log(`${label(\"ID\")} ${project._id}`);\r\n\r\n if (project.description) {\r\n console.log(`${label(\"Description\")} ${project.description}`);\r\n }\r\n\r\n console.log(`${label(\"Auth\")} ${project.isAuthEnabled ? \"Enabled\" : \"Disabled\"}`);\r\n console.log(`${label(\"DB used\")} ${formatBytes(project.databaseUsed ?? 0)}`);\r\n console.log(`${label(\"Storage\")} ${formatBytes(project.storageUsed ?? 0)}`);\r\n console.log(`${label(\"Collections\")} ${project.collections?.length ?? 0}`);\r\n\r\n if (project.collections && project.collections.length > 0) {\r\n console.log(\"\\nCollections:\");\r\n for (const col of project.collections) {\r\n const fieldCount = col.model?.length ?? 0;\r\n console.log(` • ${col.name} (${fieldCount} field${fieldCount !== 1 ? \"s\" : \"\"})`);\r\n }\r\n }\r\n\r\n console.log();\r\n } catch (error) {\r\n if (error instanceof APIError) {\r\n if (error.status === 401) {\r\n logger.error(\"Token is invalid or expired. Run 'ub login' to re-authenticate.\");\r\n } else if (error.status === 404) {\r\n logger.error(\"Project not found.\");\r\n } else {\r\n logger.error(error.message);\r\n }\r\n return;\r\n }\r\n logger.error(\"Unable to connect to the urBackend API.\");\r\n }\r\n}\r\n","import { getProject } from \"../../services/project.service.js\";\r\nimport { getToken, getCurrentProject } from \"../../core/config.js\";\r\nimport { label } from \"../../utils/format.js\";\r\nimport { APIError } from \"../../core/errors.js\";\r\nimport { logger } from \"../../core/logger.js\";\r\n\r\nexport async function collectionListCommand(projectId?: string): Promise {\r\n const token = getToken();\r\n if (!token) {\r\n logger.error(\"You are not logged in. Run 'ub login' first.\");\r\n return;\r\n }\r\n\r\n const id = projectId ?? getCurrentProject();\r\n if (!id) {\r\n logger.error(\"No active project. Run 'ub project use' to select one.\");\r\n return;\r\n }\r\n\r\n try {\r\n const project = await getProject(id);\r\n const collections = project.collections ?? [];\r\n\r\n if (collections.length === 0) {\r\n logger.info(`No collections in project \"${project.name}\".`);\r\n return;\r\n }\r\n\r\n console.log(`\\nCollections in \"${project.name}\" (${collections.length}):\\n`);\r\n\r\n for (const col of collections) {\r\n const fieldCount = col.model?.length ?? 0;\r\n const rlsMode = col.rls?.enabled ? col.rls.mode : \"disabled\";\r\n\r\n console.log(` ${col.name}`);\r\n console.log(` ${label(\"Fields\", 10)} ${fieldCount}`);\r\n console.log(` ${label(\"RLS\", 10)} ${rlsMode}`);\r\n\r\n if (col.model && col.model.length > 0) {\r\n const fieldSummary = col.model\r\n .slice(0, 5)\r\n .map((f) => `${f.key}: ${f.type}${f.required ? \"*\" : \"\"}`)\r\n .join(\", \");\r\n const extra = col.model.length > 5 ? ` +${col.model.length - 5} more` : \"\";\r\n console.log(` ${label(\"Schema\", 10)} ${fieldSummary}${extra}`);\r\n }\r\n\r\n console.log();\r\n }\r\n\r\n console.log(\" * = required field\");\r\n console.log();\r\n } catch (error) {\r\n if (error instanceof APIError) {\r\n if (error.status === 401) {\r\n logger.error(\"Token is invalid or expired. Run 'ub login' to re-authenticate.\");\r\n } else if (error.status === 404) {\r\n logger.error(\"Project not found or access denied.\");\r\n } else {\r\n logger.error(error.message);\r\n }\r\n return;\r\n }\r\n logger.error(\"Unable to connect to the urBackend API.\");\r\n }\r\n}\r\n","import { apiFetch } from \"../core/api.js\";\r\nimport type { Collection } from \"../types/project.js\";\r\n\r\ninterface CollectionResponse {\r\n data: Collection;\r\n message: string;\r\n}\r\n\r\n/**\r\n * Creates a new collection inside a project.\r\n * Endpoint: POST /projects/:projectId/collections\r\n */\r\nexport async function createCollection(\r\n projectId: string,\r\n payload: { name: string; model: Collection[\"model\"] },\r\n): Promise {\r\n const res = await apiFetch(\r\n `/projects/${projectId}/collections`,\r\n {\r\n method: \"POST\",\r\n body: JSON.stringify(payload),\r\n },\r\n );\r\n return res.data;\r\n}\r\n\r\n/**\r\n * Deletes a collection from a project.\r\n * Endpoint: DELETE /projects/:projectId/collections/:collectionName\r\n */\r\nexport async function deleteCollection(\r\n projectId: string,\r\n collectionName: string,\r\n): Promise {\r\n const encodedName = encodeURIComponent(collectionName);\r\n return apiFetch(\r\n `/projects/${projectId}/collections/${encodedName}`,\r\n { method: \"DELETE\" },\r\n );\r\n}\r\n","import { deleteCollection } from \"../../services/collection.service.js\";\r\nimport { getProject } from \"../../services/project.service.js\";\r\nimport { getToken, getCurrentProject } from \"../../core/config.js\";\r\nimport { confirm } from \"../../utils/prompt.js\";\r\nimport { APIError } from \"../../core/errors.js\";\r\nimport { logger } from \"../../core/logger.js\";\r\n\r\nexport interface CollectionDeleteOptions {\r\n force?: boolean;\r\n project?: string;\r\n}\r\n\r\nexport async function collectionDeleteCommand(\r\n collectionName: string,\r\n options: CollectionDeleteOptions,\r\n): Promise {\r\n const token = getToken();\r\n if (!token) {\r\n logger.error(\"You are not logged in. Run 'ub login' first.\");\r\n return;\r\n }\r\n\r\n const projectId = options.project ?? getCurrentProject();\r\n if (!projectId) {\r\n logger.error(\"No active project. Run 'ub project use' to select one.\");\r\n return;\r\n }\r\n\r\n try {\r\n // Verify the collection actually exists before asking for confirmation\r\n const project = await getProject(projectId);\r\n const exists = project.collections?.some((c) => c.name === collectionName);\r\n\r\n if (!exists) {\r\n logger.error(\r\n `Collection \"${collectionName}\" not found in project \"${project.name}\".`,\r\n );\r\n return;\r\n }\r\n\r\n if (!options.force) {\r\n console.log(\r\n `\\n⚠ This will permanently delete the collection \"${collectionName}\" and ALL its data.\\n`,\r\n );\r\n const ok = await confirm(\"Are you sure?\");\r\n if (!ok) {\r\n logger.info(\"Aborted.\");\r\n return;\r\n }\r\n }\r\n\r\n await deleteCollection(projectId, collectionName);\r\n logger.success(`Collection \"${collectionName}\" deleted.`);\r\n } catch (error) {\r\n if (error instanceof APIError) {\r\n if (error.status === 401) {\r\n logger.error(\"Token is invalid or expired. Run 'ub login' to re-authenticate.\");\r\n } else if (error.status === 404) {\r\n logger.error(\"Collection or project not found.\");\r\n } else {\r\n logger.error(error.message);\r\n }\r\n return;\r\n }\r\n logger.error(\"Unable to connect to the urBackend API.\");\r\n }\r\n}\r\n","import { apiFetch } from \"../core/api.js\";\r\n\r\ninterface APIResponse {\r\n success: boolean;\r\n data: T;\r\n message: string;\r\n}\r\n\r\nexport interface GlobalStats {\r\n plan: string;\r\n planExpiresAt: string | null;\r\n limits: {\r\n maxProjects: number;\r\n maxCollections: number;\r\n reqPerDay: number;\r\n storageBytes: number;\r\n mongoBytes: number;\r\n authUsersLimit: number;\r\n };\r\n usage: {\r\n totalProjects: number;\r\n totalCollections: number;\r\n totalStorageUsed: number;\r\n totalDatabaseUsed: number;\r\n totalRequests: number;\r\n totalWebhooks: number;\r\n totalUsers: number;\r\n };\r\n}\r\n\r\nexport interface RecentActivityLog {\r\n id: string;\r\n projectName: string;\r\n projectId: string;\r\n method: string;\r\n path: string;\r\n status: number;\r\n timestamp: string;\r\n}\r\n\r\nexport async function getGlobalStats(): Promise {\r\n const res = await apiFetch>(\"/analytics/stats\", {\r\n method: \"GET\",\r\n });\r\n return res.data;\r\n}\r\n\r\nexport async function getRecentActivity(): Promise {\r\n const res = await apiFetch>(\r\n \"/analytics/activity\",\r\n { method: \"GET\" },\r\n );\r\n if (!Array.isArray(res.data)) {\r\n throw new Error(\"Invalid response from /analytics/activity\");\r\n }\r\n return res.data;\r\n}","import { getGlobalStats, getRecentActivity } from \"../../services/analytics.service.js\";\r\nimport { getProject } from \"../../services/project.service.js\";\r\nimport { getToken, getCurrentProject } from \"../../core/config.js\";\r\nimport { formatBytes, label } from \"../../utils/format.js\";\r\nimport { APIError } from \"../../core/errors.js\";\r\nimport { logger } from \"../../core/logger.js\";\r\n\r\nfunction statusIcon(status: number): string {\r\n if (status < 300) return \"✓\";\r\n if (status < 400) return \"→\";\r\n if (status < 500) return \"⚠\";\r\n return \"✖\";\r\n}\r\n\r\nexport async function statusCommand(): Promise {\r\n const token = getToken();\r\n if (!token) {\r\n logger.error(\"You are not logged in. Run 'ub login' first.\");\r\n return;\r\n }\r\n\r\n const currentProjectId = getCurrentProject();\r\n\r\n try {\r\n const stats = await getGlobalStats();\r\n\r\n console.log(\"\\n── Account ─────────────────────────────────────\");\r\n console.log(`${label(\"Plan\")} ${stats.plan.toUpperCase()}`);\r\n\r\n if (stats.planExpiresAt) {\r\n console.log(`${label(\"Plan expires\")} ${stats.planExpiresAt}`);\r\n }\r\n\r\n console.log(\"\\n── Usage ────────────────────────────────────────\");\r\n console.log(\r\n `${label(\"Projects\")} ${stats.usage.totalProjects} / ${stats.limits.maxProjects}`,\r\n );\r\n console.log(\r\n `${label(\"Collections\")} ${stats.usage.totalCollections} / ${stats.limits.maxCollections}`,\r\n );\r\n console.log(\r\n `${label(\"Database\")} ${formatBytes(stats.usage.totalDatabaseUsed)} / ${formatBytes(stats.limits.mongoBytes)}`,\r\n );\r\n console.log(\r\n `${label(\"Storage\")} ${formatBytes(stats.usage.totalStorageUsed)} / ${formatBytes(stats.limits.storageBytes)}`,\r\n );\r\n console.log(\r\n `${label(\"API requests\")} ${stats.usage.totalRequests.toLocaleString()} / ${stats.limits.reqPerDay.toLocaleString()} today`,\r\n );\r\n console.log(`${label(\"Auth users\")} ${stats.usage.totalUsers} / ${stats.limits.authUsersLimit}`);\r\n console.log(`${label(\"Webhooks\")} ${stats.usage.totalWebhooks}`);\r\n\r\n if (currentProjectId) {\r\n try {\r\n const project = await getProject(currentProjectId);\r\n console.log(\"\\n── Active project ───────────────────────────────\");\r\n console.log(`${label(\"Name\")} ${project.name}`);\r\n console.log(`${label(\"ID\")} ${project._id}`);\r\n console.log(`${label(\"Collections\")} ${project.collections?.length ?? 0}`);\r\n console.log(`${label(\"Auth\")} ${project.isAuthEnabled ? \"Enabled\" : \"Disabled\"}`);\r\n } catch {\r\n // non-fatal\r\n }\r\n } else {\r\n console.log(\"\\nTip: Run 'ub project use' to select a project.\");\r\n }\r\n\r\n try {\r\n const activity = await getRecentActivity();\r\n if (activity.length > 0) {\r\n console.log(\"\\n── Recent activity (last 10) ────────────────────\");\r\n for (const log of activity.slice(0, 10)) {\r\n const icon = statusIcon(log.status);\r\n const time = new Date(log.timestamp).toLocaleTimeString();\r\n console.log(\r\n ` ${icon} [${log.status}] ${log.method.padEnd(6)} ${log.path.padEnd(32)} ${time}`,\r\n );\r\n }\r\n }\r\n } catch {\r\n // non-fatal\r\n }\r\n\r\n console.log();\r\n } catch (error) {\r\n if (error instanceof APIError) {\r\n if (error.status === 401) {\r\n logger.error(\"Token is invalid or expired. Run 'ub login' to re-authenticate.\");\r\n } else {\r\n logger.error(error.message);\r\n }\r\n return;\r\n }\r\n logger.error(\"Unable to connect to the urBackend API.\");\r\n }\r\n}","import { getProfile } from \"../../services/auth.service.js\";\r\nimport { getProject } from \"../../services/project.service.js\";\r\nimport {\r\n getToken,\r\n getCurrentProject,\r\n loadConfig,\r\n configFilePath,\r\n} from \"../../core/config.js\";\r\nimport { APIError } from \"../../core/errors.js\";\r\nimport { logger } from \"../../core/logger.js\";\r\n\r\ninterface DoctorResult {\r\n configFound: boolean;\r\n patValid: boolean;\r\n projectSelected: boolean;\r\n projectAccessible: boolean;\r\n dashboardApiReachable: boolean;\r\n dashboardApiLatencyMs: number | null;\r\n email: string | null;\r\n plan: string | null;\r\n projectName: string | null;\r\n}\r\n\r\nexport interface DoctorOptions {\r\n json?: boolean;\r\n}\r\n\r\nasync function checkApiReachable(apiBase: string): Promise {\r\n const start = Date.now();\r\n try {\r\n const response = await fetch(`${apiBase}/health`, {\r\n signal: AbortSignal.timeout(5000),\r\n });\r\n if (!response.ok) {\r\n return null;\r\n }\r\n return Date.now() - start;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\nexport async function doctorCommand(options: DoctorOptions = {}): Promise {\r\n const config = loadConfig();\r\n const token = getToken();\r\n const currentProjectId = getCurrentProject();\r\n\r\n const result: DoctorResult = {\r\n configFound: false,\r\n patValid: false,\r\n projectSelected: !!currentProjectId,\r\n projectAccessible: false,\r\n dashboardApiReachable: false,\r\n dashboardApiLatencyMs: null,\r\n email: null,\r\n plan: null,\r\n projectName: null,\r\n };\r\n\r\n // 1. Config file\r\n try {\r\n const fs = await import(\"node:fs\");\r\n result.configFound = fs.existsSync(configFilePath());\r\n } catch {\r\n result.configFound = false;\r\n }\r\n\r\n // 2. API reachability (independent of auth)\r\n const latency = await checkApiReachable(config.apiBase);\r\n result.dashboardApiReachable = latency !== null;\r\n result.dashboardApiLatencyMs = latency;\r\n\r\n // 3. PAT validity\r\n if (token) {\r\n try {\r\n const profile = await getProfile();\r\n result.patValid = true;\r\n result.email = profile.developer.email;\r\n result.plan = profile.developer.plan;\r\n } catch {\r\n result.patValid = false;\r\n }\r\n }\r\n\r\n // 4. Project accessibility\r\n if (currentProjectId && result.patValid) {\r\n try {\r\n const project = await getProject(currentProjectId);\r\n result.projectAccessible = true;\r\n result.projectName = project.name;\r\n } catch {\r\n result.projectAccessible = false;\r\n }\r\n }\r\n\r\n // Output\r\n if (options.json) {\r\n console.log(JSON.stringify(result, null, 2));\r\n return;\r\n }\r\n\r\n console.log(\"\\n── urBackend CLI Doctor ─────────────────────────\\n\");\r\n\r\n const ok = \"✓\";\r\n const fail = \"✖\";\r\n const warn = \"⚠\";\r\n\r\n console.log(\r\n ` ${result.configFound ? ok : fail} Config file ${result.configFound ? configFilePath() : \"Not found — run 'ub login'\"}`,\r\n );\r\n\r\n console.log(\r\n ` ${result.dashboardApiReachable ? ok : fail} Dashboard API ${\r\n result.dashboardApiReachable\r\n ? `Reachable (${result.dashboardApiLatencyMs}ms)`\r\n : `Unreachable — ${config.apiBase}`\r\n }`,\r\n );\r\n\r\n if (!token) {\r\n console.log(` ${fail} PAT Not set — run 'ub login'`);\r\n } else {\r\n console.log(\r\n ` ${result.patValid ? ok : fail} PAT ${\r\n result.patValid\r\n ? `Valid (${result.email}, ${result.plan})`\r\n : \"Invalid or expired — run 'ub login'\"\r\n }`,\r\n );\r\n }\r\n\r\n if (!currentProjectId) {\r\n console.log(` ${warn} Active project Not selected — run 'ub project use'`);\r\n } else {\r\n console.log(\r\n ` ${result.projectAccessible ? ok : fail} Active project ${\r\n result.projectAccessible\r\n ? `${result.projectName} (${currentProjectId})`\r\n : `ID ${currentProjectId} — not found or access denied`\r\n }`,\r\n );\r\n }\r\n\r\n console.log();\r\n\r\n // Summary\r\n const allGood =\r\n result.configFound &&\r\n result.patValid &&\r\n result.dashboardApiReachable &&\r\n result.projectSelected &&\r\n result.projectAccessible;\r\n\r\n if (allGood) {\r\n logger.success(\"Everything looks good.\");\r\n } else {\r\n logger.warn(\"Some checks failed. Review the output above.\");\r\n }\r\n\r\n console.log();\r\n}\r\n"],"mappings":"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","names":["path","fs"]} \ No newline at end of file diff --git a/sdks/urbackend-cli/package-lock.json b/sdks/urbackend-cli/package-lock.json new file mode 100644 index 000000000..0e5296dc3 --- /dev/null +++ b/sdks/urbackend-cli/package-lock.json @@ -0,0 +1,1509 @@ +{ + "name": "@urbackend/cli", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@urbackend/cli", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "commander": "^12.1.0" + }, + "bin": { + "ub": "bin/ub.js" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "tsup": "^8.5.1", + "typescript": "^5.7.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.20.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.0.tgz", + "integrity": "sha512-QWlFW2wf3nTjC13/DqRnBpR4ZO36VJH/JVBkA/vcnmbTBNQIlnObqyqZE1tUR7+Ni23Lda8R1BxMfbXRpCUx5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/bundle-require": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz", + "integrity": "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "load-tsconfig": "^0.2.3" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "peerDependencies": { + "esbuild": ">=0.18" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fix-dts-default-cjs-exports": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz", + "integrity": "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "rollup": "^4.34.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/load-tsconfig": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", + "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/rollup": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/tsup": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.5.1.tgz", + "integrity": "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-require": "^5.1.0", + "cac": "^6.7.14", + "chokidar": "^4.0.3", + "consola": "^3.4.0", + "debug": "^4.4.0", + "esbuild": "^0.27.0", + "fix-dts-default-cjs-exports": "^1.0.0", + "joycon": "^3.1.1", + "picocolors": "^1.1.1", + "postcss-load-config": "^6.0.1", + "resolve-from": "^5.0.0", + "rollup": "^4.34.8", + "source-map": "^0.7.6", + "sucrase": "^3.35.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.11", + "tree-kill": "^1.2.2" + }, + "bin": { + "tsup": "dist/cli-default.js", + "tsup-node": "dist/cli-node.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@microsoft/api-extractor": "^7.36.0", + "@swc/core": "^1", + "postcss": "^8.4.12", + "typescript": ">=4.5.0" + }, + "peerDependenciesMeta": { + "@microsoft/api-extractor": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "postcss": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/sdks/urbackend-cli/package.json b/sdks/urbackend-cli/package.json new file mode 100644 index 000000000..1f4fed3ee --- /dev/null +++ b/sdks/urbackend-cli/package.json @@ -0,0 +1,41 @@ +{ + "name": "@urbackend/cli", + "version": "0.1.0", + "description": "Official CLI for urBackend — manage projects, schemas, and more from your terminal", + "publishConfig": { + "access": "public" + }, + "type": "module", + "bin": { + "ub": "./bin/ub.js" + }, + "files": [ + "bin", + "dist", + "README.md" + ], + "scripts": { + "build": "tsup --config tsup.config.ts", + "dev": "tsup --config tsup.config.ts --watch", + "lint": "eslint src", + "format": "prettier --write \"src/**/*.ts\"" + }, + "dependencies": { + "commander": "^12.1.0" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "tsup": "^8.5.1", + "typescript": "^5.7.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "keywords": [ + "urbackend", + "cli", + "baas" + ], + "author": "urBackend Contributors", + "license": "MIT" +} diff --git a/sdks/urbackend-cli/src/commands/auth/login.ts b/sdks/urbackend-cli/src/commands/auth/login.ts new file mode 100644 index 000000000..0427fd330 --- /dev/null +++ b/sdks/urbackend-cli/src/commands/auth/login.ts @@ -0,0 +1,58 @@ +import { authenticate } from "../../services/auth.service.js"; +import { saveToken } from "../../core/config.js"; +import { isValidPAT } from "../../utils/token.js"; +import { promptSecret } from "../../utils/prompt.js"; +import { label } from "../../utils/format.js"; +import { APIError } from "../../core/errors.js"; +import { logger } from "../../core/logger.js"; + +export async function loginCommand(): Promise { + console.log("Generate a Personal Access Token from the urBackend dashboard:"); + console.log(" Settings → Access Tokens → New Token\n"); + + const token = await promptSecret("Paste your Personal Access Token: "); + + if (!isValidPAT(token)) { + logger.error( + "Invalid token format. urBackend PATs start with 'ubpat_' followed by at least 10 characters.", + ); + process.exitCode = 1; + return; + } + + try { + const profile = await authenticate(token); + + try { + saveToken(token); + } catch { + logger.error("Authenticated, but failed to persist the token locally."); + process.exitCode = 1; + return; + } + + logger.success("Logged in successfully.\n"); + console.log(`${label("Email")} ${profile.developer.email}`); + console.log(`${label("Plan")} ${profile.developer.plan}`); + + if (profile.developer.githubUsername) { + console.log(`${label("GitHub")} ${profile.developer.githubUsername}`); + } + + if (profile.auth.scopes.length > 0) { + console.log(`${label("Scopes")} ${profile.auth.scopes.join(", ")}`); + } + } catch (error) { + if (error instanceof APIError) { + if (error.status === 401) { + logger.error("Invalid or expired token. Generate a new one from the dashboard."); + } else { + logger.error(error.message); + } + process.exitCode = 1; + return; + } + logger.error("Unable to connect to the urBackend API."); + process.exitCode = 1; + } +} diff --git a/sdks/urbackend-cli/src/commands/auth/logout.ts b/sdks/urbackend-cli/src/commands/auth/logout.ts new file mode 100644 index 000000000..3c0a0bd49 --- /dev/null +++ b/sdks/urbackend-cli/src/commands/auth/logout.ts @@ -0,0 +1,7 @@ +import { clearToken } from "../../core/config.js"; +import { logger } from "../../core/logger.js"; + +export async function logoutCommand(): Promise { + clearToken(); + logger.success("Logged out. Your token has been removed from this machine."); +} diff --git a/sdks/urbackend-cli/src/commands/auth/whoami.ts b/sdks/urbackend-cli/src/commands/auth/whoami.ts new file mode 100644 index 000000000..ff025d9a1 --- /dev/null +++ b/sdks/urbackend-cli/src/commands/auth/whoami.ts @@ -0,0 +1,49 @@ +import { getProfile } from "../../services/auth.service.js"; +import { getToken, getCurrentProject } from "../../core/config.js"; +import { label, formatDate } from "../../utils/format.js"; +import { APIError } from "../../core/errors.js"; +import { logger } from "../../core/logger.js"; + +export async function whoamiCommand(): Promise { + const token = getToken(); + + if (!token) { + logger.error("You are not logged in. Run 'ub login' first."); + process.exitCode = 1; + return; + } + + try { + const profile = await getProfile(); + + console.log(`${label("Email")} ${profile.developer.email}`); + console.log(`${label("Plan")} ${profile.developer.plan}`); + + if (profile.developer.githubUsername) { + console.log(`${label("GitHub")} ${profile.developer.githubUsername}`); + } + + console.log(`${label("Token type")} ${profile.auth.tokenType}`); + + if (profile.auth.scopes.length > 0) { + console.log(`${label("Scopes")} ${profile.auth.scopes.join(", ")}`); + } + + const currentProject = getCurrentProject(); + if (currentProject) { + console.log(`${label("Project")} ${currentProject}`); + } + } catch (error) { + if (error instanceof APIError) { + if (error.status === 401) { + logger.error("Token is invalid or expired. Run 'ub login' to re-authenticate."); + } else { + logger.error(error.message); + } + process.exitCode = 1; + return; + } + logger.error("Unable to connect to the urBackend API."); + process.exitCode = 1; + } +} diff --git a/sdks/urbackend-cli/src/commands/collection/delete.ts b/sdks/urbackend-cli/src/commands/collection/delete.ts new file mode 100644 index 000000000..4fe82499e --- /dev/null +++ b/sdks/urbackend-cli/src/commands/collection/delete.ts @@ -0,0 +1,67 @@ +import { deleteCollection } from "../../services/collection.service.js"; +import { getProject } from "../../services/project.service.js"; +import { getToken, getCurrentProject } from "../../core/config.js"; +import { confirm } from "../../utils/prompt.js"; +import { APIError } from "../../core/errors.js"; +import { logger } from "../../core/logger.js"; + +export interface CollectionDeleteOptions { + force?: boolean; + project?: string; +} + +export async function collectionDeleteCommand( + collectionName: string, + options: CollectionDeleteOptions, +): Promise { + const token = getToken(); + if (!token) { + logger.error("You are not logged in. Run 'ub login' first."); + return; + } + + const projectId = options.project ?? getCurrentProject(); + if (!projectId) { + logger.error("No active project. Run 'ub project use' to select one."); + return; + } + + try { + // Verify the collection actually exists before asking for confirmation + const project = await getProject(projectId); + const exists = project.collections?.some((c) => c.name === collectionName); + + if (!exists) { + logger.error( + `Collection "${collectionName}" not found in project "${project.name}".`, + ); + return; + } + + if (!options.force) { + console.log( + `\n⚠ This will permanently delete the collection "${collectionName}" and ALL its data.\n`, + ); + const ok = await confirm("Are you sure?"); + if (!ok) { + logger.info("Aborted."); + return; + } + } + + await deleteCollection(projectId, collectionName); + logger.success(`Collection "${collectionName}" deleted.`); + } catch (error) { + if (error instanceof APIError) { + if (error.status === 401) { + logger.error("Token is invalid or expired. Run 'ub login' to re-authenticate."); + } else if (error.status === 404) { + logger.error("Collection or project not found."); + } else { + logger.error(error.message); + } + return; + } + logger.error("Unable to connect to the urBackend API."); + } +} diff --git a/sdks/urbackend-cli/src/commands/collection/list.ts b/sdks/urbackend-cli/src/commands/collection/list.ts new file mode 100644 index 000000000..79b498c95 --- /dev/null +++ b/sdks/urbackend-cli/src/commands/collection/list.ts @@ -0,0 +1,66 @@ +import { getProject } from "../../services/project.service.js"; +import { getToken, getCurrentProject } from "../../core/config.js"; +import { label } from "../../utils/format.js"; +import { APIError } from "../../core/errors.js"; +import { logger } from "../../core/logger.js"; + +export async function collectionListCommand(projectId?: string): Promise { + const token = getToken(); + if (!token) { + logger.error("You are not logged in. Run 'ub login' first."); + return; + } + + const id = projectId ?? getCurrentProject(); + if (!id) { + logger.error("No active project. Run 'ub project use' to select one."); + return; + } + + try { + const project = await getProject(id); + const collections = project.collections ?? []; + + if (collections.length === 0) { + logger.info(`No collections in project "${project.name}".`); + return; + } + + console.log(`\nCollections in "${project.name}" (${collections.length}):\n`); + + for (const col of collections) { + const fieldCount = col.model?.length ?? 0; + const rlsMode = col.rls?.enabled ? col.rls.mode : "disabled"; + + console.log(` ${col.name}`); + console.log(` ${label("Fields", 10)} ${fieldCount}`); + console.log(` ${label("RLS", 10)} ${rlsMode}`); + + if (col.model && col.model.length > 0) { + const fieldSummary = col.model + .slice(0, 5) + .map((f) => `${f.key}: ${f.type}${f.required ? "*" : ""}`) + .join(", "); + const extra = col.model.length > 5 ? ` +${col.model.length - 5} more` : ""; + console.log(` ${label("Schema", 10)} ${fieldSummary}${extra}`); + } + + console.log(); + } + + console.log(" * = required field"); + console.log(); + } catch (error) { + if (error instanceof APIError) { + if (error.status === 401) { + logger.error("Token is invalid or expired. Run 'ub login' to re-authenticate."); + } else if (error.status === 404) { + logger.error("Project not found or access denied."); + } else { + logger.error(error.message); + } + return; + } + logger.error("Unable to connect to the urBackend API."); + } +} diff --git a/sdks/urbackend-cli/src/commands/doctor/index.ts b/sdks/urbackend-cli/src/commands/doctor/index.ts new file mode 100644 index 000000000..94b2be7c7 --- /dev/null +++ b/sdks/urbackend-cli/src/commands/doctor/index.ts @@ -0,0 +1,177 @@ +import { getProfile } from "../../services/auth.service.js"; +import { getProject } from "../../services/project.service.js"; +import { + getToken, + getCurrentProject, + loadConfig, + configFilePath, +} from "../../core/config.js"; +import { APIError } from "../../core/errors.js"; +import { logger } from "../../core/logger.js"; + +interface DoctorResult { + configFound: boolean; + patValid: boolean | null; + projectSelected: boolean; + projectAccessible: boolean | null; + dashboardApiReachable: boolean; + dashboardApiLatencyMs: number | null; + email: string | null; + plan: string | null; + projectName: string | null; +} + +export interface DoctorOptions { + json?: boolean; +} + +async function checkApiReachable(apiBase: string): Promise { + const start = Date.now(); + try { + const response = await fetch(`${apiBase}/health`, { + signal: AbortSignal.timeout(5000), + }); + if (!response.ok) { + return null; + } + return Date.now() - start; + } catch { + return null; + } +} + +export async function doctorCommand(options: DoctorOptions = {}): Promise { + const config = loadConfig(); + const token = getToken(); + const currentProjectId = getCurrentProject(); + + const result: DoctorResult = { + configFound: false, + patValid: false, + projectSelected: !!currentProjectId, + projectAccessible: false, + dashboardApiReachable: false, + dashboardApiLatencyMs: null, + email: null, + plan: null, + projectName: null, + }; + + // 1. Config file + try { + const fs = await import("node:fs"); + result.configFound = fs.existsSync(configFilePath()); + } catch { + result.configFound = false; + } + + // 2. API reachability (independent of auth) + const latency = await checkApiReachable(config.apiBase); + result.dashboardApiReachable = latency !== null; + result.dashboardApiLatencyMs = latency; + + // 3. PAT validity + if (token) { + try { + const profile = await getProfile(); + result.patValid = true; + result.email = profile.developer.email; + result.plan = profile.developer.plan; + } catch (error) { + if (error instanceof APIError && error.status === 401) { + result.patValid = false; + } else { + result.patValid = null; + } + } + } + + // 4. Project accessibility + if (currentProjectId && result.patValid) { + try { + const project = await getProject(currentProjectId); + result.projectAccessible = true; + result.projectName = project.name; + } catch (error) { + if (error instanceof APIError && (error.status === 401 || error.status === 403)) { + result.projectAccessible = false; + } else { + result.projectAccessible = null; + } + } + } + + // Output + if (options.json) { + console.log(JSON.stringify(result, null, 2)); + return; + } + + console.log("\n── urBackend CLI Doctor ─────────────────────────\n"); + + const ok = "✓"; + const fail = "✖"; + const warn = "⚠"; + + console.log( + ` ${result.configFound ? ok : fail} Config file ${result.configFound ? configFilePath() : "Not found — run 'ub login'"}`, + ); + + console.log( + ` ${result.dashboardApiReachable ? ok : fail} Dashboard API ${ + result.dashboardApiReachable + ? `Reachable (${result.dashboardApiLatencyMs}ms)` + : `Unreachable — ${config.apiBase}` + }`, + ); + + if (!token) { + console.log(` ${fail} PAT Not set — run 'ub login'`); + } else if (result.patValid === null) { + console.log( + ` ${warn} PAT Unable to validate — API unreachable or backend error`, + ); + } else { + console.log( + ` ${result.patValid ? ok : fail} PAT ${ + result.patValid + ? `Valid (${result.email}, ${result.plan})` + : "Invalid or expired — run 'ub login'" + }`, + ); + } + + if (!currentProjectId) { + console.log(` ${warn} Active project Not selected — run 'ub project use'`); + } else if (result.projectAccessible === null) { + console.log( + ` ${warn} Active project Unable to validate — API unreachable or backend error`, + ); + } else { + console.log( + ` ${result.projectAccessible ? ok : fail} Active project ${ + result.projectAccessible + ? `${result.projectName} (${currentProjectId})` + : `ID ${currentProjectId} — not found or access denied` + }`, + ); + } + + console.log(); + + // Summary + const allGood = + result.configFound && + result.patValid === true && + result.dashboardApiReachable && + result.projectSelected && + result.projectAccessible === true; + + if (allGood) { + logger.success("Everything looks good."); + } else { + logger.warn("Some checks failed. Review the output above."); + } + + console.log(); +} diff --git a/sdks/urbackend-cli/src/commands/project/info.ts b/sdks/urbackend-cli/src/commands/project/info.ts new file mode 100644 index 000000000..ef5ccf342 --- /dev/null +++ b/sdks/urbackend-cli/src/commands/project/info.ts @@ -0,0 +1,58 @@ +import { getProject } from "../../services/project.service.js"; +import { getCurrentProject, getToken } from "../../core/config.js"; +import { formatBytes, label } from "../../utils/format.js"; +import { APIError } from "../../core/errors.js"; +import { logger } from "../../core/logger.js"; + +export async function projectInfoCommand(projectId?: string): Promise { + const token = getToken(); + if (!token) { + logger.error("You are not logged in. Run 'ub login' first."); + return; + } + + const id = projectId ?? getCurrentProject(); + + if (!id) { + logger.error("No active project. Run 'ub project use' to select one."); + return; + } + + try { + const project = await getProject(id); + + console.log(`\n${project.name}\n`); + console.log(`${label("ID")} ${project._id}`); + + if (project.description) { + console.log(`${label("Description")} ${project.description}`); + } + + console.log(`${label("Auth")} ${project.isAuthEnabled ? "Enabled" : "Disabled"}`); + console.log(`${label("DB used")} ${formatBytes(project.databaseUsed ?? 0)}`); + console.log(`${label("Storage")} ${formatBytes(project.storageUsed ?? 0)}`); + console.log(`${label("Collections")} ${project.collections?.length ?? 0}`); + + if (project.collections && project.collections.length > 0) { + console.log("\nCollections:"); + for (const col of project.collections) { + const fieldCount = col.model?.length ?? 0; + console.log(` • ${col.name} (${fieldCount} field${fieldCount !== 1 ? "s" : ""})`); + } + } + + console.log(); + } catch (error) { + if (error instanceof APIError) { + if (error.status === 401) { + logger.error("Token is invalid or expired. Run 'ub login' to re-authenticate."); + } else if (error.status === 404) { + logger.error("Project not found."); + } else { + logger.error(error.message); + } + return; + } + logger.error("Unable to connect to the urBackend API."); + } +} diff --git a/sdks/urbackend-cli/src/commands/project/list.ts b/sdks/urbackend-cli/src/commands/project/list.ts new file mode 100644 index 000000000..f82771b4f --- /dev/null +++ b/sdks/urbackend-cli/src/commands/project/list.ts @@ -0,0 +1,52 @@ +import { listProjects } from "../../services/project.service.js"; +import { getCurrentProject, getToken } from "../../core/config.js"; +import { formatBytes } from "../../utils/format.js"; +import { APIError } from "../../core/errors.js"; +import { logger } from "../../core/logger.js"; + +export async function projectListCommand(): Promise { + const token = getToken(); + if (!token) { + logger.error("You are not logged in. Run 'ub login' first."); + return; + } + + try { + const projects = await listProjects(); + const currentProjectId = getCurrentProject(); + + if (projects.length === 0) { + logger.info("No projects found. Create one at dashboard.urbackend.bitbros.in"); + return; + } + + console.log(`\nFound ${projects.length} project(s):\n`); + + for (const project of projects) { + const active = project._id === currentProjectId ? " (active)" : ""; + const db = formatBytes(project.databaseUsed ?? 0); + const colCount = project.collections?.length ?? 0; + + console.log(` ${project.name}${active}`); + console.log(` ID ${project._id}`); + console.log(` Collections ${colCount}`); + console.log(` DB used ${db}`); + + if (project.description) { + console.log(` Description ${project.description}`); + } + + console.log(); + } + } catch (error) { + if (error instanceof APIError) { + if (error.status === 401) { + logger.error("Token is invalid or expired. Run 'ub login' to re-authenticate."); + } else { + logger.error(error.message); + } + return; + } + logger.error("Unable to connect to the urBackend API."); + } +} diff --git a/sdks/urbackend-cli/src/commands/project/use.ts b/sdks/urbackend-cli/src/commands/project/use.ts new file mode 100644 index 000000000..58b42c4fe --- /dev/null +++ b/sdks/urbackend-cli/src/commands/project/use.ts @@ -0,0 +1,74 @@ +import { listProjects, getProject } from "../../services/project.service.js"; +import { saveCurrentProject, getToken } from "../../core/config.js"; +import { prompt } from "../../utils/prompt.js"; +import { APIError } from "../../core/errors.js"; +import { logger } from "../../core/logger.js"; + +export async function projectUseCommand(projectIdOrName?: string): Promise { + const token = getToken(); + if (!token) { + logger.error("You are not logged in. Run 'ub login' first."); + return; + } + + try { + const projects = await listProjects(); + + if (projects.length === 0) { + logger.info("No projects found. Create one at dashboard.urbackend.bitbros.in"); + return; + } + + let selectedId: string | undefined; + + if (projectIdOrName) { + // Try matching by ID or name + const match = projects.find( + (p) => + p._id === projectIdOrName || + p.name.toLowerCase() === projectIdOrName.toLowerCase(), + ); + if (!match) { + logger.error(`No project found matching "${projectIdOrName}".`); + logger.info("Run 'ub project list' to see available projects."); + return; + } + selectedId = match._id; + } else { + // Interactive selection + console.log("\nAvailable projects:\n"); + projects.forEach((p, i) => { + console.log(` [${i + 1}] ${p.name} (${p._id})`); + }); + console.log(); + + const answer = await prompt("Enter project number: "); + if (!/^\d+$/.test(answer)) { + logger.error("Invalid selection."); + return; + } + const index = Number(answer) - 1; + if (isNaN(index) || index < 0 || index >= projects.length) { + logger.error("Invalid selection."); + return; + } + + selectedId = projects[index]._id; + } + + saveCurrentProject(selectedId); + + const project = projects.find((p) => p._id === selectedId)!; + logger.success(`Switched to project: ${project.name} (${selectedId})`); + } catch (error) { + if (error instanceof APIError) { + if (error.status === 401) { + logger.error("Token is invalid or expired. Run 'ub login' to re-authenticate."); + } else { + logger.error(error.message); + } + return; + } + logger.error("Unable to connect to the urBackend API."); + } +} diff --git a/sdks/urbackend-cli/src/commands/status/index.ts b/sdks/urbackend-cli/src/commands/status/index.ts new file mode 100644 index 000000000..719dd0c5c --- /dev/null +++ b/sdks/urbackend-cli/src/commands/status/index.ts @@ -0,0 +1,96 @@ +import { getGlobalStats, getRecentActivity } from "../../services/analytics.service.js"; +import { getProject } from "../../services/project.service.js"; +import { getToken, getCurrentProject } from "../../core/config.js"; +import { formatBytes, label } from "../../utils/format.js"; +import { APIError } from "../../core/errors.js"; +import { logger } from "../../core/logger.js"; + +function statusIcon(status: number): string { + if (status < 300) return "✓"; + if (status < 400) return "→"; + if (status < 500) return "⚠"; + return "✖"; +} + +export async function statusCommand(): Promise { + const token = getToken(); + if (!token) { + logger.error("You are not logged in. Run 'ub login' first."); + return; + } + + const currentProjectId = getCurrentProject(); + + try { + const stats = await getGlobalStats(); + + console.log("\n── Account ─────────────────────────────────────"); + console.log(`${label("Plan")} ${stats.plan.toUpperCase()}`); + + if (stats.planExpiresAt) { + console.log(`${label("Plan expires")} ${stats.planExpiresAt}`); + } + + console.log("\n── Usage ────────────────────────────────────────"); + console.log( + `${label("Projects")} ${stats.usage.totalProjects} / ${stats.limits.maxProjects}`, + ); + console.log( + `${label("Collections")} ${stats.usage.totalCollections} / ${stats.limits.maxCollections}`, + ); + console.log( + `${label("Database")} ${formatBytes(stats.usage.totalDatabaseUsed)} / ${formatBytes(stats.limits.mongoBytes)}`, + ); + console.log( + `${label("Storage")} ${formatBytes(stats.usage.totalStorageUsed)} / ${formatBytes(stats.limits.storageBytes)}`, + ); + console.log( + `${label("API requests")} ${stats.usage.totalRequests.toLocaleString()} / ${stats.limits.reqPerDay.toLocaleString()} today`, + ); + console.log(`${label("Auth users")} ${stats.usage.totalUsers} / ${stats.limits.authUsersLimit}`); + console.log(`${label("Webhooks")} ${stats.usage.totalWebhooks}`); + + if (currentProjectId) { + try { + const project = await getProject(currentProjectId); + console.log("\n── Active project ───────────────────────────────"); + console.log(`${label("Name")} ${project.name}`); + console.log(`${label("ID")} ${project._id}`); + console.log(`${label("Collections")} ${project.collections?.length ?? 0}`); + console.log(`${label("Auth")} ${project.isAuthEnabled ? "Enabled" : "Disabled"}`); + } catch { + // non-fatal + } + } else { + console.log("\nTip: Run 'ub project use' to select a project."); + } + + try { + const activity = await getRecentActivity(); + if (activity.length > 0) { + console.log("\n── Recent activity (last 10) ────────────────────"); + for (const log of activity.slice(0, 10)) { + const icon = statusIcon(log.status); + const time = new Date(log.timestamp).toLocaleTimeString(); + console.log( + ` ${icon} [${log.status}] ${log.method.padEnd(6)} ${log.path.padEnd(32)} ${time}`, + ); + } + } + } catch { + // non-fatal + } + + console.log(); + } catch (error) { + if (error instanceof APIError) { + if (error.status === 401) { + logger.error("Token is invalid or expired. Run 'ub login' to re-authenticate."); + } else { + logger.error(error.message); + } + return; + } + logger.error("Unable to connect to the urBackend API."); + } +} \ No newline at end of file diff --git a/sdks/urbackend-cli/src/core/api.ts b/sdks/urbackend-cli/src/core/api.ts new file mode 100644 index 000000000..e42ff83af --- /dev/null +++ b/sdks/urbackend-cli/src/core/api.ts @@ -0,0 +1,83 @@ +import { loadConfig } from "./config.js"; +import { APIError } from "./errors.js"; + +export interface FetchOptions extends RequestInit { + /** Override the PAT for this single request (e.g. during login verification) */ + token?: string; +} + +/** + * Central HTTP client for all CLI → dashboard-api communication. + * Automatically injects the stored PAT as a Bearer token. + * + * All dashboard-api routes are mounted under /api — this client + * prepends that prefix automatically so callers just write /user/cli/me. + */ +export async function apiFetch( + endpoint: string, + options: FetchOptions = {}, +): Promise { + const config = loadConfig(); + + const headers = new Headers(options.headers); + + if (options.body && !headers.has("Content-Type")) { + headers.set("Content-Type", "application/json"); + } + + const token = options.token ?? config.pat; + if (token) { + headers.set("Authorization", `Bearer ${token}`); + } + + // All dashboard-api routes are under /api + const base = config.apiBase.replace(/\/+$/, ""); + const apiPath = endpoint.startsWith("/") ? endpoint : `/${endpoint}`; + const url = `${base}/api${apiPath}`; + + let response: Response; + + const controller = new AbortController(); + const signal = options.signal ?? controller.signal; + const timeoutId = + options.signal ? undefined : setTimeout(() => controller.abort(), 15_000); + + try { + response = await fetch(url, { + ...options, + headers, + signal, + }); + } catch (error) { + throw new APIError( + 0, + error instanceof Error && error.name === "AbortError" + ? "The urBackend API request timed out." + : "Unable to connect to the urBackend API. Is the server running?", + ); + } finally { + if (timeoutId) clearTimeout(timeoutId); + } + + if (!response.ok) { + let message = response.statusText; + + try { + const body = (await response.json()) as { + message?: string; + error?: string; + }; + message = body.message ?? body.error ?? message; + } catch { + // ignore — use statusText + } + + throw new APIError(response.status, message); + } + + if (response.status === 204) { + return undefined as T; + } + + return (await response.json()) as T; +} \ No newline at end of file diff --git a/sdks/urbackend-cli/src/core/config.ts b/sdks/urbackend-cli/src/core/config.ts new file mode 100644 index 000000000..4ab5aaaa5 --- /dev/null +++ b/sdks/urbackend-cli/src/core/config.ts @@ -0,0 +1,77 @@ +import fs from "node:fs"; +import path from "node:path"; +import type { CLIConfig } from "../types/config.js"; +import { CONFIG_DIR, CONFIG_PATH, DEFAULT_API_BASE } from "./constants.js"; + +const DEFAULT_CONFIG: CLIConfig = { + apiBase: DEFAULT_API_BASE, +}; + +export function loadConfig(): CLIConfig { + if (!fs.existsSync(CONFIG_PATH)) { + return { ...DEFAULT_CONFIG }; + } + + try { + const raw = fs.readFileSync(CONFIG_PATH, "utf8"); + return { + ...DEFAULT_CONFIG, + ...JSON.parse(raw), + }; + } catch { + return { ...DEFAULT_CONFIG }; + } +} + +export function saveConfig(config: CLIConfig): void { + if (!fs.existsSync(CONFIG_DIR)) { + fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 }); + } + + // Atomic write: write to temp file then rename to avoid corruption on crash + const tmp = CONFIG_PATH + ".tmp"; + fs.writeFileSync(tmp, JSON.stringify(config, null, 2), { + encoding: "utf8", + mode: 0o600, + }); + fs.renameSync(tmp, CONFIG_PATH); + fs.chmodSync(CONFIG_DIR, 0o700); + fs.chmodSync(CONFIG_PATH, 0o600); +} + +export function saveToken(token: string): void { + const config = loadConfig(); + const nextConfig: CLIConfig = { ...config, pat: token }; + if (config.pat && config.pat !== token) { + delete nextConfig.currentProject; + } + saveConfig(nextConfig); +} + +export function clearToken(): void { + const config = loadConfig(); + delete config.pat; + delete config.currentProject; + saveConfig(config); +} + +export function getToken(): string | undefined { + return loadConfig().pat; +} + +export function saveCurrentProject(projectId: string): void { + const config = loadConfig(); + saveConfig({ ...config, currentProject: projectId }); +} + +export function getCurrentProject(): string | undefined { + return loadConfig().currentProject; +} + +export function configFilePath(): string { + return CONFIG_PATH; +} + +export function configDirPath(): string { + return path.dirname(CONFIG_PATH); +} diff --git a/sdks/urbackend-cli/src/core/constants.ts b/sdks/urbackend-cli/src/core/constants.ts new file mode 100644 index 000000000..4f64eb00b --- /dev/null +++ b/sdks/urbackend-cli/src/core/constants.ts @@ -0,0 +1,15 @@ +import os from "node:os"; +import path from "node:path"; + +export const CLI_NAME = "ub"; + +export const CONFIG_DIR = path.join(os.homedir(), ".ub"); + +export const CONFIG_PATH = path.join(CONFIG_DIR, "config.json"); + +/** + * Default production API base URL. + * Override via URBACKEND_API_URL env variable for local development. + */ +export const DEFAULT_API_BASE = + process.env.URBACKEND_API_URL ?? "https://api.urbackend.bitbros.in"; diff --git a/sdks/urbackend-cli/src/core/errors.ts b/sdks/urbackend-cli/src/core/errors.ts new file mode 100644 index 000000000..7f042ae6e --- /dev/null +++ b/sdks/urbackend-cli/src/core/errors.ts @@ -0,0 +1,9 @@ +export class APIError extends Error { + constructor( + public readonly status: number, + message: string, + ) { + super(message); + this.name = "APIError"; + } +} diff --git a/sdks/urbackend-cli/src/core/logger.ts b/sdks/urbackend-cli/src/core/logger.ts new file mode 100644 index 000000000..cd21b9733 --- /dev/null +++ b/sdks/urbackend-cli/src/core/logger.ts @@ -0,0 +1,17 @@ +export const logger = { + success(message: string): void { + console.log(`✓ ${message}`); + }, + + error(message: string): void { + console.error(`✖ ${message}`); + }, + + warn(message: string): void { + console.warn(`⚠ ${message}`); + }, + + info(message: string): void { + console.log(message); + }, +}; diff --git a/sdks/urbackend-cli/src/index.ts b/sdks/urbackend-cli/src/index.ts new file mode 100644 index 000000000..6370dfc2e --- /dev/null +++ b/sdks/urbackend-cli/src/index.ts @@ -0,0 +1,101 @@ +import { Command } from "commander"; +import { loginCommand } from "./commands/auth/login.js"; +import { logoutCommand } from "./commands/auth/logout.js"; +import { whoamiCommand } from "./commands/auth/whoami.js"; +import { projectListCommand } from "./commands/project/list.js"; +import { projectUseCommand } from "./commands/project/use.js"; +import { projectInfoCommand } from "./commands/project/info.js"; +import { collectionListCommand } from "./commands/collection/list.js"; +import { collectionDeleteCommand } from "./commands/collection/delete.js"; +import { statusCommand } from "./commands/status/index.js"; +import { doctorCommand } from "./commands/doctor/index.js"; + +const program = new Command(); + +program + .name("ub") + .description("Official urBackend CLI — manage projects, schemas, and more") + .version("0.1.0"); + +// ── Authentication ────────────────────────────────────────────────────────── + +program + .command("login") + .description("Authenticate with a Personal Access Token") + .action(loginCommand); + +program + .command("logout") + .description("Remove stored credentials from this machine") + .action(logoutCommand); + +program + .command("whoami") + .description("Show the currently authenticated developer") + .action(whoamiCommand); + +// ── Projects ───────────────────────────────────────────────────────────────── + +const projectCmd = program + .command("project") + .description("Manage urBackend projects"); + +projectCmd + .command("list") + .alias("ls") + .description("List all accessible projects") + .action(projectListCommand); + +projectCmd + .command("use [projectIdOrName]") + .description("Set the active project for subsequent commands") + .action(projectUseCommand); + +projectCmd + .command("info [projectId]") + .description("Show details for the active (or specified) project") + .action(projectInfoCommand); + +// ── Collections ────────────────────────────────────────────────────────────── + +const collectionCmd = program + .command("collection") + .alias("col") + .description("Manage collections inside the active project"); + +collectionCmd + .command("list") + .alias("ls") + .description("List all collections in the active project") + .option("-p, --project ", "Target a specific project ID") + .action((options) => collectionListCommand(options.project)); + +collectionCmd + .command("delete ") + .alias("rm") + .description("Delete a collection and all its data") + .option("-f, --force", "Skip confirmation prompt") + .option("-p, --project ", "Target a specific project ID") + .action((name, options) => collectionDeleteCommand(name, options)); + +// ── Status ──────────────────────────────────────────────────────────────────── + +program + .command("status") + .description("Show account usage and recent API activity") + .action(statusCommand); + +// ── Doctor ──────────────────────────────────────────────────────────────────── + +program + .command("doctor") + .description("Run diagnostic checks on your CLI setup") + .option("--json", "Output results as JSON (useful for CI/AI agents)") + .action((options) => doctorCommand(options)); + +// ── Parse ───────────────────────────────────────────────────────────────────── + +program.parseAsync(process.argv).catch((err: Error) => { + console.error(`✖ ${err.message}`); + process.exit(1); +}); diff --git a/sdks/urbackend-cli/src/services/analytics.service.ts b/sdks/urbackend-cli/src/services/analytics.service.ts new file mode 100644 index 000000000..339b27331 --- /dev/null +++ b/sdks/urbackend-cli/src/services/analytics.service.ts @@ -0,0 +1,57 @@ +import { apiFetch } from "../core/api.js"; + +interface APIResponse { + success: boolean; + data: T; + message: string; +} + +export interface GlobalStats { + plan: string; + planExpiresAt: string | null; + limits: { + maxProjects: number; + maxCollections: number; + reqPerDay: number; + storageBytes: number; + mongoBytes: number; + authUsersLimit: number; + }; + usage: { + totalProjects: number; + totalCollections: number; + totalStorageUsed: number; + totalDatabaseUsed: number; + totalRequests: number; + totalWebhooks: number; + totalUsers: number; + }; +} + +export interface RecentActivityLog { + id: string; + projectName: string; + projectId: string; + method: string; + path: string; + status: number; + timestamp: string; +} + +export async function getGlobalStats(): Promise { + const res = await apiFetch>("/analytics/stats", { + method: "GET", + }); + return res.data; +} + +export async function getRecentActivity(): Promise { + const res = await apiFetch>( + "/analytics/activity", + { method: "GET" }, + ); + if (!Array.isArray(res.data)) { + throw new Error("Invalid response from /analytics/activity"); + } + return res.data; +} \ No newline at end of file diff --git a/sdks/urbackend-cli/src/services/auth.service.ts b/sdks/urbackend-cli/src/services/auth.service.ts new file mode 100644 index 000000000..925456d57 --- /dev/null +++ b/sdks/urbackend-cli/src/services/auth.service.ts @@ -0,0 +1,24 @@ +import { apiFetch } from "../core/api.js"; +import type { CLIProfile } from "../types/auth.js"; + +interface APIResponse { + success: boolean; + data: T; + message: string; +} + +export async function authenticate(token: string): Promise { + const res = await apiFetch>("/user/cli/me", { + method: "GET", + token, + }); + + return res.data; +} + +export async function getProfile(): Promise { + const res = await apiFetch>("/user/cli/me", { + method: "GET", + }); + return res.data; +} \ No newline at end of file diff --git a/sdks/urbackend-cli/src/services/collection.service.ts b/sdks/urbackend-cli/src/services/collection.service.ts new file mode 100644 index 000000000..bd11020da --- /dev/null +++ b/sdks/urbackend-cli/src/services/collection.service.ts @@ -0,0 +1,40 @@ +import { apiFetch } from "../core/api.js"; +import type { Collection } from "../types/project.js"; + +interface CollectionResponse { + data: Collection; + message: string; +} + +/** + * Creates a new collection inside a project. + * Endpoint: POST /projects/:projectId/collections + */ +export async function createCollection( + projectId: string, + payload: { name: string; model: Collection["model"] }, +): Promise { + const res = await apiFetch( + `/projects/${projectId}/collections`, + { + method: "POST", + body: JSON.stringify(payload), + }, + ); + return res.data; +} + +/** + * Deletes a collection from a project. + * Endpoint: DELETE /projects/:projectId/collections/:collectionName + */ +export async function deleteCollection( + projectId: string, + collectionName: string, +): Promise { + const encodedName = encodeURIComponent(collectionName); + return apiFetch( + `/projects/${projectId}/collections/${encodedName}`, + { method: "DELETE" }, + ); +} diff --git a/sdks/urbackend-cli/src/services/project.service.ts b/sdks/urbackend-cli/src/services/project.service.ts new file mode 100644 index 000000000..3b8aa1f6a --- /dev/null +++ b/sdks/urbackend-cli/src/services/project.service.ts @@ -0,0 +1,45 @@ +import { apiFetch } from "../core/api.js"; +import type { Project, ProjectListItem } from "../types/project.js"; + +interface APIResponse { + success: boolean; + data: T; + message: string; +} + +export async function listProjects(): Promise { + const res = await apiFetch>( + "/projects", + { method: "GET" }, + ); + // Backend returns raw array, not wrapped in { data: [...] } + if (Array.isArray(res)) return res; + return (res as APIResponse).data ?? []; +} + +export async function getProject(projectId: string): Promise { + const res = await apiFetch>( + `/projects/${projectId}`, + { method: "GET" }, + ); + if ("data" in res && res.data) return (res as APIResponse).data; + return res as Project; +} + +export async function createProject(payload: { + name: string; + description?: string; +}): Promise { + const res = await apiFetch>("/projects", { + method: "POST", + body: JSON.stringify(payload), + }); + if ("data" in res && res.data) return (res as APIResponse).data; + return res as Project; +} + +export async function deleteProject(projectId: string): Promise { + return apiFetch(`/projects/${projectId}`, { + method: "DELETE", + }); +} \ No newline at end of file diff --git a/sdks/urbackend-cli/src/types/analytics.ts b/sdks/urbackend-cli/src/types/analytics.ts new file mode 100644 index 000000000..a6bceb226 --- /dev/null +++ b/sdks/urbackend-cli/src/types/analytics.ts @@ -0,0 +1,31 @@ +export interface GlobalStats { + plan: string; + planExpiresAt: string | null; + limits: { + maxProjects: number; + maxCollections: number; + reqPerDay: number; + storageBytes: number; + mongoBytes: number; + authUsersLimit: number; + }; + usage: { + totalProjects: number; + totalCollections: number; + totalStorageUsed: number; + totalDatabaseUsed: number; + totalRequests: number; + totalWebhooks: number; + totalUsers: number; + }; +} + +export interface RecentActivityLog { + id: string; + projectName: string; + projectId: string; + method: string; + path: string; + status: number; + timestamp: string; +} \ No newline at end of file diff --git a/sdks/urbackend-cli/src/types/auth.ts b/sdks/urbackend-cli/src/types/auth.ts new file mode 100644 index 000000000..914d2a6ed --- /dev/null +++ b/sdks/urbackend-cli/src/types/auth.ts @@ -0,0 +1,44 @@ +export interface CLIDeveloper { + id: string; + email: string; + plan: "free" | "pro"; + githubUsername: string | null; + avatarUrl: string | null; +} + +export interface CLIAuth { + scopes: string[]; + tokenType: "human" | "agent"; +} + +export interface CLIProfile { + developer: CLIDeveloper; + auth: CLIAuth; +} + +export interface PAT { + id: string; + suffix: string; + label: string; + type: "human" | "agent"; + scopes: string[]; + createdAt: string; + expiresAt: string; + lastUsedAt: string | null; + lastUsedIp: string | null; +} + +export interface PATListResponse { + pats: PAT[]; +} + +export interface PATCreateResponse { + rawToken: string; + pat: { + suffix: string; + label: string; + type: string; + scopes: string[]; + expiresAt: string; + }; +} diff --git a/sdks/urbackend-cli/src/types/config.ts b/sdks/urbackend-cli/src/types/config.ts new file mode 100644 index 000000000..21fb68baf --- /dev/null +++ b/sdks/urbackend-cli/src/types/config.ts @@ -0,0 +1,5 @@ +export interface CLIConfig { + apiBase: string; + pat?: string; + currentProject?: string; +} diff --git a/sdks/urbackend-cli/src/types/project.ts b/sdks/urbackend-cli/src/types/project.ts new file mode 100644 index 000000000..4378fc541 --- /dev/null +++ b/sdks/urbackend-cli/src/types/project.ts @@ -0,0 +1,45 @@ +export interface CollectionField { + key: string; + type: "String" | "Number" | "Boolean" | "Date" | "Object" | "Array" | "Ref"; + required: boolean; + unique?: boolean; + ref?: string; + default?: unknown; + fields?: CollectionField[]; +} + +export interface CollectionRLS { + enabled: boolean; + mode: "public-read" | "private" | "owner-write-only"; + ownerField: string; + requireAuthForWrite: boolean; +} + +export interface Collection { + _id?: string; + name: string; + model: CollectionField[]; + rls?: CollectionRLS; +} + +export interface Project { + _id: string; + name: string; + description?: string; + publishableKey?: string; + collections: Collection[]; + isAuthEnabled?: boolean; + databaseUsed?: number; + storageUsed?: number; + updatedAt?: string; +} + +export interface ProjectListItem { + _id: string; + name: string; + description?: string; + collections: Collection[]; + databaseUsed?: number; + storageUsed?: number; + updatedAt?: string; +} diff --git a/sdks/urbackend-cli/src/utils/format.ts b/sdks/urbackend-cli/src/utils/format.ts new file mode 100644 index 000000000..e96fa6e3f --- /dev/null +++ b/sdks/urbackend-cli/src/utils/format.ts @@ -0,0 +1,29 @@ +/** + * Formats bytes into a human-readable string (KB, MB, GB). + */ +export function formatBytes(bytes: number): string { + if (bytes === 0) return "0 B"; + const units = ["B", "KB", "MB", "GB", "TB"]; + const i = Math.floor(Math.log(bytes) / Math.log(1024)); + const value = (bytes / Math.pow(1024, i)).toFixed(2); + return `${value} ${units[i]}`; +} + +/** + * Formats an ISO date string to a locale-friendly short format. + */ +export function formatDate(iso: string | null | undefined): string { + if (!iso) return "Never"; + return new Date(iso).toLocaleDateString("en-US", { + year: "numeric", + month: "short", + day: "numeric", + }); +} + +/** + * Pads a string label to a fixed width for aligned terminal output. + */ +export function label(text: string, width = 14): string { + return text.padEnd(width); +} diff --git a/sdks/urbackend-cli/src/utils/prompt.ts b/sdks/urbackend-cli/src/utils/prompt.ts new file mode 100644 index 000000000..342d26c2b --- /dev/null +++ b/sdks/urbackend-cli/src/utils/prompt.ts @@ -0,0 +1,52 @@ +import { createInterface } from "node:readline/promises"; +import { stdin, stdout } from "node:process"; + +/** + * Prompts the user for a single line of input. + * Automatically closes the readline interface after reading. + */ +export async function prompt(question: string): Promise { + const rl = createInterface({ input: stdin, output: stdout }); + const answer = (await rl.question(question)).trim(); + rl.close(); + return answer; +} + +/** + * Prompts the user for secret input (like a password/token) without echoing characters. + */ +export async function promptSecret(question: string): Promise { + const rl = createInterface({ + input: stdin, + output: stdout, + }); + + // Write the question first + stdout.write(question); + + // Standard Node.js readline secret pattern using internal _writeToOutput override: + const oldWrite = (rl as any)._writeToOutput; + (rl as any)._writeToOutput = function _writeToOutput(stringToWrite: string) { + if (stringToWrite === "\r" || stringToWrite === "\n" || stringToWrite === "\r\n") { + oldWrite.call(rl, stringToWrite); + } else if (stringToWrite === question) { + oldWrite.call(rl, stringToWrite); + } else { + // Echo nothing or a mask (nothing is standard for non-echoing Unix style secret prompts) + } + }; + + const answer = (await rl.question("")).trim(); + (rl as any)._writeToOutput = oldWrite; + rl.close(); + return answer; +} + +/** + * Prompts for a yes/no confirmation. + * Returns true for "y" or "yes" (case-insensitive), false otherwise. + */ +export async function confirm(question: string): Promise { + const answer = await prompt(`${question} (y/n): `); + return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes"; +} diff --git a/sdks/urbackend-cli/src/utils/token.ts b/sdks/urbackend-cli/src/utils/token.ts new file mode 100644 index 000000000..7b1382587 --- /dev/null +++ b/sdks/urbackend-cli/src/utils/token.ts @@ -0,0 +1,7 @@ +/** + * Validates the format of a Personal Access Token. + * urBackend PATs always start with "ubpat_" followed by at least 10 characters. + */ +export function isValidPAT(token: string): boolean { + return typeof token === "string" && /^ubpat_\S{10,}$/.test(token); +} diff --git a/sdks/urbackend-cli/tsconfig.json b/sdks/urbackend-cli/tsconfig.json new file mode 100644 index 000000000..97092a363 --- /dev/null +++ b/sdks/urbackend-cli/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "resolveJsonModule": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src"], + "exclude": ["node_modules", "dist"] +} diff --git a/sdks/urbackend-cli/tsup.config.ts b/sdks/urbackend-cli/tsup.config.ts new file mode 100644 index 000000000..25b8d35e4 --- /dev/null +++ b/sdks/urbackend-cli/tsup.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from "tsup"; + +export default defineConfig({ + entry: ["src/index.ts"], + format: ["esm", "cjs"], + target: "node20", + outDir: "dist", + dts: true, + sourcemap: true, + clean: true, + splitting: false, + banner: { + js: "// @urbackend/cli — Official urBackend CLI", + }, +});