From d275d3255da8422ade20e368eb1c278657387ee3 Mon Sep 17 00:00:00 2001 From: kevinwang Date: Wed, 16 Jul 2025 17:44:49 -0400 Subject: [PATCH 1/5] feat: Added login method --- package-lock.json | 129 ++++++++++++++++++++++++++++++++++++- package.json | 13 ++-- src/commands/login.ts | 23 +++++++ src/orchestrators/login.ts | 55 ++++++++++++++++ 4 files changed, 212 insertions(+), 8 deletions(-) create mode 100644 src/commands/login.ts create mode 100644 src/orchestrators/login.ts diff --git a/package-lock.json b/package-lock.json index e9c62510..cf484014 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "codify", - "version": "0.7.2", + "version": "0.9.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "codify", - "version": "0.7.2", + "version": "0.9.0", "license": "MIT", "dependencies": { "@codifycli/ink-form": "0.0.12", @@ -36,6 +36,7 @@ "json5": "^2.2.3", "latest-semver": "^4.0.0", "nanoid": "^5.0.9", + "open": "^10.1.2", "parse-json": "^8.1.0", "react": "^18.3.1", "semver": "^7.5.4", @@ -5617,6 +5618,20 @@ "semver": "^7.0.0" } }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", @@ -6425,6 +6440,32 @@ "node": ">=0.10.0" } }, + "node_modules/default-browser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", + "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", + "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/defer-to-connect": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", @@ -6450,6 +6491,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/define-properties": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", @@ -9586,6 +9638,37 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container/node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-map": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", @@ -10792,6 +10875,37 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/open": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/open/-/open-10.1.2.tgz", + "integrity": "sha512-cxN6aIDPz6rm8hbebcP7vrQNhvRcveZoJU72Y7vskh4oIm+BZwBECnx5nTmrlres1Qapvx27Qo1Auukpf8PKXw==", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open/node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -11894,6 +12008,17 @@ "fsevents": "~2.3.2" } }, + "node_modules/run-applescript": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", + "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", diff --git a/package.json b/package.json index 861141b0..fed94b10 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "@codifycli/ink-form": "0.0.12", "@homebridge/node-pty-prebuilt-multiarch": "^0.12.0-beta.5", "@inkjs/ui": "^2", + "@mischnic/json-sourcemap": "^0.1.1", "@oclif/core": "^4.0.8", "@oclif/plugin-autocomplete": "^3.2.24", "@oclif/plugin-help": "^6.2.4", @@ -22,19 +23,19 @@ "ink-big-text": "^2.0.0", "ink-gradient": "^3.0.0", "ink-select-input": "^6.0.0", + "jju": "^1.4.0", "jotai": "^2.11.1", "js-yaml": "^4.1.0", "js-yaml-source-map": "^0.2.2", "json-source-map": "^0.6.1", + "json5": "^2.2.3", "latest-semver": "^4.0.0", "nanoid": "^5.0.9", + "open": "^10.1.2", "parse-json": "^8.1.0", "react": "^18.3.1", "semver": "^7.5.4", - "supports-color": "^9.4.0", - "json5": "^2.2.3", - "@mischnic/json-sourcemap": "^0.1.1", - "jju": "^1.4.0" + "supports-color": "^9.4.0" }, "description": "Codify allows users to configure settings, install new packages, and automate their systems using code instead of the GUI. Get set up on a new laptop in one click, maintain a Codify file within your project so anyone can get started and never lose your cool apps or favourite settings again.", "devDependencies": { @@ -43,15 +44,15 @@ "@types/chalk": "^2.2.0", "@types/debug": "^4.1.12", "@types/diff": "^7.0.1", + "@types/jju": "^1.4.5", "@types/js-yaml": "^4.0.9", + "@types/json5": "^2.2.0", "@types/mocha": "^10.0.10", "@types/node": "^20", "@types/react": "^18.3.1", "@types/semver": "^7.5.4", "@types/strip-ansi": "^5.2.1", - "@types/jju": "^1.4.5", "@typescript-eslint/eslint-plugin": "^8.16.0", - "@types/json5": "^2.2.0", "codify-plugin-lib": "^1.0.151", "esbuild": "^0.24.0", "esbuild-plugin-copy": "^2.1.1", diff --git a/src/commands/login.ts b/src/commands/login.ts new file mode 100644 index 00000000..85907c8b --- /dev/null +++ b/src/commands/login.ts @@ -0,0 +1,23 @@ +import { BaseCommand } from '../common/base-command.js'; +import { LoginOrchestrator } from '../orchestrators/login.js'; + +export default class Login extends BaseCommand { + static description = + `Validate a codify.jsonc/codify.json/codify.yaml file. + +For more information, visit: https://docs.codifycli.com/commands/validate +` + + static flags = {} + + static examples = [ + '<%= config.bin %> <%= command.id %>', + '<%= config.bin %> <%= command.id %> --path=../../import.codify.jsonc', + ] + + public async run(): Promise { + const { flags } = await this.parse(Login) + + await LoginOrchestrator.run(); + } +} diff --git a/src/orchestrators/login.ts b/src/orchestrators/login.ts new file mode 100644 index 00000000..a783f03f --- /dev/null +++ b/src/orchestrators/login.ts @@ -0,0 +1,55 @@ +import * as fs from 'node:fs/promises'; +import { type IncomingMessage, ServerResponse, createServer } from 'node:http'; +import * as os from 'node:os'; +import path from 'node:path'; +import open from 'open'; + +export class LoginOrchestrator { + static async run(){ + const server = createServer((req, res) => { + LoginOrchestrator.handleRequests(req, res); + }); + + process.stdout.on('data', (data) => { + console.log(data); + }) + + server.listen(51_039, 'localhost', () => { + console.log('Opening CLI auth page...') + open('http://localhost:3000/auth/cli'); + }) + } + + private static async handleRequests(req: IncomingMessage, res: ServerResponse) { + try { + if (req.method !== 'POST') { + res.writeHead(400, { 'Content-Type': 'application/json' }); + return; + } + + const json = await new Promise((resolve) => { + const buf = new Array() + req.on('data', (chunk) => { + buf.push(chunk); + }).on('end', () => { + const body = Buffer.concat(buf).toString(); + const json = JSON.parse(body); + resolve(json); + }).on('error', (err) => { + console.error(err); + }) + }); + + const credentialsPath = path.join(os.homedir(), '.codify', 'credentials.json'); + console.log(`Saving credentials to ${credentialsPath}`); + await fs.writeFile(credentialsPath, JSON.stringify(json)); + + res.writeHead(200, { 'Content-Type': 'application/json' }); + process.exit(0); + } catch (error) { + console.error(error); + res.writeHead(500, { 'Content-Type': 'application/json' }); + process.exit(1); + } + } +} From 6b2cf5b092490adcd61f710b0c73dbe6235a9e4a Mon Sep 17 00:00:00 2001 From: kevinwang Date: Sun, 27 Jul 2025 16:37:49 -0400 Subject: [PATCH 2/5] feat: Added connect integration with codify-dashboard --- package-lock.json | 309 ++++++++++++++++++++++++++++++++++- package.json | 9 +- src/commands/connect.ts | 24 +++ src/connect/apply.ts | 51 ++++++ src/connect/route-handler.ts | 33 ++++ src/connect/server.ts | 112 +++++++++++++ src/orchestrators/connect.ts | 91 +++++++++++ src/orchestrators/login.ts | 4 - 8 files changed, 623 insertions(+), 10 deletions(-) create mode 100644 src/commands/connect.ts create mode 100644 src/connect/apply.ts create mode 100644 src/connect/route-handler.ts create mode 100644 src/connect/server.ts create mode 100644 src/orchestrators/connect.ts diff --git a/package-lock.json b/package-lock.json index cf484014..e1e79a95 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,7 +40,9 @@ "parse-json": "^8.1.0", "react": "^18.3.1", "semver": "^7.5.4", - "supports-color": "^9.4.0" + "socket.io": "^4.8.1", + "supports-color": "^9.4.0", + "ws": "^8.18.3" }, "bin": { "codify": "bin/run.js" @@ -59,6 +61,8 @@ "@types/react": "^18.3.1", "@types/semver": "^7.5.4", "@types/strip-ansi": "^5.2.1", + "@types/uuid": "^10.0.0", + "@types/ws": "^8.18.1", "@typescript-eslint/eslint-plugin": "^8.16.0", "codify-plugin-lib": "^1.0.151", "esbuild": "^0.24.0", @@ -76,6 +80,7 @@ "strip-ansi": "^7.1.0", "tsx": "^4.7.3", "typescript": "5.3.3", + "uuid": "^10.0.0", "vitest": "^2.1.6" }, "engines": { @@ -4260,6 +4265,12 @@ "node": ">=16.0.0" } }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, "node_modules/@szmarczak/http-timer": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", @@ -4286,6 +4297,15 @@ "chalk": "*" } }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -4418,11 +4438,28 @@ "resolved": "https://registry.npmjs.org/@types/tinycolor2/-/tinycolor2-1.4.6.tgz", "integrity": "sha512-iEN8J0BoMnsWBqjVbWH/c0G0Hh7O21lpR2/+PrvAVgWdzL7eexIFm4JN/Wn10PTcmNdtS6U67r499mlWMXOxNw==" }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/wrap-ansi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz", "integrity": "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==" }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/yauzl": { "version": "2.10.3", "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", @@ -5059,6 +5096,19 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { "version": "8.14.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", @@ -5504,6 +5554,15 @@ } ] }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "license": "MIT", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, "node_modules/basic-ftp": { "version": "5.0.5", "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", @@ -6213,6 +6272,28 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/cosmiconfig": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", @@ -6659,6 +6740,73 @@ "once": "^1.4.0" } }, + "node_modules/engine.io": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", + "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", + "license": "MIT", + "dependencies": { + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/enhanced-resolve": { "version": "5.18.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz", @@ -10382,6 +10530,27 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -10576,6 +10745,15 @@ "node": ">=18" } }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/netmask": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", @@ -12500,6 +12678,119 @@ "tslib": "^2.0.3" } }, + "node_modules/socket.io": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", + "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "license": "MIT", + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.17.1" + } + }, + "node_modules/socket.io-adapter/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-adapter/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/socks": { "version": "2.8.3", "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", @@ -13815,6 +14106,15 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/vite": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/vite/-/vite-6.0.1.tgz", @@ -14200,9 +14500,10 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", "engines": { "node": ">=10.0.0" }, diff --git a/package.json b/package.json index fed94b10..748e2247 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,9 @@ "parse-json": "^8.1.0", "react": "^18.3.1", "semver": "^7.5.4", - "supports-color": "^9.4.0" + "socket.io": "^4.8.1", + "supports-color": "^9.4.0", + "ws": "^8.18.3" }, "description": "Codify allows users to configure settings, install new packages, and automate their systems using code instead of the GUI. Get set up on a new laptop in one click, maintain a Codify file within your project so anyone can get started and never lose your cool apps or favourite settings again.", "devDependencies": { @@ -52,6 +54,8 @@ "@types/react": "^18.3.1", "@types/semver": "^7.5.4", "@types/strip-ansi": "^5.2.1", + "@types/uuid": "^10.0.0", + "@types/ws": "^8.18.1", "@typescript-eslint/eslint-plugin": "^8.16.0", "codify-plugin-lib": "^1.0.151", "esbuild": "^0.24.0", @@ -69,7 +73,8 @@ "strip-ansi": "^7.1.0", "tsx": "^4.7.3", "typescript": "5.3.3", - "vitest": "^2.1.6" + "vitest": "^2.1.6", + "uuid": "^10.0.0" }, "overrides": { "ink-form": { diff --git a/src/commands/connect.ts b/src/commands/connect.ts new file mode 100644 index 00000000..08010949 --- /dev/null +++ b/src/commands/connect.ts @@ -0,0 +1,24 @@ +import { BaseCommand } from '../common/base-command.js'; +import { LoginOrchestrator } from '../orchestrators/login.js'; +import { ConnectOrchestrator } from '../orchestrators/connect.js'; + +export default class Connect extends BaseCommand { + static description = + `Validate a codify.jsonc/codify.json/codify.yaml file. + +For more information, visit: https://docs.codifycli.com/commands/validate +` + + static flags = {} + + static examples = [ + '<%= config.bin %> <%= command.id %>', + '<%= config.bin %> <%= command.id %> --path=../../import.codify.jsonc', + ] + + public async run(): Promise { + const { flags } = await this.parse(Connect) + + await ConnectOrchestrator.run(); + } +} diff --git a/src/connect/apply.ts b/src/connect/apply.ts new file mode 100644 index 00000000..86898ad9 --- /dev/null +++ b/src/connect/apply.ts @@ -0,0 +1,51 @@ +import { spawn } from '@homebridge/node-pty-prebuilt-multiarch'; +import * as fs from 'node:fs/promises'; +import * as os from 'node:os'; +import * as path from 'node:path'; +import { v4 as uuid } from 'uuid'; +import { WebSocket } from 'ws'; + +import { WsServerManager } from './server.js'; + +export function connectApplyInitHandler(msg: any, initWs: WebSocket, manager: WsServerManager) { + const sessionId = uuid(); + + manager.startAdhocWsServer(sessionId, async (ws) => { + console.log('connected apply ws'); + + const { config } = msg; + console.log('apply ws open', config); + + const tmpDir = await fs.mkdtemp(os.tmpdir()); + const filePath = path.join(tmpDir, 'codify.json'); + + await fs.writeFile(filePath, JSON.stringify(config)); + + const pty = spawn('zsh', ['-c', 'codify apply'], { + name: 'xterm-color', + cols: 80, + rows: 30, + cwd: process.env.HOME, + env: process.env + }); + + pty.onData((data) => { + ws.send(Buffer.from(data, 'utf8')); + }); + + ws.on('message', (message) => { + pty.write(message.toString('utf8')); + }) + + pty.onExit(({ exitCode, signal }) => { + console.log('pty exit', exitCode, signal); + // ws.close(exitCode); + ws.terminate(); + }) + }); + + initWs.send(JSON.stringify({ + cmd: 'apply_init_response', + sessionId, + })) +} diff --git a/src/connect/route-handler.ts b/src/connect/route-handler.ts new file mode 100644 index 00000000..0529c724 --- /dev/null +++ b/src/connect/route-handler.ts @@ -0,0 +1,33 @@ +import { WebSocket } from 'ws'; + +import { connectApplyInitHandler } from './apply.js'; +import { WsServerManager } from './server.js'; + +export async function defaultWsHandler(ws: WebSocket, manager: WsServerManager) { + ws.on('message', (message) => { + let msg; + try { + msg = JSON.parse(message.toString('utf8')); + console.log(msg); + } catch (error) { + console.error(error); + return; + } + + const { cmd } = msg; + if (!cmd) { + console.error('No cmd found'); + return; + } + + switch (cmd) { + case 'apply_init': { + connectApplyInitHandler(msg, ws, manager); + break; + } + } + + }) +} + + diff --git a/src/connect/server.ts b/src/connect/server.ts new file mode 100644 index 00000000..d6555008 --- /dev/null +++ b/src/connect/server.ts @@ -0,0 +1,112 @@ +import { IncomingMessage, Server, createServer } from 'node:http'; +import { v4 as uuid } from 'uuid'; +import { WebSocket, WebSocketServer } from 'ws'; + +const DEFAULT_PORT = 51_040; + +export class WsServerManager { + + server: Server; + port?: number; + + private wsServerMap = new Map(); + private handlerMap = new Map void>(); + + private connectionSecret; + + constructor(connectionSecret?: string) { + this.server = createServer(); + this.connectionSecret = connectionSecret; + this.wsServerMap.set('default', this.createWssServer()); + + this.initServer(); + } + + listen(cb?: () => void, port?: number, ) { + this.port = port ?? DEFAULT_PORT + this.server.listen(this.port, 'localhost', cb); + } + + setDefaultHandler(handler: (ws: WebSocket, manager: WsServerManager) => void): WsServerManager { + const wss = this.createWssServer(); + this.wsServerMap.set('default', wss); + this.handlerMap.set('default', handler); + + return this; + } + + addAdditionalHandlers(path: string, handler: (ws: WebSocket) => void): WsServerManager { + this.handlerMap.set(path, () => { + const wss = this.addWebsocketServer(); + + }); + + return this; + } + + startAdhocWsServer(sessionId: string, handler: (ws: WebSocket, manager: WsServerManager) => void) { + this.wsServerMap.set(sessionId, this.createWssServer()); + this.handlerMap.set(sessionId, handler); + } + + private addWebsocketServer(): string { + const key = uuid(); + + const wss = new WebSocketServer({ + noServer: true + }) + this.wsServerMap.set(key, wss); + + wss.on('close', () => { + this.wsServerMap.delete(key); + }) + + return key; + } + + private initServer() { + this.server.on('upgrade', (request, socket, head) => { + console.log('upgrade') + + const { pathname } = new URL(request.url!, 'ws://localhost:51040') + console.log('Pathname:', pathname) + + const code = request.headers['sec-websocket-protocol'] + if (this.connectionSecret && code !== this.connectionSecret) { + console.log('Auth failed'); + socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n') + socket.destroy() + return; + } + + if (pathname === '/' && this.handlerMap.has('default')) { + const wss = this.wsServerMap.get('default'); + wss?.handleUpgrade(request, socket, head, (ws, request) => this.handlerMap.get('default')!(ws, this, request)); + return; + } + + const pathSections = pathname.split('/').filter(Boolean); + console.log(pathSections); + console.log('available sessions', this.handlerMap) + + if (pathSections[0] === 'session' + && pathSections[1] + && this.handlerMap.has(pathSections[1]) + ) { + const sessionId = pathSections[1]; + console.log('session found, upgrading', sessionId); + + const wss = this.wsServerMap.get(sessionId)!; + + wss.handleUpgrade(request, socket, head, (ws, request) => this.handlerMap.get(sessionId)!(ws, this, request)); + return; + } + }) + } + + private createWssServer(): WebSocketServer { + return new WebSocketServer({ + noServer: true, + }) + } +} diff --git a/src/orchestrators/connect.ts b/src/orchestrators/connect.ts new file mode 100644 index 00000000..7b6b5684 --- /dev/null +++ b/src/orchestrators/connect.ts @@ -0,0 +1,91 @@ +import { spawn } from '@homebridge/node-pty-prebuilt-multiarch'; +import { randomBytes } from 'node:crypto'; +import open from 'open'; +import { WebSocket } from 'ws'; + +import { defaultWsHandler } from '../connect/route-handler.js'; +import { WsServerManager } from '../connect/server.js'; + +export class ConnectOrchestrator { + static async run() { + const connectionSecret = ConnectOrchestrator.tokenGenerate() + console.log(connectionSecret) + + const server = new WsServerManager(connectionSecret) + .setDefaultHandler(defaultWsHandler) + .addAdditionalHandlers('/apply-logs', () => {}) + .addAdditionalHandlers('/import-logs', () => {}) + .addAdditionalHandlers('/terminal', () => {}) + + server.listen(() => { + open(`http://localhost:3000/connection/success?code=${connectionSecret}`) + }); + } + + private static onConnection(ws: WebSocket) { + console.log('[WS] Connection opened'); + + ws.on('apply', (message) => { + let data; + try { + data = JSON.parse(message.toString('utf8')); + console.log(data); + } catch (error) { + console.error(error); + } + + }); + + ws.on('close', () => { + + }); + } + + /* + private static async onApply(ws: WebSocket, data: any) { + const { config } = data; + const tmpDir = await fs.mkdtemp(os.tmpdir()); + const filePath = path.join(tmpDir, 'codify.json'); + + await fs.writeFile(filePath, JSON.stringify(config)); + + const server = createServer() + const wss = new WebSocketServer({ + + }) + + server.on('upgrade', (request, socket, head) => { + wss.handleUpgrade(request, socket, head, (ws2) => { + const pty = spawn('zsh', ['-c', '"codify apply"'], { + name: 'xterm-color', + cols: 80, + rows: 30, + cwd: process.env.HOME, + env: process.env + }); + + pty.onData((data) => { + ws2.send(Buffer.from(data, 'utf8')); + }); + + ws2.on('message', (message) => { + pty.write(message.toString('utf8')); + }) + + pty.onExit((code) => { + ws2.close(code, code); + }) + }) + }); + + server.listen(2123, () => { + ws.emit('apply_Response', { + wsPass: 'pass', + }) + }) + } */ + + private static tokenGenerate(length = 20): string { + return Buffer.from(randomBytes(length)).toString('hex') + } +} diff --git a/src/orchestrators/login.ts b/src/orchestrators/login.ts index a783f03f..7c5ddade 100644 --- a/src/orchestrators/login.ts +++ b/src/orchestrators/login.ts @@ -10,10 +10,6 @@ export class LoginOrchestrator { LoginOrchestrator.handleRequests(req, res); }); - process.stdout.on('data', (data) => { - console.log(data); - }) - server.listen(51_039, 'localhost', () => { console.log('Opening CLI auth page...') open('http://localhost:3000/auth/cli'); From d37f10d5d413905fdf4e6319dfaa3036b62d724b Mon Sep 17 00:00:00 2001 From: kevinwang Date: Sun, 27 Jul 2025 19:37:42 -0400 Subject: [PATCH 3/5] feat: switched to hono server and added cors support --- package-lock.json | 23 ++++++++++ package.json | 6 ++- src/config.ts | 8 ++++ src/orchestrators/login.ts | 90 +++++++++++++++++++++++--------------- 4 files changed, 89 insertions(+), 38 deletions(-) create mode 100644 src/config.ts diff --git a/package-lock.json b/package-lock.json index e1e79a95..7e908d4c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "@codifycli/ink-form": "0.0.12", "@homebridge/node-pty-prebuilt-multiarch": "^0.12.0-beta.5", + "@hono/node-server": "^1.17.1", "@inkjs/ui": "^2", "@mischnic/json-sourcemap": "^0.1.1", "@oclif/core": "^4.0.8", @@ -24,6 +25,7 @@ "debug": "^4.3.4", "detect-indent": "^7.0.1", "diff": "^7.0.0", + "hono": "^4.8.9", "ink": "^5.1.0", "ink-big-text": "^2.0.0", "ink-gradient": "^3.0.0", @@ -1599,6 +1601,18 @@ "semver": "^7.6.3" } }, + "node_modules/@hono/node-server": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.17.1.tgz", + "integrity": "sha512-SY79W/C+2b1MyAzmIcV32Q47vO1b5XwLRwj8S9N6Jr5n1QCkIfAIH6umOSgqWZ4/v67hg6qq8Ha5vZonVidGsg==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -8989,6 +9003,15 @@ "tslib": "^2.0.3" } }, + "node_modules/hono": { + "version": "4.8.9", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.8.9.tgz", + "integrity": "sha512-ERIxkXMRhUxGV7nS/Af52+j2KL60B1eg+k6cPtgzrGughS+espS9KQ7QO0SMnevtmRlBfAcN0mf1jKtO6j/doA==", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, "node_modules/hosted-git-info": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", diff --git a/package.json b/package.json index 748e2247..8fed2a55 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "dependencies": { "@codifycli/ink-form": "0.0.12", "@homebridge/node-pty-prebuilt-multiarch": "^0.12.0-beta.5", + "@hono/node-server": "^1.17.1", "@inkjs/ui": "^2", "@mischnic/json-sourcemap": "^0.1.1", "@oclif/core": "^4.0.8", @@ -19,6 +20,7 @@ "debug": "^4.3.4", "detect-indent": "^7.0.1", "diff": "^7.0.0", + "hono": "^4.8.9", "ink": "^5.1.0", "ink-big-text": "^2.0.0", "ink-gradient": "^3.0.0", @@ -73,8 +75,8 @@ "strip-ansi": "^7.1.0", "tsx": "^4.7.3", "typescript": "5.3.3", - "vitest": "^2.1.6", - "uuid": "^10.0.0" + "uuid": "^10.0.0", + "vitest": "^2.1.6" }, "overrides": { "ink-form": { diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 00000000..e2f12a90 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,8 @@ +export const config = { + loginServerPort: 51_039, + corsAllowedOrigins: [ + 'https://codify-dashboard.com', + 'https://https://codify-dashboard.kevinwang5658.workers.dev', + 'http://localhost:3000' + ] +} diff --git a/src/orchestrators/login.ts b/src/orchestrators/login.ts index 7c5ddade..621aa9fa 100644 --- a/src/orchestrators/login.ts +++ b/src/orchestrators/login.ts @@ -1,51 +1,69 @@ +import { serve } from '@hono/node-server'; +import { Hono } from 'hono'; +import { cors } from 'hono/cors' +import { HTTPException } from 'hono/http-exception'; import * as fs from 'node:fs/promises'; -import { type IncomingMessage, ServerResponse, createServer } from 'node:http'; import * as os from 'node:os'; import path from 'node:path'; import open from 'open'; +import { config } from '../config.js'; +import { ajv } from '../utils/ajv.js'; + +const schema = { + type: 'object', + properties: { + accessToken: { + type: 'string', + }, + email: { + type: 'string', + }, + userId: { + type: 'string', + }, + expiry: { + type: 'string', + } + }, + additionalProperties: false, + required: ['accessToken', 'email', 'userId', 'expiry'], +} + +interface Credentials { + accessToken: string; + email: string; + userId: string; + expiry: string; +} + export class LoginOrchestrator { static async run(){ - const server = createServer((req, res) => { - LoginOrchestrator.handleRequests(req, res); - }); + const server = new Hono(); - server.listen(51_039, 'localhost', () => { + server.use('*', cors({ origin: config.corsAllowedOrigins })) + server.post('/', async (c) => { + const body = await c.req.json(); + if (!ajv.validate(schema, body)) { + throw new HTTPException(400, { message: ajv.errorsText() }) + } + + await LoginOrchestrator.saveCredentials(body as unknown as Credentials) + return c.text('Success', 200); + }); + + serve({ + fetch: server.fetch, + port: config.loginServerPort, + }, () => { console.log('Opening CLI auth page...') open('http://localhost:3000/auth/cli'); }) } - private static async handleRequests(req: IncomingMessage, res: ServerResponse) { - try { - if (req.method !== 'POST') { - res.writeHead(400, { 'Content-Type': 'application/json' }); - return; - } - - const json = await new Promise((resolve) => { - const buf = new Array() - req.on('data', (chunk) => { - buf.push(chunk); - }).on('end', () => { - const body = Buffer.concat(buf).toString(); - const json = JSON.parse(body); - resolve(json); - }).on('error', (err) => { - console.error(err); - }) - }); - - const credentialsPath = path.join(os.homedir(), '.codify', 'credentials.json'); - console.log(`Saving credentials to ${credentialsPath}`); - await fs.writeFile(credentialsPath, JSON.stringify(json)); - - res.writeHead(200, { 'Content-Type': 'application/json' }); - process.exit(0); - } catch (error) { - console.error(error); - res.writeHead(500, { 'Content-Type': 'application/json' }); - process.exit(1); - } + private static async saveCredentials(credentials: Credentials) { + const credentialsPath = path.join(os.homedir(), '.codify', 'credentials.json'); + console.log(`Saving credentials to ${credentialsPath}`); + await fs.writeFile(credentialsPath, JSON.stringify(credentials)); } } From 1c07716c5be5448ca810247530f91e1b08678f9b Mon Sep 17 00:00:00 2001 From: kevinwang Date: Sun, 27 Jul 2025 22:01:08 -0400 Subject: [PATCH 4/5] feat: switched to express --- package-lock.json | 655 +++++++++++++++++++++++++++++++++++-- package.json | 4 + src/orchestrators/login.ts | 29 +- 3 files changed, 653 insertions(+), 35 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7e908d4c..ddbaf141 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@codifycli/ink-form": "0.0.12", "@homebridge/node-pty-prebuilt-multiarch": "^0.12.0-beta.5", "@hono/node-server": "^1.17.1", + "@hono/node-ws": "^1.2.0", "@inkjs/ui": "^2", "@mischnic/json-sourcemap": "^0.1.1", "@oclif/core": "^4.0.8", @@ -22,9 +23,11 @@ "ajv-formats": "^3.0.1", "chalk": "^5.3.0", "codify-schemas": "^1.0.76", + "cors": "^2.8.5", "debug": "^4.3.4", "detect-indent": "^7.0.1", "diff": "^7.0.0", + "express": "^5.1.0", "hono": "^4.8.9", "ink": "^5.1.0", "ink-big-text": "^2.0.0", @@ -55,6 +58,7 @@ "@types/chalk": "^2.2.0", "@types/debug": "^4.1.12", "@types/diff": "^7.0.1", + "@types/express": "^5.0.3", "@types/jju": "^1.4.5", "@types/js-yaml": "^4.0.9", "@types/json5": "^2.2.0", @@ -1613,6 +1617,22 @@ "hono": "^4" } }, + "node_modules/@hono/node-ws": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@hono/node-ws/-/node-ws-1.2.0.tgz", + "integrity": "sha512-OBPQ8OSHBw29mj00wT/xGYtB6HY54j0fNSdVZ7gZM3TUeq0So11GXaWtFf1xWxQNfumKIsj0wRuLKWfVsO5GgQ==", + "license": "MIT", + "dependencies": { + "ws": "^8.17.0" + }, + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "@hono/node-server": "^1.11.1", + "hono": "^4.6.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -4301,6 +4321,17 @@ "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==" }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, "node_modules/@types/chalk": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/@types/chalk/-/chalk-2.2.4.tgz", @@ -4311,6 +4342,16 @@ "chalk": "*" } }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/cors": { "version": "2.8.19", "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", @@ -4341,6 +4382,31 @@ "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", "dev": true }, + "node_modules/@types/express": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.3.tgz", + "integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.7.tgz", + "integrity": "sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, "node_modules/@types/gradient-string": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/@types/gradient-string/-/gradient-string-1.1.6.tgz", @@ -4354,6 +4420,13 @@ "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==" }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/jju": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/@types/jju/-/jju-1.4.5.tgz", @@ -4383,6 +4456,13 @@ "json5": "*" } }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/mocha": { "version": "10.0.10", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", @@ -4422,6 +4502,20 @@ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==" }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/react": { "version": "18.3.12", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz", @@ -4437,6 +4531,29 @@ "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", "dev": true }, + "node_modules/@types/send": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", + "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", + "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, "node_modules/@types/strip-ansi": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/@types/strip-ansi/-/strip-ansi-5.2.1.tgz", @@ -5607,6 +5724,38 @@ "readable-stream": "^3.4.0" } }, + "node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/bowser": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", @@ -5705,6 +5854,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", @@ -5761,7 +5919,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", - "dev": true, "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" @@ -5774,7 +5931,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", - "dev": true, "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" @@ -6269,11 +6425,22 @@ "upper-case": "^2.0.2" } }, + "node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/content-type": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -6295,6 +6462,15 @@ "node": ">= 0.6" } }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, "node_modules/cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", @@ -6638,6 +6814,15 @@ "node": ">= 14" } }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/detect-indent": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-7.0.1.tgz", @@ -6717,7 +6902,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", @@ -6727,6 +6911,12 @@ "node": ">= 0.4" } }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, "node_modules/ejs": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", @@ -6746,6 +6936,15 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -6938,7 +7137,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "engines": { "node": ">= 0.4" } @@ -6947,7 +7145,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "engines": { "node": ">= 0.4" } @@ -6962,7 +7159,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, "dependencies": { "es-errors": "^1.3.0" }, @@ -7161,6 +7357,12 @@ "node": ">=6" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -8129,6 +8331,15 @@ "node": ">=0.10.0" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/expand-template": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", @@ -8146,6 +8357,91 @@ "node": ">=12.0.0" } }, + "node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -8364,6 +8660,23 @@ "node": ">=8" } }, + "node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -8435,6 +8748,24 @@ "node": ">= 14.17" } }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -8535,7 +8866,6 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", - "dev": true, "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", @@ -8567,7 +8897,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" @@ -8804,7 +9133,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -8950,7 +9278,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -9059,6 +9386,31 @@ "node": ">=4" } }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -9511,6 +9863,15 @@ "node": ">= 12" } }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/is-accessor-descriptor": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.1.tgz", @@ -9897,6 +10258,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -10509,11 +10876,19 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, "engines": { "node": ">= 0.4" } }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/memfs": { "version": "4.14.0", "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.14.0.tgz", @@ -10533,6 +10908,18 @@ "url": "https://github.com/sponsors/streamich" } }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -10865,7 +11252,6 @@ "version": "1.13.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -11054,6 +11440,18 @@ "node": ">=8" } }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -11282,6 +11680,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/pascal-case": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", @@ -11343,6 +11750,15 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -11511,6 +11927,19 @@ "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", "dev": true }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/proxy-agent": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", @@ -11595,6 +12024,21 @@ "node": ">=18" } }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -11640,6 +12084,42 @@ "safe-buffer": "^5.1.0" } }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -12209,6 +12689,22 @@ "fsevents": "~2.3.2" } }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/run-applescript": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", @@ -12316,8 +12812,7 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/scheduler": { "version": "0.23.2", @@ -12338,6 +12833,49 @@ "node": ">=10" } }, + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/send/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/sentence-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-3.0.4.tgz", @@ -12358,6 +12896,21 @@ "randombytes": "^2.1.0" } }, + "node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -12404,6 +12957,12 @@ "node": ">= 0.4" } }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -12517,7 +13076,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dev": true, "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", @@ -12536,7 +13094,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dev": true, "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" @@ -12552,7 +13109,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dev": true, "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -12570,7 +13126,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dev": true, "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -12951,6 +13506,15 @@ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", "dev": true }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/std-env": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.0.tgz", @@ -13365,6 +13929,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, "node_modules/tree-dump": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.2.tgz", @@ -13904,6 +14477,41 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/typed-array-buffer": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", @@ -14033,6 +14641,15 @@ "node": ">= 4.0.0" } }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/upper-case": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-2.0.2.tgz", diff --git a/package.json b/package.json index 8fed2a55..5d000e31 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "@codifycli/ink-form": "0.0.12", "@homebridge/node-pty-prebuilt-multiarch": "^0.12.0-beta.5", "@hono/node-server": "^1.17.1", + "@hono/node-ws": "^1.2.0", "@inkjs/ui": "^2", "@mischnic/json-sourcemap": "^0.1.1", "@oclif/core": "^4.0.8", @@ -17,9 +18,11 @@ "ajv-formats": "^3.0.1", "chalk": "^5.3.0", "codify-schemas": "^1.0.76", + "cors": "^2.8.5", "debug": "^4.3.4", "detect-indent": "^7.0.1", "diff": "^7.0.0", + "express": "^5.1.0", "hono": "^4.8.9", "ink": "^5.1.0", "ink-big-text": "^2.0.0", @@ -48,6 +51,7 @@ "@types/chalk": "^2.2.0", "@types/debug": "^4.1.12", "@types/diff": "^7.0.1", + "@types/express": "^5.0.3", "@types/jju": "^1.4.5", "@types/js-yaml": "^4.0.9", "@types/json5": "^2.2.0", diff --git a/src/orchestrators/login.ts b/src/orchestrators/login.ts index 621aa9fa..2bd4073f 100644 --- a/src/orchestrators/login.ts +++ b/src/orchestrators/login.ts @@ -1,7 +1,5 @@ -import { serve } from '@hono/node-server'; -import { Hono } from 'hono'; -import { cors } from 'hono/cors' -import { HTTPException } from 'hono/http-exception'; +import cors from 'cors'; +import express, { json } from 'express'; import * as fs from 'node:fs/promises'; import * as os from 'node:os'; import path from 'node:path'; @@ -39,23 +37,22 @@ interface Credentials { export class LoginOrchestrator { static async run(){ - const server = new Hono(); + const app = express(); - server.use('*', cors({ origin: config.corsAllowedOrigins })) - server.post('/', async (c) => { - const body = await c.req.json(); + app.use(cors({ origin: config.corsAllowedOrigins })) + app.use(json()) + + app.post('/', async (req, res) => { + const body = req.body as Credentials; if (!ajv.validate(schema, body)) { - throw new HTTPException(400, { message: ajv.errorsText() }) + return res.status(400).send({ message: ajv.errorsText() }) } - await LoginOrchestrator.saveCredentials(body as unknown as Credentials) - return c.text('Success', 200); + await LoginOrchestrator.saveCredentials(body) + return res.sendStatus(200); }); - - serve({ - fetch: server.fetch, - port: config.loginServerPort, - }, () => { + + app.listen(config.loginServerPort, () => { console.log('Opening CLI auth page...') open('http://localhost:3000/auth/cli'); }) From 91869b5dd6b172ae85b1800e3478cd7c44ec75da Mon Sep 17 00:00:00 2001 From: kevinwang Date: Mon, 28 Jul 2025 22:28:59 -0400 Subject: [PATCH 5/5] chore: refactored apply flow to use express --- src/config.ts | 2 + src/connect/apply.ts | 2 +- src/connect/http-route-handler.ts | 54 ++++++++ src/connect/server.ts | 127 ++++++++---------- .../{route-handler.ts => ws-route-handler.ts} | 2 + src/orchestrators/connect.ts | 87 ++---------- 6 files changed, 130 insertions(+), 144 deletions(-) create mode 100644 src/connect/http-route-handler.ts rename src/connect/{route-handler.ts => ws-route-handler.ts} (93%) diff --git a/src/config.ts b/src/config.ts index e2f12a90..715f0c3b 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,5 +1,7 @@ export const config = { loginServerPort: 51_039, + connectServerPort: 51_040, + corsAllowedOrigins: [ 'https://codify-dashboard.com', 'https://https://codify-dashboard.kevinwang5658.workers.dev', diff --git a/src/connect/apply.ts b/src/connect/apply.ts index 86898ad9..548741d5 100644 --- a/src/connect/apply.ts +++ b/src/connect/apply.ts @@ -10,7 +10,7 @@ import { WsServerManager } from './server.js'; export function connectApplyInitHandler(msg: any, initWs: WebSocket, manager: WsServerManager) { const sessionId = uuid(); - manager.startAdhocWsServer(sessionId, async (ws) => { + manager.addAdhocWsServer(sessionId, async (ws) => { console.log('connected apply ws'); const { config } = msg; diff --git a/src/connect/http-route-handler.ts b/src/connect/http-route-handler.ts new file mode 100644 index 00000000..0e42e02a --- /dev/null +++ b/src/connect/http-route-handler.ts @@ -0,0 +1,54 @@ +import { spawn } from '@homebridge/node-pty-prebuilt-multiarch'; +import { ResourceConfig } from 'codify-schemas'; +import { Router } from 'express'; +import * as fs from 'node:fs/promises'; +import * as os from 'node:os'; +import * as path from 'node:path'; +import { v4 as uuid } from 'uuid'; +import { WebSocket } from 'ws'; + +import { WsServerManager } from './server.js'; + +const router = Router({ + mergeParams: true, +}); + +router.post('/apply', (req, res) => { + const sessionId = uuid(); + const manager = WsServerManager.get(); + const config = req.body; + + manager.addAdhocWsServer(sessionId, async (ws: WebSocket) => wsHandler(ws, config)); + + return res.status(200).json({ sessionId }); +}); + +async function wsHandler(ws: WebSocket, config: ResourceConfig): Promise { + const tmpDir = await fs.mkdtemp(os.tmpdir()); + const filePath = path.join(tmpDir, 'codify.json'); + + await fs.writeFile(filePath, JSON.stringify(config)); + + const pty = spawn('zsh', ['-c', 'codify apply'], { + name: 'xterm-color', + cols: 80, + rows: 30, + cwd: process.env.HOME, + env: process.env + }); + + pty.onData((data) => { + ws.send(Buffer.from(data, 'utf8')); + }); + + ws.on('message', (message) => { + pty.write(message.toString('utf8')); + }) + + pty.onExit(({ exitCode, signal }) => { + console.log('pty exit', exitCode, signal); + ws.terminate(); + }) +} + +export default router; diff --git a/src/connect/server.ts b/src/connect/server.ts index d6555008..5a2821cb 100644 --- a/src/connect/server.ts +++ b/src/connect/server.ts @@ -1,8 +1,10 @@ import { IncomingMessage, Server, createServer } from 'node:http'; +import { Duplex } from 'node:stream'; import { v4 as uuid } from 'uuid'; import { WebSocket, WebSocketServer } from 'ws'; +import { config } from '../config.js'; -const DEFAULT_PORT = 51_040; +let instance: WsServerManager | undefined; export class WsServerManager { @@ -14,19 +16,27 @@ export class WsServerManager { private connectionSecret; - constructor(connectionSecret?: string) { - this.server = createServer(); + static init(server: Server, connectionSecret?: string): WsServerManager { + instance = new WsServerManager(server, connectionSecret); + return instance; + } + + static get(): WsServerManager { + if (!instance) { + throw new Error('You must call WsServerManager.init before using it'); + } + + return instance; + } + + private constructor(server: Server, connectionSecret?: string) { + this.server = server this.connectionSecret = connectionSecret; this.wsServerMap.set('default', this.createWssServer()); - this.initServer(); + this.server.on('upgrade', this.onUpgrade) } - listen(cb?: () => void, port?: number, ) { - this.port = port ?? DEFAULT_PORT - this.server.listen(this.port, 'localhost', cb); - } - setDefaultHandler(handler: (ws: WebSocket, manager: WsServerManager) => void): WsServerManager { const wss = this.createWssServer(); this.wsServerMap.set('default', wss); @@ -35,73 +45,50 @@ export class WsServerManager { return this; } - addAdditionalHandlers(path: string, handler: (ws: WebSocket) => void): WsServerManager { - this.handlerMap.set(path, () => { - const wss = this.addWebsocketServer(); - - }); - - return this; - } - - startAdhocWsServer(sessionId: string, handler: (ws: WebSocket, manager: WsServerManager) => void) { + addAdhocWsServer(sessionId: string, handler: (ws: WebSocket, manager: WsServerManager) => void) { this.wsServerMap.set(sessionId, this.createWssServer()); this.handlerMap.set(sessionId, handler); } - private addWebsocketServer(): string { - const key = uuid(); - - const wss = new WebSocketServer({ - noServer: true - }) - this.wsServerMap.set(key, wss); - - wss.on('close', () => { - this.wsServerMap.delete(key); - }) - - return key; + private onUpgrade = (request: IncomingMessage, socket: Duplex, head: Buffer): void => { + const { pathname } = new URL(request.url!, 'ws://localhost:51040') + + if (!this.validateOrigin(request.headers.origin!) + || this.validateConnectionSecret(request)) { + console.error('Unauthorized request from', request.headers.origin); + socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n') + socket.destroy(); + return; + } + + if (pathname === '/ws' && this.handlerMap.has('default')) { + const wss = this.wsServerMap.get('default'); + wss?.handleUpgrade(request, socket, head, (ws, request) => this.handlerMap.get('default')!(ws, this, request)); + return; + } + + const pathSections = pathname.split('/').filter(Boolean); + if ( + pathSections[0] === 'ws' + && pathSections[1] === 'session' + && pathSections[2] + && this.handlerMap.has(pathSections[2]) + ) { + const sessionId = pathSections[2]; + console.log('session found, upgrading', sessionId); + + const wss = this.wsServerMap.get(sessionId)!; + + wss.handleUpgrade(request, socket, head, (ws, request) => this.handlerMap.get(sessionId)!(ws, this, request)); + } } - private initServer() { - this.server.on('upgrade', (request, socket, head) => { - console.log('upgrade') - - const { pathname } = new URL(request.url!, 'ws://localhost:51040') - console.log('Pathname:', pathname) - - const code = request.headers['sec-websocket-protocol'] - if (this.connectionSecret && code !== this.connectionSecret) { - console.log('Auth failed'); - socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n') - socket.destroy() - return; - } - - if (pathname === '/' && this.handlerMap.has('default')) { - const wss = this.wsServerMap.get('default'); - wss?.handleUpgrade(request, socket, head, (ws, request) => this.handlerMap.get('default')!(ws, this, request)); - return; - } - - const pathSections = pathname.split('/').filter(Boolean); - console.log(pathSections); - console.log('available sessions', this.handlerMap) - - if (pathSections[0] === 'session' - && pathSections[1] - && this.handlerMap.has(pathSections[1]) - ) { - const sessionId = pathSections[1]; - console.log('session found, upgrading', sessionId); - - const wss = this.wsServerMap.get(sessionId)!; - - wss.handleUpgrade(request, socket, head, (ws, request) => this.handlerMap.get(sessionId)!(ws, this, request)); - return; - } - }) + private validateOrigin = (origin: string): boolean => + config.corsAllowedOrigins.includes(origin) + + private validateConnectionSecret = (request: IncomingMessage): boolean => { + const connectionSecret = request.headers['connection-secret'] as string; + return connectionSecret === this.connectionSecret; } private createWssServer(): WebSocketServer { diff --git a/src/connect/route-handler.ts b/src/connect/ws-route-handler.ts similarity index 93% rename from src/connect/route-handler.ts rename to src/connect/ws-route-handler.ts index 0529c724..c3d64ab7 100644 --- a/src/connect/route-handler.ts +++ b/src/connect/ws-route-handler.ts @@ -4,6 +4,8 @@ import { connectApplyInitHandler } from './apply.js'; import { WsServerManager } from './server.js'; export async function defaultWsHandler(ws: WebSocket, manager: WsServerManager) { + console.log('[WS] Connection opened'); + ws.on('message', (message) => { let msg; try { diff --git a/src/orchestrators/connect.ts b/src/orchestrators/connect.ts index 7b6b5684..526e0f02 100644 --- a/src/orchestrators/connect.ts +++ b/src/orchestrators/connect.ts @@ -1,90 +1,31 @@ -import { spawn } from '@homebridge/node-pty-prebuilt-multiarch'; +import cors from 'cors'; +import express, { json } from 'express'; import { randomBytes } from 'node:crypto'; import open from 'open'; import { WebSocket } from 'ws'; -import { defaultWsHandler } from '../connect/route-handler.js'; +import { config } from '../config.js'; +import HttpRouteHandler from '../connect/http-route-handler.js'; import { WsServerManager } from '../connect/server.js'; +import { defaultWsHandler } from '../connect/ws-route-handler.js'; export class ConnectOrchestrator { static async run() { const connectionSecret = ConnectOrchestrator.tokenGenerate() - console.log(connectionSecret) - - const server = new WsServerManager(connectionSecret) - .setDefaultHandler(defaultWsHandler) - .addAdditionalHandlers('/apply-logs', () => {}) - .addAdditionalHandlers('/import-logs', () => {}) - .addAdditionalHandlers('/terminal', () => {}) - - server.listen(() => { + const app = express(); + + app.use(cors({ origin: config.corsAllowedOrigins })) + app.use(json()) + app.use(HttpRouteHandler); + + const server = app.listen(config.connectServerPort, () => { open(`http://localhost:3000/connection/success?code=${connectionSecret}`) }); - } - - private static onConnection(ws: WebSocket) { - console.log('[WS] Connection opened'); - - ws.on('apply', (message) => { - let data; - try { - data = JSON.parse(message.toString('utf8')); - console.log(data); - } catch (error) { - console.error(error); - } - }); - - ws.on('close', () => { - - }); + const wsManager = WsServerManager.init(server, connectionSecret) + .setDefaultHandler(defaultWsHandler) } - /* - private static async onApply(ws: WebSocket, data: any) { - const { config } = data; - const tmpDir = await fs.mkdtemp(os.tmpdir()); - const filePath = path.join(tmpDir, 'codify.json'); - - await fs.writeFile(filePath, JSON.stringify(config)); - - const server = createServer() - const wss = new WebSocketServer({ - - }) - - server.on('upgrade', (request, socket, head) => { - wss.handleUpgrade(request, socket, head, (ws2) => { - const pty = spawn('zsh', ['-c', '"codify apply"'], { - name: 'xterm-color', - cols: 80, - rows: 30, - cwd: process.env.HOME, - env: process.env - }); - - pty.onData((data) => { - ws2.send(Buffer.from(data, 'utf8')); - }); - - ws2.on('message', (message) => { - pty.write(message.toString('utf8')); - }) - - pty.onExit((code) => { - ws2.close(code, code); - }) - }) - }); - - server.listen(2123, () => { - ws.emit('apply_Response', { - wsPass: 'pass', - }) - }) - } */ - private static tokenGenerate(length = 20): string { return Buffer.from(randomBytes(length)).toString('hex') }