Merge pull request #87 from NeuralNomadsAI/dev
Release 0.7.6 - Question tool fixes + Split test
This commit is contained in:
43
.github/workflows/release-ui.yml
vendored
Normal file
43
.github/workflows/release-ui.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch: {}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
NODE_VERSION: 20
|
||||
|
||||
jobs:
|
||||
release-ui:
|
||||
if: ${{ github.ref == 'refs/heads/main' }}
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: npm
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci --workspaces --include=optional
|
||||
|
||||
- name: Install Cloudflare worker deps
|
||||
run: npm ci
|
||||
working-directory: packages/cloudflare
|
||||
|
||||
- name: Build UI
|
||||
run: npm run build --workspace @codenomad/ui
|
||||
|
||||
- name: Publish UI zip + update manifest
|
||||
working-directory: packages/cloudflare
|
||||
env:
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
CODENOMAD_R2_BUCKET: ${{ vars.CODENOMAD_R2_BUCKET }}
|
||||
run: npm run release:ui
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -7,4 +7,9 @@ release/
|
||||
.electron-vite/
|
||||
out/
|
||||
.dir-locals.el
|
||||
.opencode/bashOutputs/
|
||||
.opencode/bashOutputs/
|
||||
|
||||
# Local runtime artifacts
|
||||
.codenomad/
|
||||
.tmp/
|
||||
packages/cloudflare/.wrangler/
|
||||
19
package-lock.json
generated
19
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "codenomad-workspace",
|
||||
"version": "0.7.5",
|
||||
"version": "0.7.6",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "codenomad-workspace",
|
||||
"version": "0.7.5",
|
||||
"version": "0.7.6",
|
||||
"dependencies": {
|
||||
"7zip-bin": "^5.2.0",
|
||||
"google-auth-library": "^10.5.0"
|
||||
@@ -1632,7 +1632,6 @@
|
||||
"version": "2.10.3",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
@@ -2271,7 +2270,6 @@
|
||||
},
|
||||
"node_modules/buffer-crc32": {
|
||||
"version": "0.2.13",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
@@ -3674,7 +3672,6 @@
|
||||
},
|
||||
"node_modules/fd-slicer": {
|
||||
"version": "1.1.0",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pend": "~1.2.0"
|
||||
@@ -5352,7 +5349,6 @@
|
||||
},
|
||||
"node_modules/pend": {
|
||||
"version": "1.2.0",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
@@ -7324,7 +7320,6 @@
|
||||
},
|
||||
"node_modules/yauzl": {
|
||||
"version": "2.10.0",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"buffer-crc32": "~0.2.3",
|
||||
@@ -7389,7 +7384,7 @@
|
||||
},
|
||||
"packages/electron-app": {
|
||||
"name": "@neuralnomads/codenomad-electron-app",
|
||||
"version": "0.7.5",
|
||||
"version": "0.7.6",
|
||||
"dependencies": {
|
||||
"@codenomad/ui": "file:../ui",
|
||||
"@neuralnomads/codenomad": "file:../server"
|
||||
@@ -7423,7 +7418,7 @@
|
||||
},
|
||||
"packages/server": {
|
||||
"name": "@neuralnomads/codenomad",
|
||||
"version": "0.7.5",
|
||||
"version": "0.7.6",
|
||||
"dependencies": {
|
||||
"@fastify/cors": "^8.5.0",
|
||||
"@fastify/reply-from": "^9.8.0",
|
||||
@@ -7433,12 +7428,14 @@
|
||||
"fuzzysort": "^2.0.4",
|
||||
"pino": "^9.4.0",
|
||||
"undici": "^6.19.8",
|
||||
"yauzl": "^2.10.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"bin": {
|
||||
"codenomad": "dist/bin.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/yauzl": "^2.10.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsx": "^4.20.6",
|
||||
@@ -7458,14 +7455,14 @@
|
||||
},
|
||||
"packages/tauri-app": {
|
||||
"name": "@codenomad/tauri-app",
|
||||
"version": "0.7.5",
|
||||
"version": "0.7.6",
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "^2.9.4"
|
||||
}
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@codenomad/ui",
|
||||
"version": "0.7.5",
|
||||
"version": "0.7.6",
|
||||
"dependencies": {
|
||||
"@git-diff-view/solid": "^0.0.8",
|
||||
"@kobalte/core": "0.13.11",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "codenomad-workspace",
|
||||
"version": "0.7.5",
|
||||
"version": "0.7.6",
|
||||
"private": true,
|
||||
"description": "CodeNomad monorepo workspace",
|
||||
"workspaces": {
|
||||
|
||||
1
packages/cloudflare/.gitignore
vendored
Normal file
1
packages/cloudflare/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
dist/
|
||||
1515
packages/cloudflare/package-lock.json
generated
Normal file
1515
packages/cloudflare/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
14
packages/cloudflare/package.json
Normal file
14
packages/cloudflare/package.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "@codenomad/ui-host-worker",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build:manifest": "node ./scripts/build-manifest.mjs",
|
||||
"release:ui": "node ./scripts/release-ui.mjs",
|
||||
"dev": "wrangler dev",
|
||||
"deploy": "wrangler deploy"
|
||||
},
|
||||
"devDependencies": {
|
||||
"wrangler": "^4.0.0"
|
||||
}
|
||||
}
|
||||
4
packages/cloudflare/release-config.json
Normal file
4
packages/cloudflare/release-config.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"minServerVersion": "0.7.5",
|
||||
"latestServerUrl": "https://github.com/NeuralNomadsAI/CodeNomad/releases/latest"
|
||||
}
|
||||
75
packages/cloudflare/scripts/build-manifest.mjs
Normal file
75
packages/cloudflare/scripts/build-manifest.mjs
Normal file
@@ -0,0 +1,75 @@
|
||||
import { createHash } from "crypto"
|
||||
import fs from "fs"
|
||||
import path from "path"
|
||||
import { fileURLToPath } from "url"
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = path.dirname(__filename)
|
||||
|
||||
const root = path.resolve(__dirname, "..")
|
||||
const repoRoot = path.resolve(root, "..", "..")
|
||||
|
||||
const releaseConfigPath = path.join(root, "release-config.json")
|
||||
const uiPackageJsonPath = path.join(repoRoot, "packages/ui/package.json")
|
||||
const serverPackageJsonPath = path.join(repoRoot, "packages/server/package.json")
|
||||
|
||||
const distDir = path.join(root, "dist")
|
||||
const manifestPath = path.join(distDir, "version.json")
|
||||
|
||||
const args = new Set(process.argv.slice(2))
|
||||
|
||||
function getArgValue(flag) {
|
||||
const idx = process.argv.indexOf(flag)
|
||||
if (idx === -1) return null
|
||||
return process.argv[idx + 1] ?? null
|
||||
}
|
||||
|
||||
const zipPath = getArgValue("--zip")
|
||||
|
||||
if (!zipPath) {
|
||||
console.error("Usage: node scripts/build-manifest.mjs --zip <path-to-ui-zip>")
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const resolvedZipPath = path.resolve(process.cwd(), zipPath)
|
||||
if (!fs.existsSync(resolvedZipPath)) {
|
||||
console.error(`Zip not found: ${resolvedZipPath}`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const releaseConfig = JSON.parse(fs.readFileSync(releaseConfigPath, "utf-8"))
|
||||
const uiPackageJson = JSON.parse(fs.readFileSync(uiPackageJsonPath, "utf-8"))
|
||||
const serverPackageJson = JSON.parse(fs.readFileSync(serverPackageJsonPath, "utf-8"))
|
||||
|
||||
const bucket = process.env.CODENOMAD_R2_BUCKET
|
||||
|
||||
if (!bucket) {
|
||||
console.error("Missing env var: CODENOMAD_R2_BUCKET")
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const uiVersion = uiPackageJson.version
|
||||
const serverVersion = serverPackageJson.version
|
||||
|
||||
if (!uiVersion || !serverVersion) {
|
||||
console.error("Missing version fields in package.json")
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const sha256 = createHash("sha256").update(fs.readFileSync(resolvedZipPath)).digest("hex")
|
||||
|
||||
const uiPackageURL = `https://download.codenomad.neuralnomads.ai/ui/ui-${uiVersion}.zip`
|
||||
|
||||
const manifest = {
|
||||
minServerVersion: releaseConfig.minServerVersion,
|
||||
latestUIVersion: uiVersion,
|
||||
uiPackageURL,
|
||||
sha256,
|
||||
latestServerVersion: serverVersion,
|
||||
latestServerUrl: releaseConfig.latestServerUrl,
|
||||
}
|
||||
|
||||
fs.mkdirSync(distDir, { recursive: true })
|
||||
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf-8")
|
||||
|
||||
console.log(`Wrote ${manifestPath}`)
|
||||
81
packages/cloudflare/scripts/release-ui.mjs
Normal file
81
packages/cloudflare/scripts/release-ui.mjs
Normal file
@@ -0,0 +1,81 @@
|
||||
import { execFileSync } from "child_process"
|
||||
import fs from "fs"
|
||||
import os from "os"
|
||||
import path from "path"
|
||||
import { fileURLToPath } from "url"
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = path.dirname(__filename)
|
||||
|
||||
const root = path.resolve(__dirname, "..")
|
||||
const repoRoot = path.resolve(root, "..", "..")
|
||||
|
||||
const r2Bucket = process.env.CODENOMAD_R2_BUCKET
|
||||
|
||||
if (!r2Bucket) {
|
||||
console.error("Missing env var: CODENOMAD_R2_BUCKET")
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const uiPackageJsonPath = path.join(repoRoot, "packages/ui/package.json")
|
||||
const uiPackageJson = JSON.parse(fs.readFileSync(uiPackageJsonPath, "utf-8"))
|
||||
const uiVersion = uiPackageJson.version
|
||||
|
||||
if (!uiVersion) {
|
||||
console.error("Missing packages/ui/package.json version")
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const uiBuildDir = path.join(repoRoot, "packages/ui/src/renderer/dist")
|
||||
if (!fs.existsSync(uiBuildDir)) {
|
||||
console.error(`Missing UI build dir: ${uiBuildDir}. Run UI build first.`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "codenomad-ui-release-"))
|
||||
const zipPath = path.join(tmpDir, `ui-${uiVersion}.zip`)
|
||||
|
||||
try {
|
||||
// Zip the CONTENTS of the dist dir (so index.html is at zip root).
|
||||
execFileSync("/usr/bin/zip", ["-q", "-r", zipPath, "."], { cwd: uiBuildDir, stdio: "inherit" })
|
||||
|
||||
// Upload to R2.
|
||||
const objectKey = `ui/ui-${uiVersion}.zip`
|
||||
console.log(`[release-ui] Uploading ${zipPath} -> r2://${r2Bucket}/${objectKey}`)
|
||||
|
||||
execFileSync(
|
||||
"npx",
|
||||
["wrangler", "r2", "object", "put", "--remote", `${r2Bucket}/${objectKey}`, "--file", zipPath],
|
||||
{ cwd: root, stdio: "inherit" },
|
||||
)
|
||||
|
||||
// Generate version.json into packages/cloudflare/dist
|
||||
console.log("[release-ui] Generating version.json")
|
||||
execFileSync(
|
||||
process.execPath,
|
||||
[path.join(root, "scripts/build-manifest.mjs"), "--zip", zipPath],
|
||||
{
|
||||
cwd: root,
|
||||
stdio: "inherit",
|
||||
env: {
|
||||
...process.env,
|
||||
CODENOMAD_R2_BUCKET: r2Bucket,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
console.log("[release-ui] Deploying worker")
|
||||
execFileSync("npx", ["wrangler", "deploy"], {
|
||||
cwd: root,
|
||||
stdio: "inherit",
|
||||
env: {
|
||||
...process.env,
|
||||
CLOUDFLARE_API_TOKEN: process.env.CLOUDFLARE_API_TOKEN,
|
||||
CLOUDFLARE_ACCOUNT_ID: process.env.CLOUDFLARE_ACCOUNT_ID,
|
||||
},
|
||||
})
|
||||
|
||||
console.log("[release-ui] Done")
|
||||
} finally {
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true })
|
||||
}
|
||||
29
packages/cloudflare/src/index.ts
Normal file
29
packages/cloudflare/src/index.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
export interface Env {
|
||||
ASSETS: { fetch: (request: Request) => Promise<Response> }
|
||||
}
|
||||
|
||||
function withHeader(response: Response, key: string, value: string): Response {
|
||||
const headers = new Headers(response.headers)
|
||||
headers.set(key, value)
|
||||
return new Response(response.body, {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers,
|
||||
})
|
||||
}
|
||||
|
||||
export default {
|
||||
async fetch(request: Request, env: Env): Promise<Response> {
|
||||
const url = new URL(request.url)
|
||||
|
||||
if (url.pathname === "/version.json") {
|
||||
const assetResponse = await env.ASSETS.fetch(request)
|
||||
|
||||
// Ensure this stays fresh; the server uses it on startup.
|
||||
const withCache = withHeader(assetResponse, "Cache-Control", "no-cache")
|
||||
return withHeader(withCache, "Content-Type", "application/json; charset=utf-8")
|
||||
}
|
||||
|
||||
return new Response("Not found", { status: 404 })
|
||||
},
|
||||
}
|
||||
15
packages/cloudflare/wrangler.toml
Normal file
15
packages/cloudflare/wrangler.toml
Normal file
@@ -0,0 +1,15 @@
|
||||
name = "codenomad-ui-host"
|
||||
main = "src/index.ts"
|
||||
compatibility_date = "2026-01-22"
|
||||
|
||||
# Custom domain for the manifest host.
|
||||
# Note: Custom domains apply to all paths on the hostname.
|
||||
[[routes]]
|
||||
pattern = "ui.codenomad.neuralnomads.ai"
|
||||
custom_domain = true
|
||||
|
||||
[assets]
|
||||
directory = "./dist"
|
||||
binding = "ASSETS"
|
||||
not_found_handling = "404-page"
|
||||
run_worker_first = ["/version.json"]
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@neuralnomads/codenomad-electron-app",
|
||||
"version": "0.7.5",
|
||||
"version": "0.7.6",
|
||||
"description": "CodeNomad - AI coding assistant",
|
||||
"author": {
|
||||
"name": "Neural Nomads",
|
||||
|
||||
774
packages/server/package-lock.json
generated
774
packages/server/package-lock.json
generated
@@ -1,20 +1,30 @@
|
||||
{
|
||||
"name": "@neuralnomads/codenomad",
|
||||
"version": "0.7.5",
|
||||
"version": "0.7.6",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@neuralnomads/codenomad",
|
||||
"version": "0.7.5",
|
||||
"version": "0.7.6",
|
||||
"dependencies": {
|
||||
"@fastify/cors": "^8.5.0",
|
||||
"@fastify/reply-from": "^9.8.0",
|
||||
"@fastify/static": "^7.0.4",
|
||||
"commander": "^12.1.0",
|
||||
"fastify": "^4.28.1",
|
||||
"fuzzysort": "^2.0.4",
|
||||
"pino": "^9.4.0",
|
||||
"undici": "^6.19.8",
|
||||
"yauzl": "^2.10.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"bin": {
|
||||
"codenomad": "dist/bin.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/yauzl": "^2.10.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsx": "^4.20.6",
|
||||
"typescript": "^5.6.3"
|
||||
@@ -475,6 +485,15 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@fastify/accept-negotiator": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@fastify/accept-negotiator/-/accept-negotiator-1.1.0.tgz",
|
||||
"integrity": "sha512-OIHZrb2ImZ7XG85HXOONLcJWGosv7sIvM2ifAPQVhg9Lv7qdmMBNVaai4QTdyuaqbKM5eO6sLSQOYI7wEQeCJQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@fastify/ajv-compiler": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-3.6.0.tgz",
|
||||
@@ -486,6 +505,15 @@
|
||||
"fast-uri": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@fastify/busboy": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz",
|
||||
"integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@fastify/cors": {
|
||||
"version": "8.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@fastify/cors/-/cors-8.5.0.tgz",
|
||||
@@ -520,6 +548,77 @@
|
||||
"fast-deep-equal": "^3.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@fastify/reply-from": {
|
||||
"version": "9.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@fastify/reply-from/-/reply-from-9.8.0.tgz",
|
||||
"integrity": "sha512-bPNVaFhEeNI0Lyl6404YZaPFokudCplidE3QoOcr78yOy6H9sYw97p5KPYvY/NJNUHfFtvxOaSAHnK+YSiv/Mg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@fastify/error": "^3.0.0",
|
||||
"end-of-stream": "^1.4.4",
|
||||
"fast-content-type-parse": "^1.1.0",
|
||||
"fast-querystring": "^1.0.0",
|
||||
"fastify-plugin": "^4.0.0",
|
||||
"toad-cache": "^3.7.0",
|
||||
"undici": "^5.19.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@fastify/reply-from/node_modules/undici": {
|
||||
"version": "5.29.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz",
|
||||
"integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@fastify/busboy": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@fastify/send": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@fastify/send/-/send-2.1.0.tgz",
|
||||
"integrity": "sha512-yNYiY6sDkexoJR0D8IDy3aRP3+L4wdqCpvx5WP+VtEU58sn7USmKynBzDQex5X42Zzvw2gNzzYgP90UfWShLFA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lukeed/ms": "^2.0.1",
|
||||
"escape-html": "~1.0.3",
|
||||
"fast-decode-uri-component": "^1.0.1",
|
||||
"http-errors": "2.0.0",
|
||||
"mime": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@fastify/static": {
|
||||
"version": "7.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@fastify/static/-/static-7.0.4.tgz",
|
||||
"integrity": "sha512-p2uKtaf8BMOZWLs6wu+Ihg7bWNBdjNgCwDza4MJtTqg+5ovKmcbgbR9Xs5/smZ1YISfzKOCNYmZV8LaCj+eJ1Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@fastify/accept-negotiator": "^1.0.0",
|
||||
"@fastify/send": "^2.0.0",
|
||||
"content-disposition": "^0.5.3",
|
||||
"fastify-plugin": "^4.0.0",
|
||||
"fastq": "^1.17.0",
|
||||
"glob": "^10.3.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"string-width": "^5.1.2",
|
||||
"string-width-cjs": "npm:string-width@^4.2.0",
|
||||
"strip-ansi": "^7.0.1",
|
||||
"strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
|
||||
"wrap-ansi": "^8.1.0",
|
||||
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/resolve-uri": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||
@@ -548,12 +647,31 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@lukeed/ms": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.2.tgz",
|
||||
"integrity": "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@pinojs/redact": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz",
|
||||
"integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@pkgjs/parseargs": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
||||
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsconfig/node10": {
|
||||
"version": "1.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz",
|
||||
@@ -593,6 +711,16 @@
|
||||
"undici-types": "~7.16.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/yauzl": {
|
||||
"version": "2.10.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
|
||||
"integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/abstract-logging": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz",
|
||||
@@ -674,6 +802,30 @@
|
||||
],
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
|
||||
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "6.2.3",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
|
||||
"integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/arg": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
|
||||
@@ -700,6 +852,48 @@
|
||||
"fastq": "^1.17.1"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer-crc32": {
|
||||
"version": "0.2.13",
|
||||
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
|
||||
"integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "12.1.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
|
||||
@@ -709,6 +903,18 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/content-disposition": {
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "5.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
|
||||
@@ -725,6 +931,48 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cross-env": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
|
||||
"integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cross-spawn": "^7.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"cross-env": "src/bin/cross-env.js",
|
||||
"cross-env-shell": "src/bin/cross-env-shell.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.14",
|
||||
"npm": ">=6",
|
||||
"yarn": ">=1"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
"which": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"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/diff": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
||||
@@ -735,6 +983,27 @@
|
||||
"node": ">=0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/eastasianwidth": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "9.2.2",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
|
||||
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/end-of-stream": {
|
||||
"version": "1.4.5",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
|
||||
"integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"once": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
|
||||
@@ -777,6 +1046,12 @@
|
||||
"@esbuild/win32-x64": "0.25.12"
|
||||
}
|
||||
},
|
||||
"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/fast-content-type-parse": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-1.1.0.tgz",
|
||||
@@ -891,6 +1166,15 @@
|
||||
"reusify": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/fd-slicer": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
|
||||
"integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pend": "~1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/find-my-way": {
|
||||
"version": "8.2.2",
|
||||
"resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-8.2.2.tgz",
|
||||
@@ -905,6 +1189,22 @@
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/foreground-child": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
|
||||
"integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"cross-spawn": "^7.0.6",
|
||||
"signal-exit": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
@@ -929,6 +1229,12 @@
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fuzzysort": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/fuzzysort/-/fuzzysort-2.0.4.tgz",
|
||||
"integrity": "sha512-Api1mJL+Ad7W7vnDZnWq5pGaXJjyencT+iKGia2PlHUcSsSzWwIQ3S1isiMpwpavjYtGd2FzhUIhnnhOULZgDw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/get-tsconfig": {
|
||||
"version": "4.13.0",
|
||||
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz",
|
||||
@@ -942,6 +1248,48 @@
|
||||
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "10.5.0",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
|
||||
"integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"foreground-child": "^3.1.0",
|
||||
"jackspeak": "^3.1.2",
|
||||
"minimatch": "^9.0.4",
|
||||
"minipass": "^7.1.2",
|
||||
"package-json-from-dist": "^1.0.0",
|
||||
"path-scurry": "^1.11.1"
|
||||
},
|
||||
"bin": {
|
||||
"glob": "dist/esm/bin.mjs"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"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/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ipaddr.js": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||
@@ -951,6 +1299,36 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/jackspeak": {
|
||||
"version": "3.4.3",
|
||||
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
|
||||
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
"@isaacs/cliui": "^8.0.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@pkgjs/parseargs": "^0.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/json-schema-ref-resolver": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-1.0.1.tgz",
|
||||
@@ -977,6 +1355,12 @@
|
||||
"set-cookie-parser": "^2.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "10.4.3",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
|
||||
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/make-error": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
|
||||
@@ -984,6 +1368,42 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/mime": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
|
||||
"integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"mime": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/minipass": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
|
||||
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/mnemonist": {
|
||||
"version": "0.39.6",
|
||||
"resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.39.6.tgz",
|
||||
@@ -1008,6 +1428,52 @@
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/package-json-from-dist": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
|
||||
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
|
||||
"license": "BlueOak-1.0.0"
|
||||
},
|
||||
"node_modules/path-key": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/path-scurry": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
|
||||
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
"lru-cache": "^10.2.0",
|
||||
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/pend": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
|
||||
"integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pino": {
|
||||
"version": "9.14.0",
|
||||
"resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz",
|
||||
@@ -1139,6 +1605,26 @@
|
||||
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/safe-regex2": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-3.1.0.tgz",
|
||||
@@ -1181,6 +1667,45 @@
|
||||
"integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"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",
|
||||
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"shebang-regex": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-regex": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/signal-exit": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
|
||||
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/sonic-boom": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz",
|
||||
@@ -1199,6 +1724,111 @@
|
||||
"node": ">= 10.x"
|
||||
}
|
||||
},
|
||||
"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/string-width": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
|
||||
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"eastasianwidth": "^0.2.0",
|
||||
"emoji-regex": "^9.2.2",
|
||||
"strip-ansi": "^7.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width-cjs": {
|
||||
"name": "string-width",
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width-cjs/node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width-cjs/node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/string-width-cjs/node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
|
||||
"integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi-cjs": {
|
||||
"name": "strip-ansi",
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi-cjs/node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/thread-stream": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz",
|
||||
@@ -1217,6 +1847,15 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"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/ts-node": {
|
||||
"version": "10.9.2",
|
||||
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
|
||||
@@ -1296,6 +1935,15 @@
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "6.23.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz",
|
||||
"integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.16.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
|
||||
@@ -1310,6 +1958,128 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"isexe": "^2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"node-which": "bin/node-which"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
|
||||
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^6.1.0",
|
||||
"string-width": "^5.0.1",
|
||||
"strip-ansi": "^7.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi-cjs": {
|
||||
"name": "wrap-ansi",
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi-cjs/node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi-cjs/node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/wrap-ansi-cjs/node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/yauzl": {
|
||||
"version": "2.10.0",
|
||||
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
|
||||
"integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"buffer-crc32": "~0.2.3",
|
||||
"fd-slicer": "~1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/yn": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@neuralnomads/codenomad",
|
||||
"version": "0.7.5",
|
||||
"version": "0.7.6",
|
||||
"description": "CodeNomad Server",
|
||||
"author": {
|
||||
"name": "Neural Nomads",
|
||||
@@ -32,9 +32,11 @@
|
||||
"fuzzysort": "^2.0.4",
|
||||
"pino": "^9.4.0",
|
||||
"undici": "^6.19.8",
|
||||
"yauzl": "^2.10.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/yauzl": "^2.10.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsx": "^4.20.6",
|
||||
|
||||
@@ -167,7 +167,6 @@ export type WorkspaceEventType =
|
||||
| "instance.dataChanged"
|
||||
| "instance.event"
|
||||
| "instance.eventStatus"
|
||||
| "app.releaseAvailable"
|
||||
|
||||
export type WorkspaceEventPayload =
|
||||
| { type: "workspace.created"; workspace: WorkspaceDescriptor }
|
||||
@@ -180,7 +179,6 @@ export type WorkspaceEventPayload =
|
||||
| { type: "instance.dataChanged"; instanceId: string; data: InstanceData }
|
||||
| { type: "instance.event"; instanceId: string; event: InstanceStreamEvent }
|
||||
| { type: "instance.eventStatus"; instanceId: string; status: InstanceStreamStatus; reason?: string }
|
||||
| { type: "app.releaseAvailable"; release: LatestReleaseInfo }
|
||||
|
||||
export interface NetworkAddress {
|
||||
ip: string
|
||||
@@ -198,6 +196,19 @@ export interface LatestReleaseInfo {
|
||||
notes?: string
|
||||
}
|
||||
|
||||
export interface UiMeta {
|
||||
version?: string
|
||||
source: "bundled" | "downloaded" | "previous" | "override" | "dev-proxy" | "missing"
|
||||
}
|
||||
|
||||
export interface SupportMeta {
|
||||
supported: boolean
|
||||
message?: string
|
||||
minServerVersion?: string
|
||||
latestServerVersion?: string
|
||||
latestServerUrl?: string
|
||||
}
|
||||
|
||||
export interface ServerMeta {
|
||||
/** Base URL clients should target for REST calls (useful for Electron embedding). */
|
||||
httpBaseUrl: string
|
||||
@@ -215,8 +226,9 @@ export interface ServerMeta {
|
||||
workspaceRoot: string
|
||||
/** Reachable addresses for this server, external first. */
|
||||
addresses: NetworkAddress[]
|
||||
/** Optional metadata about the most recent public release. */
|
||||
latestRelease?: LatestReleaseInfo
|
||||
serverVersion?: string
|
||||
ui?: UiMeta
|
||||
support?: SupportMeta
|
||||
}
|
||||
|
||||
export type BackgroundProcessStatus = "running" | "stopped" | "error"
|
||||
|
||||
@@ -29,7 +29,6 @@ export class EventBus extends EventEmitter {
|
||||
this.on("instance.dataChanged", handler)
|
||||
this.on("instance.event", handler)
|
||||
this.on("instance.eventStatus", handler)
|
||||
this.on("app.releaseAvailable", handler)
|
||||
return () => {
|
||||
this.off("workspace.created", handler)
|
||||
this.off("workspace.started", handler)
|
||||
@@ -41,7 +40,6 @@ export class EventBus extends EventEmitter {
|
||||
this.off("instance.dataChanged", handler)
|
||||
this.off("instance.event", handler)
|
||||
this.off("instance.eventStatus", handler)
|
||||
this.off("app.releaseAvailable", handler)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import { InstanceStore } from "./storage/instance-store"
|
||||
import { InstanceEventBridge } from "./workspaces/instance-events"
|
||||
import { createLogger } from "./logger"
|
||||
import { launchInBrowser } from "./launcher"
|
||||
import { startReleaseMonitor } from "./releases/release-monitor"
|
||||
import { resolveUi } from "./ui/remote-ui"
|
||||
import { AuthManager, BOOTSTRAP_TOKEN_STDOUT_PREFIX, DEFAULT_AUTH_USERNAME } from "./auth/manager"
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
@@ -37,6 +37,9 @@ interface CliOptions {
|
||||
logDestination?: string
|
||||
uiStaticDir: string
|
||||
uiDevServer?: string
|
||||
uiAutoUpdate: boolean
|
||||
uiNoUpdate: boolean
|
||||
uiManifestUrl?: string
|
||||
launch: boolean
|
||||
authUsername: string
|
||||
authPassword?: string
|
||||
@@ -66,6 +69,9 @@ function parseCliOptions(argv: string[]): CliOptions {
|
||||
new Option("--ui-dir <path>", "Directory containing the built UI bundle").env("CLI_UI_DIR").default(DEFAULT_UI_STATIC_DIR),
|
||||
)
|
||||
.addOption(new Option("--ui-dev-server <url>", "Proxy UI requests to a running dev server").env("CLI_UI_DEV_SERVER"))
|
||||
.addOption(new Option("--ui-no-update", "Disable remote UI updates").env("CLI_UI_NO_UPDATE").default(false))
|
||||
.addOption(new Option("--ui-auto-update <enabled>", "Enable remote UI updates (true|false)").env("CLI_UI_AUTO_UPDATE").default("true"))
|
||||
.addOption(new Option("--ui-manifest-url <url>", "Remote UI manifest URL").env("CLI_UI_MANIFEST_URL"))
|
||||
.addOption(new Option("--launch", "Launch the UI in a browser after start").env("CLI_LAUNCH").default(false))
|
||||
.addOption(
|
||||
new Option("--username <username>", "Username for server authentication")
|
||||
@@ -91,6 +97,9 @@ function parseCliOptions(argv: string[]): CliOptions {
|
||||
logDestination?: string
|
||||
uiDir: string
|
||||
uiDevServer?: string
|
||||
uiNoUpdate?: boolean
|
||||
uiAutoUpdate?: string
|
||||
uiManifestUrl?: string
|
||||
launch?: boolean
|
||||
username: string
|
||||
password?: string
|
||||
@@ -101,6 +110,9 @@ function parseCliOptions(argv: string[]): CliOptions {
|
||||
|
||||
const normalizedHost = resolveHost(parsed.host)
|
||||
|
||||
const autoUpdateString = (parsed.uiAutoUpdate ?? "true").trim().toLowerCase()
|
||||
const uiAutoUpdate = autoUpdateString === "1" || autoUpdateString === "true" || autoUpdateString === "yes"
|
||||
|
||||
return {
|
||||
port: parsed.port,
|
||||
host: normalizedHost,
|
||||
@@ -111,6 +123,9 @@ function parseCliOptions(argv: string[]): CliOptions {
|
||||
logDestination: parsed.logDestination,
|
||||
uiStaticDir: parsed.uiDir,
|
||||
uiDevServer: parsed.uiDevServer,
|
||||
uiAutoUpdate,
|
||||
uiNoUpdate: Boolean(parsed.uiNoUpdate),
|
||||
uiManifestUrl: parsed.uiManifestUrl,
|
||||
launch: Boolean(parsed.launch),
|
||||
authUsername: parsed.username,
|
||||
authPassword: parsed.password,
|
||||
@@ -141,6 +156,10 @@ function resolveHost(input: string | undefined): string {
|
||||
return trimmed
|
||||
}
|
||||
|
||||
function programHasArg(argv: string[], flag: string): boolean {
|
||||
return argv.includes(flag)
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const options = parseCliOptions(process.argv.slice(2))
|
||||
const logger = createLogger({ level: options.logLevel, destination: options.logDestination, component: "app" })
|
||||
@@ -205,19 +224,36 @@ async function main() {
|
||||
logger: logger.child({ component: "instance-events" }),
|
||||
})
|
||||
|
||||
const releaseMonitor = startReleaseMonitor({
|
||||
currentVersion: packageJson.version,
|
||||
logger: logger.child({ component: "release-monitor" }),
|
||||
onUpdate: (release) => {
|
||||
if (release) {
|
||||
serverMeta.latestRelease = release
|
||||
eventBus.publish({ type: "app.releaseAvailable", release })
|
||||
} else {
|
||||
delete serverMeta.latestRelease
|
||||
}
|
||||
},
|
||||
const uiDirEnvOverride = Boolean(process.env.CLI_UI_DIR)
|
||||
const uiDirCliOverride = programHasArg(process.argv.slice(2), "--ui-dir")
|
||||
const uiOverrideIsExplicit = uiDirEnvOverride || uiDirCliOverride
|
||||
const uiDirOverride = uiOverrideIsExplicit ? options.uiStaticDir : undefined
|
||||
|
||||
const autoUpdateEnabled = options.uiAutoUpdate && !options.uiNoUpdate
|
||||
|
||||
const uiResolution = await resolveUi({
|
||||
serverVersion: packageJson.version,
|
||||
bundledUiDir: DEFAULT_UI_STATIC_DIR,
|
||||
autoUpdate: autoUpdateEnabled,
|
||||
overrideUiDir: uiDirOverride,
|
||||
uiDevServerUrl: options.uiDevServer,
|
||||
manifestUrl: options.uiManifestUrl,
|
||||
logger: logger.child({ component: "ui" }),
|
||||
})
|
||||
|
||||
serverMeta.serverVersion = packageJson.version
|
||||
serverMeta.ui = {
|
||||
version: uiResolution.uiVersion,
|
||||
source: uiResolution.source,
|
||||
}
|
||||
serverMeta.support = {
|
||||
supported: uiResolution.supported,
|
||||
message: uiResolution.message,
|
||||
latestServerVersion: uiResolution.latestServerVersion,
|
||||
latestServerUrl: uiResolution.latestServerUrl,
|
||||
minServerVersion: uiResolution.minServerVersion,
|
||||
}
|
||||
|
||||
const server = createHttpServer({
|
||||
host: options.host,
|
||||
port: options.port,
|
||||
@@ -229,8 +265,8 @@ async function main() {
|
||||
serverMeta,
|
||||
instanceStore,
|
||||
authManager,
|
||||
uiStaticDir: options.uiStaticDir,
|
||||
uiDevServerUrl: options.uiDevServer,
|
||||
uiStaticDir: uiResolution.uiStaticDir ?? DEFAULT_UI_STATIC_DIR,
|
||||
uiDevServerUrl: uiResolution.uiDevServerUrl,
|
||||
logger,
|
||||
})
|
||||
|
||||
@@ -266,7 +302,7 @@ async function main() {
|
||||
logger.error({ err: error }, "Workspace manager shutdown failed")
|
||||
}
|
||||
|
||||
releaseMonitor.stop()
|
||||
// no-op: remote UI manifest replaces GitHub release monitor
|
||||
|
||||
logger.info("Exiting process")
|
||||
process.exit(0)
|
||||
|
||||
535
packages/server/src/ui/remote-ui.ts
Normal file
535
packages/server/src/ui/remote-ui.ts
Normal file
@@ -0,0 +1,535 @@
|
||||
import { createHash } from "crypto"
|
||||
import fs from "fs"
|
||||
import { promises as fsp } from "fs"
|
||||
import os from "os"
|
||||
import path from "path"
|
||||
import { Readable } from "stream"
|
||||
import { fetch } from "undici"
|
||||
import yauzl from "yauzl"
|
||||
import type { Logger } from "../logger"
|
||||
|
||||
export interface RemoteUiManifest {
|
||||
minServerVersion: string
|
||||
latestUIVersion: string
|
||||
uiPackageURL: string
|
||||
sha256: string
|
||||
latestServerVersion?: string
|
||||
latestServerUrl?: string
|
||||
}
|
||||
|
||||
export type UiSource = "bundled" | "downloaded" | "previous" | "override" | "dev-proxy" | "missing"
|
||||
|
||||
export interface UiResolution {
|
||||
uiStaticDir?: string
|
||||
uiDevServerUrl?: string
|
||||
source: UiSource
|
||||
uiVersion?: string
|
||||
supported: boolean
|
||||
message?: string
|
||||
latestServerVersion?: string
|
||||
latestServerUrl?: string
|
||||
minServerVersion?: string
|
||||
}
|
||||
|
||||
export interface RemoteUiOptions {
|
||||
serverVersion: string
|
||||
bundledUiDir: string
|
||||
autoUpdate: boolean
|
||||
overrideUiDir?: string
|
||||
uiDevServerUrl?: string
|
||||
manifestUrl?: string
|
||||
configDir?: string
|
||||
logger: Logger
|
||||
}
|
||||
|
||||
const DEFAULT_MANIFEST_URL = "https://ui.codenomad.neuralnomads.ai/version.json"
|
||||
|
||||
const MANIFEST_TIMEOUT_MS = 5_000
|
||||
const ZIP_TIMEOUT_MS = 30_000
|
||||
|
||||
export async function resolveUi(options: RemoteUiOptions): Promise<UiResolution> {
|
||||
const manifestUrl = options.manifestUrl ?? DEFAULT_MANIFEST_URL
|
||||
|
||||
if (options.uiDevServerUrl) {
|
||||
return {
|
||||
uiDevServerUrl: options.uiDevServerUrl,
|
||||
source: "dev-proxy",
|
||||
supported: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (options.overrideUiDir) {
|
||||
const resolved = await resolveStaticUiDir(options.overrideUiDir)
|
||||
return {
|
||||
uiStaticDir: resolved ?? options.overrideUiDir,
|
||||
source: "override",
|
||||
uiVersion: await readUiVersion(resolved ?? options.overrideUiDir),
|
||||
supported: true,
|
||||
}
|
||||
}
|
||||
|
||||
const uiRoot = resolveUiCacheRoot(options.configDir)
|
||||
const currentDir = path.join(uiRoot, "current")
|
||||
const previousDir = path.join(uiRoot, "previous")
|
||||
|
||||
if (!options.autoUpdate) {
|
||||
const local = await resolveStaticUiDir(currentDir)
|
||||
if (local) {
|
||||
return {
|
||||
uiStaticDir: local,
|
||||
source: "downloaded",
|
||||
uiVersion: await readUiVersion(local),
|
||||
supported: true,
|
||||
}
|
||||
}
|
||||
|
||||
const bundled = await resolveStaticUiDir(options.bundledUiDir)
|
||||
return {
|
||||
uiStaticDir: bundled ?? options.bundledUiDir,
|
||||
source: bundled ? "bundled" : "missing",
|
||||
uiVersion: bundled ? await readUiVersion(bundled) : undefined,
|
||||
supported: true,
|
||||
}
|
||||
}
|
||||
|
||||
let manifest: RemoteUiManifest | null = null
|
||||
try {
|
||||
manifest = await fetchManifest(manifestUrl, options.logger)
|
||||
} catch (error) {
|
||||
options.logger.debug({ err: error }, "Remote UI manifest unavailable; using cached/bundled UI")
|
||||
}
|
||||
|
||||
if (!manifest) {
|
||||
return await resolveFromCacheOrBundled({
|
||||
logger: options.logger,
|
||||
bundledUiDir: options.bundledUiDir,
|
||||
currentDir,
|
||||
previousDir,
|
||||
supported: true,
|
||||
})
|
||||
}
|
||||
|
||||
const supported = compareSemverCore(options.serverVersion, manifest.minServerVersion) >= 0
|
||||
if (!supported) {
|
||||
const message = "Upgrade App to use latest features"
|
||||
return await resolveFromCacheOrBundled({
|
||||
logger: options.logger,
|
||||
bundledUiDir: options.bundledUiDir,
|
||||
currentDir,
|
||||
previousDir,
|
||||
supported: false,
|
||||
message,
|
||||
latestServerVersion: manifest.latestServerVersion,
|
||||
latestServerUrl: manifest.latestServerUrl,
|
||||
minServerVersion: manifest.minServerVersion,
|
||||
})
|
||||
}
|
||||
|
||||
const currentVersion = await readUiVersion(currentDir)
|
||||
if (currentVersion && currentVersion === manifest.latestUIVersion) {
|
||||
const currentResolved = await resolveStaticUiDir(currentDir)
|
||||
if (currentResolved) {
|
||||
return {
|
||||
uiStaticDir: currentResolved,
|
||||
source: "downloaded",
|
||||
uiVersion: currentVersion,
|
||||
supported: true,
|
||||
latestServerVersion: manifest.latestServerVersion,
|
||||
latestServerUrl: manifest.latestServerUrl,
|
||||
minServerVersion: manifest.minServerVersion,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await installRemoteUi({
|
||||
manifest,
|
||||
uiRoot,
|
||||
currentDir,
|
||||
previousDir,
|
||||
logger: options.logger,
|
||||
})
|
||||
} catch (error) {
|
||||
options.logger.warn({ err: error }, "Failed to install remote UI; falling back")
|
||||
return await resolveFromCacheOrBundled({
|
||||
logger: options.logger,
|
||||
bundledUiDir: options.bundledUiDir,
|
||||
currentDir,
|
||||
previousDir,
|
||||
supported: true,
|
||||
latestServerVersion: manifest.latestServerVersion,
|
||||
latestServerUrl: manifest.latestServerUrl,
|
||||
minServerVersion: manifest.minServerVersion,
|
||||
})
|
||||
}
|
||||
|
||||
const installed = await resolveStaticUiDir(currentDir)
|
||||
if (installed) {
|
||||
return {
|
||||
uiStaticDir: installed,
|
||||
source: "downloaded",
|
||||
uiVersion: await readUiVersion(installed),
|
||||
supported: true,
|
||||
latestServerVersion: manifest.latestServerVersion,
|
||||
latestServerUrl: manifest.latestServerUrl,
|
||||
minServerVersion: manifest.minServerVersion,
|
||||
}
|
||||
}
|
||||
|
||||
return await resolveFromCacheOrBundled({
|
||||
logger: options.logger,
|
||||
bundledUiDir: options.bundledUiDir,
|
||||
currentDir,
|
||||
previousDir,
|
||||
supported: true,
|
||||
latestServerVersion: manifest.latestServerVersion,
|
||||
latestServerUrl: manifest.latestServerUrl,
|
||||
minServerVersion: manifest.minServerVersion,
|
||||
})
|
||||
}
|
||||
|
||||
function resolveUiCacheRoot(configDir?: string): string {
|
||||
if (configDir) {
|
||||
return path.join(configDir, "ui")
|
||||
}
|
||||
return path.join(os.homedir(), ".config", "codenomad", "ui")
|
||||
}
|
||||
|
||||
async function resolveFromCacheOrBundled(args: {
|
||||
logger: Logger
|
||||
bundledUiDir: string
|
||||
currentDir: string
|
||||
previousDir: string
|
||||
supported: boolean
|
||||
message?: string
|
||||
latestServerVersion?: string
|
||||
latestServerUrl?: string
|
||||
minServerVersion?: string
|
||||
}): Promise<UiResolution> {
|
||||
const currentResolved = await resolveStaticUiDir(args.currentDir)
|
||||
if (currentResolved) {
|
||||
return {
|
||||
uiStaticDir: currentResolved,
|
||||
source: "downloaded",
|
||||
uiVersion: await readUiVersion(currentResolved),
|
||||
supported: args.supported,
|
||||
message: args.message,
|
||||
latestServerVersion: args.latestServerVersion,
|
||||
latestServerUrl: args.latestServerUrl,
|
||||
minServerVersion: args.minServerVersion,
|
||||
}
|
||||
}
|
||||
|
||||
const previousResolved = await resolveStaticUiDir(args.previousDir)
|
||||
if (previousResolved) {
|
||||
return {
|
||||
uiStaticDir: previousResolved,
|
||||
source: "previous",
|
||||
uiVersion: await readUiVersion(previousResolved),
|
||||
supported: args.supported,
|
||||
message: args.message,
|
||||
latestServerVersion: args.latestServerVersion,
|
||||
latestServerUrl: args.latestServerUrl,
|
||||
minServerVersion: args.minServerVersion,
|
||||
}
|
||||
}
|
||||
|
||||
const bundledResolved = await resolveStaticUiDir(args.bundledUiDir)
|
||||
if (bundledResolved) {
|
||||
return {
|
||||
uiStaticDir: bundledResolved,
|
||||
source: "bundled",
|
||||
uiVersion: await readUiVersion(bundledResolved),
|
||||
supported: args.supported,
|
||||
message: args.message,
|
||||
latestServerVersion: args.latestServerVersion,
|
||||
latestServerUrl: args.latestServerUrl,
|
||||
minServerVersion: args.minServerVersion,
|
||||
}
|
||||
}
|
||||
|
||||
args.logger.warn({ bundledUiDir: args.bundledUiDir }, "No UI assets found")
|
||||
return {
|
||||
uiStaticDir: args.bundledUiDir,
|
||||
source: "missing",
|
||||
supported: args.supported,
|
||||
message: args.message,
|
||||
latestServerVersion: args.latestServerVersion,
|
||||
latestServerUrl: args.latestServerUrl,
|
||||
minServerVersion: args.minServerVersion,
|
||||
}
|
||||
}
|
||||
|
||||
async function resolveStaticUiDir(uiDir: string): Promise<string | null> {
|
||||
try {
|
||||
const indexPath = path.join(uiDir, "index.html")
|
||||
await fsp.access(indexPath, fs.constants.R_OK)
|
||||
return uiDir
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
interface UiVersionFile {
|
||||
uiVersion?: string
|
||||
version?: string
|
||||
}
|
||||
|
||||
async function readUiVersion(uiDir: string): Promise<string | undefined> {
|
||||
try {
|
||||
const content = await fsp.readFile(path.join(uiDir, "ui-version.json"), "utf-8")
|
||||
const parsed = JSON.parse(content) as UiVersionFile
|
||||
return parsed.uiVersion ?? parsed.version
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchManifest(url: string, logger: Logger): Promise<RemoteUiManifest> {
|
||||
const controller = new AbortController()
|
||||
const timeout = setTimeout(() => controller.abort(), MANIFEST_TIMEOUT_MS)
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
signal: controller.signal,
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"User-Agent": "CodeNomad-CLI",
|
||||
},
|
||||
})
|
||||
if (!response.ok) {
|
||||
throw new Error(`Manifest responded with ${response.status}`)
|
||||
}
|
||||
const json = (await response.json()) as RemoteUiManifest
|
||||
validateManifest(json)
|
||||
return json
|
||||
} catch (error) {
|
||||
logger.debug({ err: error, url }, "Failed to fetch remote UI manifest")
|
||||
throw error
|
||||
} finally {
|
||||
clearTimeout(timeout)
|
||||
}
|
||||
}
|
||||
|
||||
function validateManifest(manifest: RemoteUiManifest) {
|
||||
const required: Array<keyof RemoteUiManifest> = ["minServerVersion", "latestUIVersion", "uiPackageURL", "sha256"]
|
||||
for (const key of required) {
|
||||
const value = manifest[key]
|
||||
if (typeof value !== "string" || value.trim().length === 0) {
|
||||
throw new Error(`Manifest missing ${key}`)
|
||||
}
|
||||
}
|
||||
if (!/^https:\/\//i.test(manifest.uiPackageURL)) {
|
||||
throw new Error("uiPackageURL must be https")
|
||||
}
|
||||
if (!/^[a-f0-9]{64}$/i.test(manifest.sha256.trim())) {
|
||||
throw new Error("sha256 must be 64 hex chars")
|
||||
}
|
||||
}
|
||||
|
||||
async function installRemoteUi(args: {
|
||||
manifest: RemoteUiManifest
|
||||
uiRoot: string
|
||||
currentDir: string
|
||||
previousDir: string
|
||||
logger: Logger
|
||||
}) {
|
||||
await fsp.mkdir(args.uiRoot, { recursive: true })
|
||||
|
||||
const tmpDir = path.join(args.uiRoot, `tmp-${Date.now()}`)
|
||||
const zipPath = path.join(args.uiRoot, `ui-${args.manifest.latestUIVersion}.zip`)
|
||||
|
||||
try {
|
||||
await downloadFile(args.manifest.uiPackageURL, zipPath, args.logger)
|
||||
const digest = await sha256File(zipPath)
|
||||
if (digest.toLowerCase() !== args.manifest.sha256.toLowerCase()) {
|
||||
throw new Error(`sha256 mismatch for UI zip (expected ${args.manifest.sha256}, got ${digest})`)
|
||||
}
|
||||
|
||||
await extractZip(zipPath, tmpDir)
|
||||
|
||||
const indexPath = path.join(tmpDir, "index.html")
|
||||
if (!fs.existsSync(indexPath)) {
|
||||
throw new Error("Extracted UI missing index.html")
|
||||
}
|
||||
|
||||
await rotateDirs({ currentDir: args.currentDir, previousDir: args.previousDir, logger: args.logger })
|
||||
|
||||
fs.rmSync(args.currentDir, { recursive: true, force: true })
|
||||
fs.renameSync(tmpDir, args.currentDir)
|
||||
} finally {
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true })
|
||||
fs.rmSync(zipPath, { force: true })
|
||||
}
|
||||
}
|
||||
|
||||
async function rotateDirs(args: { currentDir: string; previousDir: string; logger: Logger }) {
|
||||
try {
|
||||
if (fs.existsSync(args.previousDir)) {
|
||||
fs.rmSync(args.previousDir, { recursive: true, force: true })
|
||||
}
|
||||
if (fs.existsSync(args.currentDir)) {
|
||||
fs.renameSync(args.currentDir, args.previousDir)
|
||||
}
|
||||
} catch (error) {
|
||||
args.logger.warn({ err: error }, "Failed to rotate UI cache directories")
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadFile(url: string, targetPath: string, logger: Logger) {
|
||||
const controller = new AbortController()
|
||||
const timeout = setTimeout(() => controller.abort(), ZIP_TIMEOUT_MS)
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
signal: controller.signal,
|
||||
headers: {
|
||||
Accept: "application/octet-stream",
|
||||
"User-Agent": "CodeNomad-CLI",
|
||||
},
|
||||
})
|
||||
if (!response.ok || !response.body) {
|
||||
throw new Error(`UI zip download failed with ${response.status}`)
|
||||
}
|
||||
|
||||
await fsp.mkdir(path.dirname(targetPath), { recursive: true })
|
||||
const fileStream = fs.createWriteStream(targetPath)
|
||||
|
||||
const body = response.body
|
||||
if (!body) {
|
||||
throw new Error("UI zip response missing body")
|
||||
}
|
||||
|
||||
const nodeStream = Readable.fromWeb(body as any)
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
nodeStream.pipe(fileStream)
|
||||
nodeStream.on("error", reject)
|
||||
fileStream.on("error", reject)
|
||||
fileStream.on("finish", () => resolve())
|
||||
})
|
||||
|
||||
logger.debug({ url, targetPath }, "Downloaded remote UI bundle")
|
||||
} finally {
|
||||
clearTimeout(timeout)
|
||||
}
|
||||
}
|
||||
|
||||
async function sha256File(filePath: string): Promise<string> {
|
||||
const hash = createHash("sha256")
|
||||
const stream = fs.createReadStream(filePath)
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
stream.on("data", (chunk) => hash.update(chunk))
|
||||
stream.on("error", reject)
|
||||
stream.on("end", () => resolve())
|
||||
})
|
||||
return hash.digest("hex")
|
||||
}
|
||||
|
||||
async function extractZip(zipPath: string, targetDir: string): Promise<void> {
|
||||
await fsp.mkdir(targetDir, { recursive: true })
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
yauzl.open(zipPath, { lazyEntries: true }, (openErr, zipfile) => {
|
||||
if (openErr || !zipfile) {
|
||||
reject(openErr ?? new Error("Unable to open zip"))
|
||||
return
|
||||
}
|
||||
|
||||
const root = path.resolve(targetDir)
|
||||
|
||||
const closeWithError = (error: unknown) => {
|
||||
try {
|
||||
zipfile.close()
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
reject(error)
|
||||
}
|
||||
|
||||
zipfile.readEntry()
|
||||
|
||||
zipfile.on("entry", (entry) => {
|
||||
// Normalize and guard against zip-slip.
|
||||
const entryPath = entry.fileName.replace(/\\/g, "/")
|
||||
|
||||
const segments = entryPath.split("/").filter(Boolean)
|
||||
if (segments.some((segment: string) => segment === "..") || path.isAbsolute(entryPath)) {
|
||||
closeWithError(new Error(`Invalid zip entry path: ${entry.fileName}`))
|
||||
return
|
||||
}
|
||||
|
||||
const destination = path.resolve(targetDir, entryPath)
|
||||
if (!destination.startsWith(root + path.sep) && destination !== root) {
|
||||
closeWithError(new Error(`Zip entry escapes target dir: ${entry.fileName}`))
|
||||
return
|
||||
}
|
||||
|
||||
const isDirectory = entry.fileName.endsWith("/")
|
||||
|
||||
if (isDirectory) {
|
||||
fsp
|
||||
.mkdir(destination, { recursive: true })
|
||||
.then(() => zipfile.readEntry())
|
||||
.catch((error) => closeWithError(error))
|
||||
return
|
||||
}
|
||||
|
||||
fsp
|
||||
.mkdir(path.dirname(destination), { recursive: true })
|
||||
.then(() => {
|
||||
zipfile.openReadStream(entry, (streamErr, readStream) => {
|
||||
if (streamErr || !readStream) {
|
||||
closeWithError(streamErr ?? new Error("Unable to read zip entry"))
|
||||
return
|
||||
}
|
||||
|
||||
const writeStream = fs.createWriteStream(destination)
|
||||
const cleanup = (error?: unknown) => {
|
||||
readStream.destroy()
|
||||
writeStream.destroy()
|
||||
if (error) {
|
||||
closeWithError(error)
|
||||
}
|
||||
}
|
||||
|
||||
readStream.on("error", cleanup)
|
||||
writeStream.on("error", cleanup)
|
||||
writeStream.on("finish", () => zipfile.readEntry())
|
||||
|
||||
readStream.pipe(writeStream)
|
||||
})
|
||||
})
|
||||
.catch((error) => closeWithError(error))
|
||||
})
|
||||
|
||||
zipfile.on("end", () => {
|
||||
zipfile.close()
|
||||
resolve()
|
||||
})
|
||||
|
||||
zipfile.on("error", (error) => closeWithError(error))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function compareSemverCore(a: string, b: string): number {
|
||||
const pa = parseSemverCore(a)
|
||||
const pb = parseSemverCore(b)
|
||||
if (pa.major !== pb.major) return pa.major > pb.major ? 1 : -1
|
||||
if (pa.minor !== pb.minor) return pa.minor > pb.minor ? 1 : -1
|
||||
if (pa.patch !== pb.patch) return pa.patch > pb.patch ? 1 : -1
|
||||
return 0
|
||||
}
|
||||
|
||||
function parseSemverCore(value: string): { major: number; minor: number; patch: number } {
|
||||
const core = value.trim().replace(/^v/i, "").split("-", 1)[0] ?? "0.0.0"
|
||||
const parts = core.split(".")
|
||||
const parsePart = (input: string | undefined) => {
|
||||
const n = Number.parseInt((input ?? "0").replace(/[^0-9]/g, ""), 10)
|
||||
return Number.isFinite(n) ? n : 0
|
||||
}
|
||||
return {
|
||||
major: parsePart(parts[0]),
|
||||
minor: parsePart(parts[1]),
|
||||
patch: parsePart(parts[2]),
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@codenomad/tauri-app",
|
||||
"version": "0.7.5",
|
||||
"version": "0.7.6",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "tauri dev",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@codenomad/ui",
|
||||
"version": "0.7.5",
|
||||
"version": "0.7.6",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -5,6 +5,7 @@ import AdvancedSettingsModal from "./advanced-settings-modal"
|
||||
import DirectoryBrowserDialog from "./directory-browser-dialog"
|
||||
import Kbd from "./kbd"
|
||||
import { openNativeFolderDialog, supportsNativeDialogs } from "../lib/native/native-functions"
|
||||
import VersionPill from "./version-pill"
|
||||
|
||||
const codeNomadLogo = new URL("../images/CodeNomad-Icon.png", import.meta.url).href
|
||||
|
||||
@@ -248,6 +249,9 @@ const FolderSelectionView: Component<FolderSelectionViewProps> = (props) => {
|
||||
</div>
|
||||
<h1 class="mb-2 text-3xl font-semibold text-primary">CodeNomad</h1>
|
||||
<p class="text-base text-secondary">Select a folder to start coding with AI</p>
|
||||
<div class="mt-2 flex justify-center">
|
||||
<VersionPill />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@@ -7,8 +7,7 @@ import {
|
||||
getPermissionQueue,
|
||||
getQuestionQueue,
|
||||
getQuestionEnqueuedAtForInstance,
|
||||
setActivePermissionIdForInstance,
|
||||
setActiveQuestionIdForInstance,
|
||||
sendPermissionResponse,
|
||||
} from "../stores/instances"
|
||||
import { ensureSessionParentExpanded, loadMessages, sessions as sessionStateSessions, setActiveSessionFromList } from "../stores/sessions"
|
||||
import { messageStoreBus } from "../stores/message-v2/bus"
|
||||
@@ -132,6 +131,45 @@ function resolveToolCallFromQuestion(instanceId: string, request: QuestionReques
|
||||
|
||||
const PermissionApprovalModal: Component<PermissionApprovalModalProps> = (props) => {
|
||||
const [loadingSession, setLoadingSession] = createSignal<string | null>(null)
|
||||
const [permissionSubmitting, setPermissionSubmitting] = createSignal<Set<string>>(new Set())
|
||||
const [permissionError, setPermissionError] = createSignal<Map<string, string>>(new Map())
|
||||
|
||||
const setPermissionBusy = (permissionId: string, busy: boolean) => {
|
||||
setPermissionSubmitting((prev) => {
|
||||
const next = new Set(prev)
|
||||
if (busy) next.add(permissionId)
|
||||
else next.delete(permissionId)
|
||||
return next
|
||||
})
|
||||
}
|
||||
|
||||
const setPermissionItemError = (permissionId: string, message: string | null) => {
|
||||
setPermissionError((prev) => {
|
||||
const next = new Map(prev)
|
||||
if (!message) next.delete(permissionId)
|
||||
else next.set(permissionId, message)
|
||||
return next
|
||||
})
|
||||
}
|
||||
|
||||
async function handlePermissionDecision(permission: PermissionRequestLike, response: "once" | "always" | "reject") {
|
||||
const permissionId = permission?.id
|
||||
if (!permissionId) return
|
||||
|
||||
if (permissionSubmitting().has(permissionId)) return
|
||||
|
||||
setPermissionBusy(permissionId, true)
|
||||
setPermissionItemError(permissionId, null)
|
||||
|
||||
try {
|
||||
const sessionId = getPermissionSessionId(permission) || ""
|
||||
await sendPermissionResponse(props.instanceId, sessionId, permissionId, response)
|
||||
} catch (error) {
|
||||
setPermissionItemError(permissionId, error instanceof Error ? error.message : "Unable to update permission")
|
||||
} finally {
|
||||
setPermissionBusy(permissionId, false)
|
||||
}
|
||||
}
|
||||
|
||||
const permissionQueue = createMemo(() => getPermissionQueue(props.instanceId))
|
||||
const questionQueue = createMemo(() => getQuestionQueue(props.instanceId))
|
||||
@@ -265,19 +303,10 @@ const PermissionApprovalModal: Component<PermissionApprovalModalProps> = (props)
|
||||
return count === 1 ? "1 question" : `${count} questions`
|
||||
}
|
||||
|
||||
const handleActivate = () => {
|
||||
if (item.kind === "permission") {
|
||||
setActivePermissionIdForInstance(props.instanceId, item.id)
|
||||
} else {
|
||||
setActiveQuestionIdForInstance(props.instanceId, item.id)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
class={`permission-center-item${isActive() ? " permission-center-item-active" : ""}`}
|
||||
role="listitem"
|
||||
onClick={handleActivate}
|
||||
>
|
||||
<div class="permission-center-item-header">
|
||||
<div class="permission-center-item-heading">
|
||||
@@ -315,17 +344,52 @@ const PermissionApprovalModal: Component<PermissionApprovalModalProps> = (props)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Show
|
||||
when={resolved()}
|
||||
fallback={
|
||||
<div class="permission-center-fallback">
|
||||
<div class="permission-center-fallback-title">
|
||||
<code>{primaryTitle()}</code>
|
||||
<Show
|
||||
when={resolved()}
|
||||
fallback={
|
||||
<div class="permission-center-fallback">
|
||||
<div class="permission-center-fallback-title">
|
||||
<code>{primaryTitle()}</code>
|
||||
</div>
|
||||
<Show when={item.kind === "permission"}>
|
||||
<div class="tool-call-permission-actions">
|
||||
<div class="tool-call-permission-buttons">
|
||||
<button
|
||||
type="button"
|
||||
class="tool-call-permission-button"
|
||||
disabled={permissionSubmitting().has(item.id)}
|
||||
onClick={() => void handlePermissionDecision(item.payload as PermissionRequestLike, "once")}
|
||||
>
|
||||
Allow Once
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="tool-call-permission-button"
|
||||
disabled={permissionSubmitting().has(item.id)}
|
||||
onClick={() => void handlePermissionDecision(item.payload as PermissionRequestLike, "always")}
|
||||
>
|
||||
Always Allow
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="tool-call-permission-button"
|
||||
disabled={permissionSubmitting().has(item.id)}
|
||||
onClick={() => void handlePermissionDecision(item.payload as PermissionRequestLike, "reject")}
|
||||
>
|
||||
Deny
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<Show when={permissionError().get(item.id)}>
|
||||
{(err) => <div class="tool-call-permission-error">{err()}</div>}
|
||||
</Show>
|
||||
</Show>
|
||||
<Show when={item.kind !== "permission"}>
|
||||
<div class="permission-center-fallback-hint">Load session for more information.</div>
|
||||
</Show>
|
||||
</div>
|
||||
<div class="permission-center-fallback-hint">Load session for more information.</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
}
|
||||
>
|
||||
{(data) => (
|
||||
<ToolCall
|
||||
toolCall={data().toolPart}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useGlobalCache } from "../lib/hooks/use-global-cache"
|
||||
import { useConfig } from "../stores/preferences"
|
||||
import type { DiffViewMode } from "../stores/preferences"
|
||||
import { activeInterruption, sendPermissionResponse, sendQuestionReject, sendQuestionReply } from "../stores/instances"
|
||||
import type { PermissionRequestLike } from "../types/permission"
|
||||
import { getPermissionDisplayTitle, getPermissionKind, getPermissionSessionId } from "../types/permission"
|
||||
import type { QuestionRequest } from "@opencode-ai/sdk/v2"
|
||||
import type { TextPart, RenderCache } from "../types/message"
|
||||
@@ -205,8 +206,9 @@ function QuestionToolBlock(props: QuestionToolBlockProps) {
|
||||
|
||||
const toggleFromCustomInput = (questionIndex: number, input: HTMLInputElement | null) => {
|
||||
if (!props.active()) return
|
||||
const value = input?.value?.trim() ?? ""
|
||||
if (!value) return
|
||||
const rawValue = input?.value ?? ""
|
||||
const value = rawValue
|
||||
if (value.trim().length === 0) return
|
||||
|
||||
const info = questions()[questionIndex]
|
||||
const multi = info?.multiple === true
|
||||
@@ -229,12 +231,13 @@ function QuestionToolBlock(props: QuestionToolBlockProps) {
|
||||
const handleCustomTyping = (questionIndex: number, input: HTMLInputElement) => {
|
||||
if (!props.active()) return
|
||||
|
||||
const value = input.value.trim()
|
||||
const value = input.value
|
||||
const trimmed = value.trim()
|
||||
const info = questions()[questionIndex]
|
||||
const multi = info?.multiple === true
|
||||
|
||||
if (!multi) {
|
||||
updateAnswer(questionIndex, value ? [value] : [])
|
||||
updateAnswer(questionIndex, trimmed.length > 0 ? [value] : [])
|
||||
return
|
||||
}
|
||||
|
||||
@@ -244,10 +247,11 @@ function QuestionToolBlock(props: QuestionToolBlockProps) {
|
||||
|
||||
let next = existing.filter((item) => item !== last)
|
||||
|
||||
if (value) {
|
||||
if (!optionLabels.has(value) && !next.includes(value)) {
|
||||
if (trimmed.length > 0) {
|
||||
// Only treat it as custom if it doesn't match an existing option label.
|
||||
if (!optionLabels.has(trimmed) && !next.includes(value)) {
|
||||
next = [...next, value]
|
||||
} else if (optionLabels.has(value)) {
|
||||
} else if (optionLabels.has(trimmed)) {
|
||||
// If they typed an existing option label, don't treat it as custom.
|
||||
} else if (!next.includes(value)) {
|
||||
next = [...next, value]
|
||||
@@ -859,15 +863,17 @@ export default function ToolCall(props: ToolCallProps) {
|
||||
const activeKey = activePermissionKey()
|
||||
if (!activeKey) return
|
||||
const handler = (event: KeyboardEvent) => {
|
||||
const permission = permissionDetails()
|
||||
if (!permission || !isPermissionActive()) return
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault()
|
||||
handlePermissionResponse("once")
|
||||
void handlePermissionResponse(permission, "once")
|
||||
} else if (event.key === "a" || event.key === "A") {
|
||||
event.preventDefault()
|
||||
handlePermissionResponse("always")
|
||||
void handlePermissionResponse(permission, "always")
|
||||
} else if (event.key === "d" || event.key === "D") {
|
||||
event.preventDefault()
|
||||
handlePermissionResponse("reject")
|
||||
void handlePermissionResponse(permission, "reject")
|
||||
}
|
||||
}
|
||||
document.addEventListener("keydown", handler)
|
||||
@@ -894,7 +900,10 @@ export default function ToolCall(props: ToolCallProps) {
|
||||
return
|
||||
}
|
||||
const answers = (questionDraftAnswers()[request.id] ?? []).map((x) => (Array.isArray(x) ? x : []))
|
||||
const normalized = request.questions.map((_, index) => answers[index] ?? [])
|
||||
const normalized = request.questions.map((_, index) => {
|
||||
const row = answers[index] ?? []
|
||||
return row.map((value) => value.trim()).filter((value) => value.length > 0)
|
||||
})
|
||||
if (normalized.some((item) => (item?.length ?? 0) === 0)) {
|
||||
setQuestionError("Please answer all questions before submitting.")
|
||||
return
|
||||
@@ -1240,11 +1249,8 @@ export default function ToolCall(props: ToolCallProps) {
|
||||
return renderer().renderBody(rendererContext)
|
||||
}
|
||||
|
||||
async function handlePermissionResponse(response: "once" | "always" | "reject") {
|
||||
const permission = permissionDetails()
|
||||
if (!permission || !isPermissionActive()) {
|
||||
return
|
||||
}
|
||||
async function handlePermissionResponse(permission: PermissionRequestLike, response: "once" | "always" | "reject") {
|
||||
if (!permission) return
|
||||
setPermissionSubmitting(true)
|
||||
setPermissionError(null)
|
||||
try {
|
||||
@@ -1310,37 +1316,37 @@ export default function ToolCall(props: ToolCallProps) {
|
||||
</div>
|
||||
)}
|
||||
</Show>
|
||||
<Show
|
||||
when={active}
|
||||
fallback={<p class="tool-call-permission-queued-text">Waiting for earlier permission responses.</p>}
|
||||
>
|
||||
<div class="tool-call-permission-actions">
|
||||
<div class="tool-call-permission-buttons">
|
||||
<button
|
||||
type="button"
|
||||
class="tool-call-permission-button"
|
||||
disabled={permissionSubmitting()}
|
||||
onClick={() => handlePermissionResponse("once")}
|
||||
>
|
||||
Allow Once
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="tool-call-permission-button"
|
||||
disabled={permissionSubmitting()}
|
||||
onClick={() => handlePermissionResponse("always")}
|
||||
>
|
||||
Always Allow
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="tool-call-permission-button"
|
||||
disabled={permissionSubmitting()}
|
||||
onClick={() => handlePermissionResponse("reject")}
|
||||
>
|
||||
Deny
|
||||
</button>
|
||||
</div>
|
||||
<Show when={!active}>
|
||||
<p class="tool-call-permission-queued-text">Waiting for earlier permission responses.</p>
|
||||
</Show>
|
||||
<div class="tool-call-permission-actions">
|
||||
<div class="tool-call-permission-buttons">
|
||||
<button
|
||||
type="button"
|
||||
class="tool-call-permission-button"
|
||||
disabled={permissionSubmitting()}
|
||||
onClick={() => void handlePermissionResponse(permission, "once")}
|
||||
>
|
||||
Allow Once
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="tool-call-permission-button"
|
||||
disabled={permissionSubmitting()}
|
||||
onClick={() => void handlePermissionResponse(permission, "always")}
|
||||
>
|
||||
Always Allow
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="tool-call-permission-button"
|
||||
disabled={permissionSubmitting()}
|
||||
onClick={() => void handlePermissionResponse(permission, "reject")}
|
||||
>
|
||||
Deny
|
||||
</button>
|
||||
</div>
|
||||
<Show when={active}>
|
||||
<div class="tool-call-permission-shortcuts">
|
||||
<kbd class="kbd">Enter</kbd>
|
||||
<span>Allow once</span>
|
||||
@@ -1349,10 +1355,10 @@ export default function ToolCall(props: ToolCallProps) {
|
||||
<kbd class="kbd">D</kbd>
|
||||
<span>Deny</span>
|
||||
</div>
|
||||
</div>
|
||||
<Show when={permissionError()}>
|
||||
<div class="tool-call-permission-error">{permissionError()}</div>
|
||||
</Show>
|
||||
</div>
|
||||
<Show when={permissionError()}>
|
||||
<div class="tool-call-permission-error">{permissionError()}</div>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
38
packages/ui/src/components/version-pill.tsx
Normal file
38
packages/ui/src/components/version-pill.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import { Show, createEffect, createSignal } from "solid-js"
|
||||
import type { ServerMeta } from "../../../server/src/api-types"
|
||||
import { getServerMeta } from "../lib/server-meta"
|
||||
|
||||
export default function VersionPill() {
|
||||
const [meta, setMeta] = createSignal<ServerMeta | null>(null)
|
||||
|
||||
createEffect(() => {
|
||||
void getServerMeta()
|
||||
.then((result) => setMeta(result))
|
||||
.catch(() => setMeta(null))
|
||||
})
|
||||
|
||||
const serverVersion = () => meta()?.serverVersion
|
||||
const uiVersion = () => meta()?.ui?.version
|
||||
const uiSource = () => meta()?.ui?.source
|
||||
|
||||
return (
|
||||
<Show when={serverVersion() || uiVersion() || uiSource()}>
|
||||
<div class="text-[11px] text-muted whitespace-nowrap">
|
||||
<Show when={serverVersion()}>
|
||||
{(v) => <span>App {v()}</span>}
|
||||
</Show>
|
||||
<Show when={uiVersion() || uiSource()}>
|
||||
<>
|
||||
<Show when={serverVersion()}>
|
||||
<span class="mx-2">·</span>
|
||||
</Show>
|
||||
<span>
|
||||
UI{uiVersion() ? ` ${uiVersion()}` : ""}
|
||||
<Show when={uiSource()}>{(s) => <span class="opacity-70"> ({s()})</span>}</Show>
|
||||
</span>
|
||||
</>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
@@ -1,25 +1,24 @@
|
||||
import { createEffect, createSignal } from "solid-js"
|
||||
import type { LatestReleaseInfo, WorkspaceEventPayload } from "../../../server/src/api-types"
|
||||
import type { SupportMeta } from "../../../server/src/api-types"
|
||||
import { getServerMeta } from "../lib/server-meta"
|
||||
import { serverEvents } from "../lib/server-events"
|
||||
import { showToastNotification, ToastHandle } from "../lib/notifications"
|
||||
import { getLogger } from "../lib/logger"
|
||||
import { hasInstances, showFolderSelection } from "./ui"
|
||||
|
||||
const log = getLogger("actions")
|
||||
|
||||
const [availableRelease, setAvailableRelease] = createSignal<LatestReleaseInfo | null>(null)
|
||||
const [supportInfo, setSupportInfo] = createSignal<SupportMeta | null>(null)
|
||||
|
||||
let initialized = false
|
||||
let visibilityEffectInitialized = false
|
||||
let activeToast: ToastHandle | null = null
|
||||
let activeToastVersion: string | null = null
|
||||
let activeToastKey: string | null = null
|
||||
|
||||
function dismissActiveToast() {
|
||||
if (activeToast) {
|
||||
activeToast.dismiss()
|
||||
activeToast = null
|
||||
activeToastVersion = null
|
||||
activeToastKey = null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,28 +29,34 @@ function ensureVisibilityEffect() {
|
||||
visibilityEffectInitialized = true
|
||||
|
||||
createEffect(() => {
|
||||
const release = availableRelease()
|
||||
const shouldShow = Boolean(release) && (!hasInstances() || showFolderSelection())
|
||||
const support = supportInfo()
|
||||
const shouldShow = Boolean(support && support.supported === false) && (!hasInstances() || showFolderSelection())
|
||||
|
||||
if (!shouldShow || !release) {
|
||||
if (!shouldShow || !support || support.supported !== false) {
|
||||
dismissActiveToast()
|
||||
return
|
||||
}
|
||||
|
||||
if (!activeToast || activeToastVersion !== release.version) {
|
||||
const key = `${support.minServerVersion ?? "unknown"}:${support.latestServerVersion ?? "unknown"}`
|
||||
|
||||
if (!activeToast || activeToastKey !== key) {
|
||||
dismissActiveToast()
|
||||
activeToast = showToastNotification({
|
||||
title: `CodeNomad ${release.version}`,
|
||||
message: release.channel === "dev" ? "Dev release build available." : "New stable build on GitHub.",
|
||||
title: support.message ?? "Upgrade required",
|
||||
message: support.latestServerVersion
|
||||
? `Update to CodeNomad ${support.latestServerVersion} to use the latest UI.`
|
||||
: "Update CodeNomad to use the latest UI.",
|
||||
variant: "info",
|
||||
duration: Number.POSITIVE_INFINITY,
|
||||
position: "bottom-right",
|
||||
action: {
|
||||
label: "View release",
|
||||
href: release.url,
|
||||
},
|
||||
action: support.latestServerUrl
|
||||
? {
|
||||
label: "Get update",
|
||||
href: support.latestServerUrl,
|
||||
}
|
||||
: undefined,
|
||||
})
|
||||
activeToastVersion = release.version
|
||||
activeToastKey = key
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -64,32 +69,17 @@ export function initReleaseNotifications() {
|
||||
|
||||
ensureVisibilityEffect()
|
||||
void refreshFromMeta()
|
||||
|
||||
serverEvents.on("app.releaseAvailable", (event) => {
|
||||
const typedEvent = event as Extract<WorkspaceEventPayload, { type: "app.releaseAvailable" }>
|
||||
applyRelease(typedEvent.release)
|
||||
})
|
||||
}
|
||||
|
||||
async function refreshFromMeta() {
|
||||
try {
|
||||
const meta = await getServerMeta(true)
|
||||
if (meta.latestRelease) {
|
||||
applyRelease(meta.latestRelease)
|
||||
}
|
||||
setSupportInfo(meta.support ?? null)
|
||||
} catch (error) {
|
||||
log.warn("Unable to load server metadata for release info", error)
|
||||
log.warn("Unable to load server metadata for support info", error)
|
||||
}
|
||||
}
|
||||
|
||||
function applyRelease(release: LatestReleaseInfo | null | undefined) {
|
||||
if (!release) {
|
||||
setAvailableRelease(null)
|
||||
return
|
||||
}
|
||||
setAvailableRelease(release)
|
||||
}
|
||||
|
||||
export function useAvailableRelease() {
|
||||
return availableRelease
|
||||
export function useSupportInfo() {
|
||||
return supportInfo
|
||||
}
|
||||
|
||||
@@ -1,10 +1,26 @@
|
||||
import fs from "fs"
|
||||
import { defineConfig } from "vite"
|
||||
import solid from "vite-plugin-solid"
|
||||
import { resolve } from "path"
|
||||
|
||||
const uiPackageJson = JSON.parse(fs.readFileSync(resolve(__dirname, "package.json"), "utf-8")) as { version?: string }
|
||||
const uiVersion = uiPackageJson.version ?? "0.0.0"
|
||||
|
||||
export default defineConfig({
|
||||
root: "./src/renderer",
|
||||
plugins: [solid()],
|
||||
plugins: [
|
||||
solid(),
|
||||
{
|
||||
name: "emit-ui-version",
|
||||
generateBundle() {
|
||||
this.emitFile({
|
||||
type: "asset",
|
||||
fileName: "ui-version.json",
|
||||
source: JSON.stringify({ uiVersion }, null, 2),
|
||||
})
|
||||
},
|
||||
},
|
||||
],
|
||||
css: {
|
||||
postcss: "./postcss.config.js",
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user