Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7079c236ab | ||
|
|
b5c6beafff | ||
|
|
a34566651a | ||
|
|
4628e67e5c | ||
|
|
578fa28461 | ||
|
|
9e6154b0de | ||
|
|
d29f71a92f | ||
|
|
aea14cda7e | ||
|
|
4893ccda6e | ||
|
|
a56c625b4f | ||
|
|
54b2187f98 |
@@ -18,7 +18,12 @@ ALLOWED_SCRIPT_PATHS="scripts/"
|
||||
WEBSOCKET_PORT="3001"
|
||||
|
||||
# User settings
|
||||
# Optional tokens for private repos: GITHUB_TOKEN (GitHub), GITLAB_TOKEN (GitLab),
|
||||
# BITBUCKET_APP_PASSWORD or BITBUCKET_TOKEN (Bitbucket). REPO_URL and added repos
|
||||
# can be GitHub, GitLab, Bitbucket, or custom Git servers.
|
||||
GITHUB_TOKEN=
|
||||
GITLAB_TOKEN=
|
||||
BITBUCKET_APP_PASSWORD=
|
||||
SAVE_FILTER=false
|
||||
FILTERS=
|
||||
AUTH_USERNAME=
|
||||
|
||||
391
package-lock.json
generated
391
package-lock.json
generated
@@ -33,7 +33,7 @@
|
||||
"dotenv": "^17.2.3",
|
||||
"jsonwebtoken": "^9.0.3",
|
||||
"lucide-react": "^0.562.0",
|
||||
"next": "^16.1.3",
|
||||
"next": ">=16.1.5",
|
||||
"node-cron": "^4.2.1",
|
||||
"node-pty": "^1.1.0",
|
||||
"react": "^19.2.3",
|
||||
@@ -68,6 +68,7 @@
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-config-next": "^16.1.3",
|
||||
"jsdom": "^27.4.0",
|
||||
"next": ">=16.1.5",
|
||||
"postcss": "^8.5.6",
|
||||
"prettier": "^3.8.0",
|
||||
"prettier-plugin-tailwindcss": "^0.7.2",
|
||||
@@ -641,34 +642,34 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@electric-sql/pglite": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.3.2.tgz",
|
||||
"integrity": "sha512-zfWWa+V2ViDCY/cmUfRqeWY1yLto+EpxjXnZzenB1TyxsTiXaTWeZFIZw6mac52BsuQm0RjCnisjBtdBaXOI6w==",
|
||||
"version": "0.3.15",
|
||||
"resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.3.15.tgz",
|
||||
"integrity": "sha512-Cj++n1Mekf9ETfdc16TlDi+cDDQF0W7EcbyRHYOAeZdsAe8M/FJg18itDTSwyHfar2WIezawM9o0EKaRGVKygQ==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@electric-sql/pglite-socket": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@electric-sql/pglite-socket/-/pglite-socket-0.0.6.tgz",
|
||||
"integrity": "sha512-6RjmgzphIHIBA4NrMGJsjNWK4pu+bCWJlEWlwcxFTVY3WT86dFpKwbZaGWZV6C5Rd7sCk1Z0CI76QEfukLAUXw==",
|
||||
"version": "0.0.20",
|
||||
"resolved": "https://registry.npmjs.org/@electric-sql/pglite-socket/-/pglite-socket-0.0.20.tgz",
|
||||
"integrity": "sha512-J5nLGsicnD9wJHnno9r+DGxfcZWh+YJMCe0q/aCgtG6XOm9Z7fKeite8IZSNXgZeGltSigM9U/vAWZQWdgcSFg==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"pglite-server": "dist/scripts/server.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@electric-sql/pglite": "0.3.2"
|
||||
"@electric-sql/pglite": "0.3.15"
|
||||
}
|
||||
},
|
||||
"node_modules/@electric-sql/pglite-tools": {
|
||||
"version": "0.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@electric-sql/pglite-tools/-/pglite-tools-0.2.7.tgz",
|
||||
"integrity": "sha512-9dAccClqxx4cZB+Ar9B+FZ5WgxDc/Xvl9DPrTWv+dYTf0YNubLzi4wHHRGRGhrJv15XwnyKcGOZAP1VXSneSUg==",
|
||||
"version": "0.2.20",
|
||||
"resolved": "https://registry.npmjs.org/@electric-sql/pglite-tools/-/pglite-tools-0.2.20.tgz",
|
||||
"integrity": "sha512-BK50ZnYa3IG7ztXhtgYf0Q7zijV32Iw1cYS8C+ThdQlwx12V5VZ9KRJ42y82Hyb4PkTxZQklVQA9JHyUlex33A==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"peerDependencies": {
|
||||
"@electric-sql/pglite": "0.3.2"
|
||||
"@electric-sql/pglite": "0.3.15"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/core": {
|
||||
@@ -687,6 +688,7 @@
|
||||
"version": "1.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz",
|
||||
"integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
@@ -1347,9 +1349,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@hono/node-server": {
|
||||
"version": "1.19.6",
|
||||
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.6.tgz",
|
||||
"integrity": "sha512-Shz/KjlIeAhfiuE93NDKVdZ7HdBVLQAfdbaXEaoAVO3ic9ibRSLGIQGkcBbFyuLr+7/1D5ZCINM8B+6IvXeMtw==",
|
||||
"version": "1.19.9",
|
||||
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz",
|
||||
"integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -1415,6 +1417,7 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz",
|
||||
"integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
@@ -1428,6 +1431,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1450,6 +1454,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1472,6 +1477,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1488,6 +1494,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1504,6 +1511,7 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1520,6 +1528,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1536,6 +1545,7 @@
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1552,6 +1562,7 @@
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1568,6 +1579,7 @@
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1584,6 +1596,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1600,6 +1613,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1616,6 +1630,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1632,6 +1647,7 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1654,6 +1670,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1676,6 +1693,7 @@
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1698,6 +1716,7 @@
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1720,6 +1739,7 @@
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1742,6 +1762,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1764,6 +1785,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1786,6 +1808,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1808,6 +1831,7 @@
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
@@ -1827,6 +1851,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1846,6 +1871,7 @@
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1865,6 +1891,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1928,9 +1955,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@mrleebo/prisma-ast": {
|
||||
"version": "0.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@mrleebo/prisma-ast/-/prisma-ast-0.12.1.tgz",
|
||||
"integrity": "sha512-JwqeCQ1U3fvccttHZq7Tk0m/TMC6WcFAQZdukypW3AzlJYKYTGNVd1ANU2GuhKnv4UQuOFj3oAl0LLG/gxFN1w==",
|
||||
"version": "0.13.1",
|
||||
"resolved": "https://registry.npmjs.org/@mrleebo/prisma-ast/-/prisma-ast-0.13.1.tgz",
|
||||
"integrity": "sha512-XyroGQXcHrZdvmrGJvsA9KNeOOgGMg1Vg9OlheUsBOSKznLMDl+YChxbkboRHvtFYJEMRYmlV3uoo/njCw05iw==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -1955,9 +1982,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/env": {
|
||||
"version": "16.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.3.tgz",
|
||||
"integrity": "sha512-BLP14oBOvZWXgfdJf9ao+VD8O30uE+x7PaV++QtACLX329WcRSJRO5YJ+Bcvu0Q+c/lei41TjSiFf6pXqnpbQA==",
|
||||
"version": "16.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.6.tgz",
|
||||
"integrity": "sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@next/eslint-plugin-next": {
|
||||
@@ -1971,12 +1999,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-darwin-arm64": {
|
||||
"version": "16.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.3.tgz",
|
||||
"integrity": "sha512-CpOD3lmig6VflihVoGxiR/l5Jkjfi4uLaOR4ziriMv0YMDoF6cclI+p5t2nstM8TmaFiY6PCTBgRWB57/+LiBA==",
|
||||
"version": "16.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.6.tgz",
|
||||
"integrity": "sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1987,12 +2016,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-darwin-x64": {
|
||||
"version": "16.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.3.tgz",
|
||||
"integrity": "sha512-aF4us2JXh0zn3hNxvL1Bx3BOuh8Lcw3p3Xnurlvca/iptrDH1BrpObwkw9WZra7L7/0qB9kjlREq3hN/4x4x+Q==",
|
||||
"version": "16.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.6.tgz",
|
||||
"integrity": "sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2003,12 +2033,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-arm64-gnu": {
|
||||
"version": "16.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.3.tgz",
|
||||
"integrity": "sha512-8VRkcpcfBtYvhGgXAF7U3MBx6+G1lACM1XCo1JyaUr4KmAkTNP8Dv2wdMq7BI+jqRBw3zQE7c57+lmp7jCFfKA==",
|
||||
"version": "16.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.6.tgz",
|
||||
"integrity": "sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2019,12 +2050,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-arm64-musl": {
|
||||
"version": "16.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.3.tgz",
|
||||
"integrity": "sha512-UbFx69E2UP7MhzogJRMFvV9KdEn4sLGPicClwgqnLht2TEi204B71HuVfps3ymGAh0c44QRAF+ZmvZZhLLmhNg==",
|
||||
"version": "16.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.6.tgz",
|
||||
"integrity": "sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2035,12 +2067,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-x64-gnu": {
|
||||
"version": "16.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.3.tgz",
|
||||
"integrity": "sha512-SzGTfTjR5e9T+sZh5zXqG/oeRQufExxBF6MssXS7HPeZFE98JDhCRZXpSyCfWrWrYrzmnw/RVhlP2AxQm+wkRQ==",
|
||||
"version": "16.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.6.tgz",
|
||||
"integrity": "sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2051,12 +2084,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-x64-musl": {
|
||||
"version": "16.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.3.tgz",
|
||||
"integrity": "sha512-HlrDpj0v+JBIvQex1mXHq93Mht5qQmfyci+ZNwGClnAQldSfxI6h0Vupte1dSR4ueNv4q7qp5kTnmLOBIQnGow==",
|
||||
"version": "16.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.6.tgz",
|
||||
"integrity": "sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2067,12 +2101,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-win32-arm64-msvc": {
|
||||
"version": "16.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.3.tgz",
|
||||
"integrity": "sha512-3gFCp83/LSduZMSIa+lBREP7+5e7FxpdBoc9QrCdmp+dapmTK9I+SLpY60Z39GDmTXSZA4huGg9WwmYbr6+WRw==",
|
||||
"version": "16.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.6.tgz",
|
||||
"integrity": "sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2083,12 +2118,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-win32-x64-msvc": {
|
||||
"version": "16.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.3.tgz",
|
||||
"integrity": "sha512-1SZVfFT8zmMB+Oblrh5OKDvUo5mYQOkX2We6VGzpg7JUVZlqe4DYOFGKYZKTweSx1gbMixyO1jnFT4thU+nNHQ==",
|
||||
"version": "16.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.6.tgz",
|
||||
"integrity": "sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2194,9 +2230,9 @@
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@prisma/config": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.2.0.tgz",
|
||||
"integrity": "sha512-qmvSnfQ6l/srBW1S7RZGfjTQhc44Yl3ldvU6y3pgmuLM+83SBDs6UQVgMtQuMRe9J3gGqB0RF8wER6RlXEr6jQ==",
|
||||
"version": "7.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.3.0.tgz",
|
||||
"integrity": "sha512-QyMV67+eXF7uMtKxTEeQqNu/Be7iH+3iDZOQZW5ttfbSwBamCSdwPszA0dum+Wx27I7anYTPLmRmMORKViSW1A==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@@ -2255,38 +2291,31 @@
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@prisma/dev": {
|
||||
"version": "0.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/dev/-/dev-0.17.0.tgz",
|
||||
"integrity": "sha512-6sGebe5jxX+FEsQTpjHLzvOGPn6ypFQprcs3jcuIWv1Xp/5v6P/rjfdvAwTkP2iF6pDx2tCd8vGLNWcsWzImTA==",
|
||||
"version": "0.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/dev/-/dev-0.20.0.tgz",
|
||||
"integrity": "sha512-ovlBYwWor0OzG+yH4J3Ot+AneD818BttLA+Ii7wjbcLHUrnC4tbUPVGyNd3c/+71KETPKZfjhkTSpdS15dmXNQ==",
|
||||
"devOptional": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@electric-sql/pglite": "0.3.2",
|
||||
"@electric-sql/pglite-socket": "0.0.6",
|
||||
"@electric-sql/pglite-tools": "0.2.7",
|
||||
"@hono/node-server": "1.19.6",
|
||||
"@mrleebo/prisma-ast": "0.12.1",
|
||||
"@prisma/get-platform": "6.8.2",
|
||||
"@prisma/query-plan-executor": "6.18.0",
|
||||
"@electric-sql/pglite": "0.3.15",
|
||||
"@electric-sql/pglite-socket": "0.0.20",
|
||||
"@electric-sql/pglite-tools": "0.2.20",
|
||||
"@hono/node-server": "1.19.9",
|
||||
"@mrleebo/prisma-ast": "0.13.1",
|
||||
"@prisma/get-platform": "7.2.0",
|
||||
"@prisma/query-plan-executor": "7.2.0",
|
||||
"foreground-child": "3.3.1",
|
||||
"get-port-please": "3.1.2",
|
||||
"hono": "4.10.6",
|
||||
"get-port-please": "3.2.0",
|
||||
"hono": "4.11.4",
|
||||
"http-status-codes": "2.3.0",
|
||||
"pathe": "2.0.3",
|
||||
"proper-lockfile": "4.1.2",
|
||||
"remeda": "2.21.3",
|
||||
"std-env": "3.9.0",
|
||||
"remeda": "2.33.4",
|
||||
"std-env": "3.10.0",
|
||||
"valibot": "1.2.0",
|
||||
"zeptomatch": "2.0.2"
|
||||
"zeptomatch": "2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/dev/node_modules/std-env": {
|
||||
"version": "3.9.0",
|
||||
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz",
|
||||
"integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@prisma/driver-adapter-utils": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.2.0.tgz",
|
||||
@@ -2297,49 +2326,73 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/engines": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.2.0.tgz",
|
||||
"integrity": "sha512-HUeOI/SvCDsHrR9QZn24cxxZcujOjcS3w1oW/XVhnSATAli5SRMOfp/WkG3TtT5rCxDA4xOnlJkW7xkho4nURA==",
|
||||
"version": "7.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.3.0.tgz",
|
||||
"integrity": "sha512-cWRQoPDXPtR6stOWuWFZf9pHdQ/o8/QNWn0m0zByxf5Kd946Q875XdEJ52pEsX88vOiXUmjuPG3euw82mwQNMg==",
|
||||
"devOptional": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@prisma/debug": "7.2.0",
|
||||
"@prisma/engines-version": "7.2.0-4.0c8ef2ce45c83248ab3df073180d5eda9e8be7a3",
|
||||
"@prisma/fetch-engine": "7.2.0",
|
||||
"@prisma/get-platform": "7.2.0"
|
||||
"@prisma/debug": "7.3.0",
|
||||
"@prisma/engines-version": "7.3.0-16.9d6ad21cbbceab97458517b147a6a09ff43aa735",
|
||||
"@prisma/fetch-engine": "7.3.0",
|
||||
"@prisma/get-platform": "7.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/engines-version": {
|
||||
"version": "7.2.0-4.0c8ef2ce45c83248ab3df073180d5eda9e8be7a3",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.2.0-4.0c8ef2ce45c83248ab3df073180d5eda9e8be7a3.tgz",
|
||||
"integrity": "sha512-KezsjCZDsbjNR7SzIiVlUsn9PnLePI7r5uxABlwL+xoerurZTfgQVbIjvjF2sVr3Uc0ZcsnREw3F84HvbggGdA==",
|
||||
"version": "7.3.0-16.9d6ad21cbbceab97458517b147a6a09ff43aa735",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.3.0-16.9d6ad21cbbceab97458517b147a6a09ff43aa735.tgz",
|
||||
"integrity": "sha512-IH2va2ouUHihyiTTRW889LjKAl1CusZOvFfZxCDNpjSENt7g2ndFsK0vdIw/72v7+jCN6YgkHmdAP/BI7SDgyg==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@prisma/engines/node_modules/@prisma/debug": {
|
||||
"version": "7.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.3.0.tgz",
|
||||
"integrity": "sha512-yh/tHhraCzYkffsI1/3a7SHX8tpgbJu1NPnuxS4rEpJdWAUDHUH25F1EDo6PPzirpyLNkgPPZdhojQK804BGtg==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@prisma/engines/node_modules/@prisma/get-platform": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.2.0.tgz",
|
||||
"integrity": "sha512-k1V0l0Td1732EHpAfi2eySTezyllok9dXb6UQanajkJQzPUGi3vO2z7jdkz67SypFTdmbnyGYxvEvYZdZsMAVA==",
|
||||
"version": "7.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.3.0.tgz",
|
||||
"integrity": "sha512-N7c6m4/I0Q6JYmWKP2RCD/sM9eWiyCPY98g5c0uEktObNSZnugW2U/PO+pwL0UaqzxqTXt7gTsYsb0FnMnJNbg==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@prisma/debug": "7.2.0"
|
||||
"@prisma/debug": "7.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/fetch-engine": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.2.0.tgz",
|
||||
"integrity": "sha512-Z5XZztJ8Ap+wovpjPD2lQKnB8nWFGNouCrglaNFjxIWAGWz0oeHXwUJRiclIoSSXN/ptcs9/behptSk8d0Yy6w==",
|
||||
"version": "7.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.3.0.tgz",
|
||||
"integrity": "sha512-Mm0F84JMqM9Vxk70pzfNpGJ1lE4hYjOeLMu7nOOD1i83nvp8MSAcFYBnHqLvEZiA6onUR+m8iYogtOY4oPO5lQ==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@prisma/debug": "7.2.0",
|
||||
"@prisma/engines-version": "7.2.0-4.0c8ef2ce45c83248ab3df073180d5eda9e8be7a3",
|
||||
"@prisma/get-platform": "7.2.0"
|
||||
"@prisma/debug": "7.3.0",
|
||||
"@prisma/engines-version": "7.3.0-16.9d6ad21cbbceab97458517b147a6a09ff43aa735",
|
||||
"@prisma/get-platform": "7.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/fetch-engine/node_modules/@prisma/debug": {
|
||||
"version": "7.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.3.0.tgz",
|
||||
"integrity": "sha512-yh/tHhraCzYkffsI1/3a7SHX8tpgbJu1NPnuxS4rEpJdWAUDHUH25F1EDo6PPzirpyLNkgPPZdhojQK804BGtg==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@prisma/fetch-engine/node_modules/@prisma/get-platform": {
|
||||
"version": "7.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.3.0.tgz",
|
||||
"integrity": "sha512-N7c6m4/I0Q6JYmWKP2RCD/sM9eWiyCPY98g5c0uEktObNSZnugW2U/PO+pwL0UaqzxqTXt7gTsYsb0FnMnJNbg==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@prisma/debug": "7.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/get-platform": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.2.0.tgz",
|
||||
"integrity": "sha512-k1V0l0Td1732EHpAfi2eySTezyllok9dXb6UQanajkJQzPUGi3vO2z7jdkz67SypFTdmbnyGYxvEvYZdZsMAVA==",
|
||||
@@ -2349,34 +2402,17 @@
|
||||
"@prisma/debug": "7.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/get-platform": {
|
||||
"version": "6.8.2",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.8.2.tgz",
|
||||
"integrity": "sha512-vXSxyUgX3vm1Q70QwzwkjeYfRryIvKno1SXbIqwSptKwqKzskINnDUcx85oX+ys6ooN2ATGSD0xN2UTfg6Zcow==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@prisma/debug": "6.8.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/get-platform/node_modules/@prisma/debug": {
|
||||
"version": "6.8.2",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.8.2.tgz",
|
||||
"integrity": "sha512-4muBSSUwJJ9BYth5N8tqts8JtiLT8QI/RSAzEogwEfpbYGFo9mYsInsVo8dqXdPO2+Rm5OG5q0qWDDE3nyUbVg==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@prisma/query-plan-executor": {
|
||||
"version": "6.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/query-plan-executor/-/query-plan-executor-6.18.0.tgz",
|
||||
"integrity": "sha512-jZ8cfzFgL0jReE1R10gT8JLHtQxjWYLiQ//wHmVYZ2rVkFHoh0DT8IXsxcKcFlfKN7ak7k6j0XMNn2xVNyr5cA==",
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/query-plan-executor/-/query-plan-executor-7.2.0.tgz",
|
||||
"integrity": "sha512-EOZmNzcV8uJ0mae3DhTsiHgoNCuu1J9mULQpGCh62zN3PxPTd+qI9tJvk5jOst8WHKQNwJWR3b39t0XvfBB0WQ==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@prisma/studio-core": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/studio-core/-/studio-core-0.9.0.tgz",
|
||||
"integrity": "sha512-xA2zoR/ADu/NCSQuriBKTh6Ps4XjU0bErkEcgMfnSGh346K1VI7iWKnoq1l2DoxUqiddPHIEWwtxJ6xCHG6W7g==",
|
||||
"version": "0.13.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/studio-core/-/studio-core-0.13.1.tgz",
|
||||
"integrity": "sha512-agdqaPEePRHcQ7CexEfkX1RvSH9uWDb6pXrZnhCRykhDFAV0/0P3d07WtfiY8hZWb7oRU4v+NkT4cGFHkQJIPg==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"peerDependencies": {
|
||||
@@ -3305,6 +3341,7 @@
|
||||
"version": "0.5.15",
|
||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
|
||||
"integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "^2.8.0"
|
||||
@@ -5279,6 +5316,7 @@
|
||||
"version": "2.9.15",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.15.tgz",
|
||||
"integrity": "sha512-kX8h7K2srmDyYnXRIppo4AH/wYgzWVCs+eKr3RusRSQ5PvRYoEFmR/I0PbdTjKFAoKqp5+kbxnNTFO9jOfSVJg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"baseline-browser-mapping": "dist/cli.js"
|
||||
@@ -5489,6 +5527,7 @@
|
||||
"version": "1.0.30001757",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz",
|
||||
"integrity": "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -5645,6 +5684,7 @@
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
|
||||
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/clsx": {
|
||||
@@ -7324,9 +7364,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/get-port-please": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/get-port-please/-/get-port-please-3.1.2.tgz",
|
||||
"integrity": "sha512-Gxc29eLs1fbn6LQ4jSU4vXjlwyZhF5HsGuMAa7gqBP4Rw4yxxltyDUuF5MBclFzDTXO+ACchGQoeela4DSfzdQ==",
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/get-port-please/-/get-port-please-3.2.0.tgz",
|
||||
"integrity": "sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -7467,6 +7507,12 @@
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/graphmatch": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/graphmatch/-/graphmatch-1.1.0.tgz",
|
||||
"integrity": "sha512-0E62MaTW5rPZVRLyIJZG/YejmdA/Xr1QydHEw3Vt+qOKkMIOE8WDLc9ZX2bmAjtJFZcId4lEdrdmASsEy7D1QA==",
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/has-bigints": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
|
||||
@@ -7661,9 +7707,9 @@
|
||||
"license": "CC0-1.0"
|
||||
},
|
||||
"node_modules/hono": {
|
||||
"version": "4.10.6",
|
||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.10.6.tgz",
|
||||
"integrity": "sha512-BIdolzGpDO9MQ4nu3AUuDwHZZ+KViNm+EZ75Ae55eMXMqLVhDFqEMXxtUe9Qh8hjL+pIna/frs2j6Y2yD5Ua/g==",
|
||||
"version": "4.11.7",
|
||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.11.7.tgz",
|
||||
"integrity": "sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
@@ -8925,9 +8971,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"version": "4.17.23",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
|
||||
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -10167,6 +10213,7 @@
|
||||
"version": "3.3.11",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
||||
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -10211,12 +10258,13 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/next": {
|
||||
"version": "16.1.3",
|
||||
"resolved": "https://registry.npmjs.org/next/-/next-16.1.3.tgz",
|
||||
"integrity": "sha512-gthG3TRD+E3/mA0uDQb9lqBmx1zVosq5kIwxNN6+MRNd085GzD+9VXMPUs+GGZCbZ+GDZdODUq4Pm7CTXK6ipw==",
|
||||
"version": "16.1.6",
|
||||
"resolved": "https://registry.npmjs.org/next/-/next-16.1.6.tgz",
|
||||
"integrity": "sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@next/env": "16.1.3",
|
||||
"@next/env": "16.1.6",
|
||||
"@swc/helpers": "0.5.15",
|
||||
"baseline-browser-mapping": "^2.8.3",
|
||||
"caniuse-lite": "^1.0.30001579",
|
||||
@@ -10230,14 +10278,14 @@
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@next/swc-darwin-arm64": "16.1.3",
|
||||
"@next/swc-darwin-x64": "16.1.3",
|
||||
"@next/swc-linux-arm64-gnu": "16.1.3",
|
||||
"@next/swc-linux-arm64-musl": "16.1.3",
|
||||
"@next/swc-linux-x64-gnu": "16.1.3",
|
||||
"@next/swc-linux-x64-musl": "16.1.3",
|
||||
"@next/swc-win32-arm64-msvc": "16.1.3",
|
||||
"@next/swc-win32-x64-msvc": "16.1.3",
|
||||
"@next/swc-darwin-arm64": "16.1.6",
|
||||
"@next/swc-darwin-x64": "16.1.6",
|
||||
"@next/swc-linux-arm64-gnu": "16.1.6",
|
||||
"@next/swc-linux-arm64-musl": "16.1.6",
|
||||
"@next/swc-linux-x64-gnu": "16.1.6",
|
||||
"@next/swc-linux-x64-musl": "16.1.6",
|
||||
"@next/swc-win32-arm64-msvc": "16.1.6",
|
||||
"@next/swc-win32-x64-msvc": "16.1.6",
|
||||
"sharp": "^0.34.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
@@ -10267,6 +10315,7 @@
|
||||
"version": "8.4.31",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
||||
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -10355,25 +10404,30 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nypm": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.2.tgz",
|
||||
"integrity": "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==",
|
||||
"version": "0.6.4",
|
||||
"resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.4.tgz",
|
||||
"integrity": "sha512-1TvCKjZyyklN+JJj2TS3P4uSQEInrM/HkkuSXsEzm1ApPgBffOn8gFguNnZf07r/1X6vlryfIqMUkJKQMzlZiw==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"citty": "^0.1.6",
|
||||
"consola": "^3.4.2",
|
||||
"citty": "^0.2.0",
|
||||
"pathe": "^2.0.3",
|
||||
"pkg-types": "^2.3.0",
|
||||
"tinyexec": "^1.0.1"
|
||||
"tinyexec": "^1.0.2"
|
||||
},
|
||||
"bin": {
|
||||
"nypm": "dist/cli.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.16.0 || >=16.10.0"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/nypm/node_modules/citty": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/citty/-/citty-0.2.0.tgz",
|
||||
"integrity": "sha512-8csy5IBFI2ex2hTVpaHN2j+LNE199AgiI7y4dMintrr8i0lQiFn+0AWMZrWdHKIgMOer65f8IThysYhoReqjWA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
@@ -10688,6 +10742,7 @@
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
@@ -10942,18 +10997,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/prisma": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/prisma/-/prisma-7.2.0.tgz",
|
||||
"integrity": "sha512-jSdHWgWOgFF24+nRyyNRVBIgGDQEsMEF8KPHvhBBg3jWyR9fUAK0Nq9ThUmiGlNgq2FA7vSk/ZoCvefod+a8qg==",
|
||||
"version": "7.3.0",
|
||||
"resolved": "https://registry.npmjs.org/prisma/-/prisma-7.3.0.tgz",
|
||||
"integrity": "sha512-ApYSOLHfMN8WftJA+vL6XwAPOh/aZ0BgUyyKPwUFgjARmG6EBI9LzDPf6SWULQMSAxydV9qn5gLj037nPNlg2w==",
|
||||
"devOptional": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@prisma/config": "7.2.0",
|
||||
"@prisma/dev": "0.17.0",
|
||||
"@prisma/engines": "7.2.0",
|
||||
"@prisma/studio-core": "0.9.0",
|
||||
"@prisma/config": "7.3.0",
|
||||
"@prisma/dev": "0.20.0",
|
||||
"@prisma/engines": "7.3.0",
|
||||
"@prisma/studio-core": "0.13.1",
|
||||
"mysql2": "3.15.3",
|
||||
"postgres": "3.4.7"
|
||||
},
|
||||
@@ -11464,13 +11519,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/remeda": {
|
||||
"version": "2.21.3",
|
||||
"resolved": "https://registry.npmjs.org/remeda/-/remeda-2.21.3.tgz",
|
||||
"integrity": "sha512-XXrZdLA10oEOQhLLzEJEiFFSKi21REGAkHdImIb4rt/XXy8ORGXh5HCcpUOsElfPNDb+X6TA/+wkh+p2KffYmg==",
|
||||
"version": "2.33.4",
|
||||
"resolved": "https://registry.npmjs.org/remeda/-/remeda-2.33.4.tgz",
|
||||
"integrity": "sha512-ygHswjlc/opg2VrtiYvUOPLjxjtdKvjGz1/plDhkG66hjNjFr1xmfrs2ClNFo/E6TyUFiwYNh53bKV26oBoMGQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"type-fest": "^4.39.1"
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/remeda"
|
||||
}
|
||||
},
|
||||
"node_modules/require-from-string": {
|
||||
@@ -11787,6 +11842,7 @@
|
||||
"version": "0.34.5",
|
||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
|
||||
"integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
@@ -11832,6 +11888,7 @@
|
||||
"version": "7.7.3",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
|
||||
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
@@ -12024,6 +12081,7 @@
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
@@ -12067,7 +12125,7 @@
|
||||
"version": "3.10.0",
|
||||
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
|
||||
"integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/stop-iteration-iterator": {
|
||||
@@ -12305,6 +12363,7 @@
|
||||
"version": "5.1.6",
|
||||
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
|
||||
"integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"client-only": "0.0.1"
|
||||
@@ -13167,19 +13226,6 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/type-fest": {
|
||||
"version": "4.41.0",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
|
||||
"integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
|
||||
"devOptional": true,
|
||||
"license": "(MIT OR CC0-1.0)",
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/typed-array-buffer": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz",
|
||||
@@ -14021,13 +14067,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/zeptomatch": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/zeptomatch/-/zeptomatch-2.0.2.tgz",
|
||||
"integrity": "sha512-H33jtSKf8Ijtb5BW6wua3G5DhnFjbFML36eFu+VdOoVY4HD9e7ggjqdM6639B+L87rjnR6Y+XeRzBXZdy52B/g==",
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/zeptomatch/-/zeptomatch-2.1.0.tgz",
|
||||
"integrity": "sha512-KiGErG2J0G82LSpniV0CtIzjlJ10E04j02VOudJsPyPwNZgGnRKQy7I1R7GMyg/QswnE4l7ohSGrQbQbjXPPDA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"grammex": "^3.1.10"
|
||||
"grammex": "^3.1.11",
|
||||
"graphmatch": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/zod": {
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
"dotenv": "^17.2.3",
|
||||
"jsonwebtoken": "^9.0.3",
|
||||
"lucide-react": "^0.562.0",
|
||||
"next": "^16.1.3",
|
||||
"next": ">=16.1.5",
|
||||
"node-cron": "^4.2.1",
|
||||
"node-pty": "^1.1.0",
|
||||
"react": "^19.2.3",
|
||||
@@ -66,6 +66,7 @@
|
||||
"zod": "^4.3.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"next": ">=16.1.5",
|
||||
"@tailwindcss/postcss": "^4.1.18",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/react": "^16.3.1",
|
||||
@@ -102,6 +103,8 @@
|
||||
"node": ">=24.0.0"
|
||||
},
|
||||
"overrides": {
|
||||
"prismjs": "^1.30.0"
|
||||
"prismjs": "^1.30.0",
|
||||
"hono": ">=4.11.7",
|
||||
"lodash": ">=4.17.23"
|
||||
}
|
||||
}
|
||||
29
server.js
29
server.js
@@ -312,7 +312,7 @@ class ScriptExecutionHandler {
|
||||
} else if (isUpdate && containerId) {
|
||||
await this.startUpdateExecution(ws, containerId, executionId, mode, server, backupStorage);
|
||||
} else if (isShell && containerId) {
|
||||
await this.startShellExecution(ws, containerId, executionId, mode, server);
|
||||
await this.startShellExecution(ws, containerId, executionId, mode, server, containerType);
|
||||
} else {
|
||||
await this.startScriptExecution(ws, scriptPath, executionId, mode, server, envVars);
|
||||
}
|
||||
@@ -1474,21 +1474,21 @@ class ScriptExecutionHandler {
|
||||
* @param {string} executionId
|
||||
* @param {string} mode
|
||||
* @param {ServerInfo|null} server
|
||||
* @param {'lxc'|'vm'} [containerType='lxc']
|
||||
*/
|
||||
async startShellExecution(ws, containerId, executionId, mode = 'local', server = null) {
|
||||
async startShellExecution(ws, containerId, executionId, mode = 'local', server = null, containerType = 'lxc') {
|
||||
try {
|
||||
|
||||
// Send start message
|
||||
const typeLabel = containerType === 'vm' ? 'VM' : 'container';
|
||||
this.sendMessage(ws, {
|
||||
type: 'start',
|
||||
data: `Starting shell session for container ${containerId}...`,
|
||||
data: `Starting shell session for ${typeLabel} ${containerId}...`,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
if (mode === 'ssh' && server) {
|
||||
await this.startSSHShellExecution(ws, containerId, executionId, server);
|
||||
await this.startSSHShellExecution(ws, containerId, executionId, server, containerType);
|
||||
} else {
|
||||
await this.startLocalShellExecution(ws, containerId, executionId);
|
||||
await this.startLocalShellExecution(ws, containerId, executionId, containerType);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
@@ -1505,12 +1505,12 @@ class ScriptExecutionHandler {
|
||||
* @param {ExtendedWebSocket} ws
|
||||
* @param {string} containerId
|
||||
* @param {string} executionId
|
||||
* @param {'lxc'|'vm'} [containerType='lxc']
|
||||
*/
|
||||
async startLocalShellExecution(ws, containerId, executionId) {
|
||||
async startLocalShellExecution(ws, containerId, executionId, containerType = 'lxc') {
|
||||
const { spawn } = await import('node-pty');
|
||||
|
||||
// Create a shell process that will run pct enter
|
||||
const childProcess = spawn('bash', ['-c', `pct enter ${containerId}`], {
|
||||
const shellCommand = containerType === 'vm' ? `qm terminal ${containerId}` : `pct enter ${containerId}`;
|
||||
const childProcess = spawn('bash', ['-c', shellCommand], {
|
||||
name: 'xterm-color',
|
||||
cols: 80,
|
||||
rows: 24,
|
||||
@@ -1553,14 +1553,15 @@ class ScriptExecutionHandler {
|
||||
* @param {string} containerId
|
||||
* @param {string} executionId
|
||||
* @param {ServerInfo} server
|
||||
* @param {'lxc'|'vm'} [containerType='lxc']
|
||||
*/
|
||||
async startSSHShellExecution(ws, containerId, executionId, server) {
|
||||
async startSSHShellExecution(ws, containerId, executionId, server, containerType = 'lxc') {
|
||||
const sshService = getSSHExecutionService();
|
||||
|
||||
const shellCommand = containerType === 'vm' ? `qm terminal ${containerId}` : `pct enter ${containerId}`;
|
||||
try {
|
||||
const execution = await sshService.executeCommand(
|
||||
server,
|
||||
`pct enter ${containerId}`,
|
||||
shellCommand,
|
||||
/** @param {string} data */
|
||||
(data) => {
|
||||
this.sendMessage(ws, {
|
||||
|
||||
@@ -199,6 +199,17 @@ export function ConfigurationModal({
|
||||
return !isNaN(num) && num > 0;
|
||||
};
|
||||
|
||||
const validateHostname = (hostname: string): boolean => {
|
||||
if (!hostname || hostname.length > 253) return false;
|
||||
const label = /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/;
|
||||
const labels = hostname.split('.');
|
||||
return labels.length >= 1 && labels.every(l => l.length >= 1 && l.length <= 63 && label.test(l));
|
||||
};
|
||||
|
||||
const validateAptCacherAddress = (value: string): boolean => {
|
||||
return validateIPv4(value) || validateHostname(value);
|
||||
};
|
||||
|
||||
const validateForm = (): boolean => {
|
||||
const newErrors: Record<string, string> = {};
|
||||
|
||||
@@ -216,8 +227,8 @@ export function ConfigurationModal({
|
||||
if (advancedVars.var_ns && !validateIPv4(advancedVars.var_ns as string)) {
|
||||
newErrors.var_ns = 'Invalid IPv4 address';
|
||||
}
|
||||
if (advancedVars.var_apt_cacher_ip && !validateIPv4(advancedVars.var_apt_cacher_ip as string)) {
|
||||
newErrors.var_apt_cacher_ip = 'Invalid IPv4 address';
|
||||
if (advancedVars.var_apt_cacher_ip && !validateAptCacherAddress(advancedVars.var_apt_cacher_ip as string)) {
|
||||
newErrors.var_apt_cacher_ip = 'Invalid IPv4 address or hostname';
|
||||
}
|
||||
// Validate IPv4 CIDR if network mode is static
|
||||
const netValue = advancedVars.var_net;
|
||||
@@ -904,13 +915,13 @@ export function ConfigurationModal({
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-foreground mb-2">
|
||||
APT Cacher IP
|
||||
APT Cacher host or IP
|
||||
</label>
|
||||
<Input
|
||||
type="text"
|
||||
value={typeof advancedVars.var_apt_cacher_ip === 'boolean' ? '' : String(advancedVars.var_apt_cacher_ip ?? '')}
|
||||
onChange={(e) => updateAdvancedVar('var_apt_cacher_ip', e.target.value)}
|
||||
placeholder="192.168.1.10"
|
||||
placeholder="192.168.1.10 or apt-cacher.internal"
|
||||
className={errors.var_apt_cacher_ip ? 'border-destructive' : ''}
|
||||
/>
|
||||
{errors.var_apt_cacher_ip && (
|
||||
|
||||
@@ -1617,7 +1617,7 @@ export function GeneralSettingsModal({
|
||||
<Input
|
||||
id="new-repo-url"
|
||||
type="url"
|
||||
placeholder="https://github.com/owner/repo"
|
||||
placeholder="https://github.com/owner/repo or https://git.example.com/owner/repo"
|
||||
value={newRepoUrl}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setNewRepoUrl(e.target.value)
|
||||
@@ -1626,8 +1626,9 @@ export function GeneralSettingsModal({
|
||||
className="w-full"
|
||||
/>
|
||||
<p className="text-muted-foreground mt-1 text-xs">
|
||||
Enter a GitHub repository URL (e.g.,
|
||||
https://github.com/owner/repo)
|
||||
Supported: GitHub, GitLab, Bitbucket, or custom Git
|
||||
servers (e.g. https://github.com/owner/repo,
|
||||
https://gitlab.com/owner/repo)
|
||||
</p>
|
||||
</div>
|
||||
<div className="border-border flex items-center justify-between gap-3 rounded-lg border p-3">
|
||||
|
||||
@@ -80,6 +80,7 @@ export function InstalledScriptsTab() {
|
||||
id: number;
|
||||
containerId: string;
|
||||
server?: any;
|
||||
containerType?: 'lxc' | 'vm';
|
||||
} | null>(null);
|
||||
const [showBackupPrompt, setShowBackupPrompt] = useState(false);
|
||||
const [showStorageSelection, setShowStorageSelection] = useState(false);
|
||||
@@ -1167,6 +1168,7 @@ export function InstalledScriptsTab() {
|
||||
id: script.id,
|
||||
containerId: script.container_id,
|
||||
server: server,
|
||||
containerType: script.is_vm ? 'vm' : 'lxc',
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1452,6 +1454,13 @@ export function InstalledScriptsTab() {
|
||||
{/* Shell Terminal */}
|
||||
{openingShell && (
|
||||
<div className="mb-8" data-terminal="shell">
|
||||
{openingShell.containerType === 'vm' && (
|
||||
<p className="text-muted-foreground mb-2 text-sm">
|
||||
VM shell uses the Proxmox serial console. The VM must have a
|
||||
serial port configured (e.g. <code className="bg-muted rounded px-1">qm set {openingShell.containerId} -serial0 socket</code>).
|
||||
Detach with <kbd className="bg-muted rounded px-1">Ctrl+O</kbd>.
|
||||
</p>
|
||||
)}
|
||||
<Terminal
|
||||
scriptPath={`shell-${openingShell.containerId}`}
|
||||
onClose={handleCloseShellTerminal}
|
||||
@@ -1459,6 +1468,7 @@ export function InstalledScriptsTab() {
|
||||
server={openingShell.server}
|
||||
isShell={true}
|
||||
containerId={openingShell.containerId}
|
||||
containerType={openingShell.containerType}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -1538,7 +1548,7 @@ export function InstalledScriptsTab() {
|
||||
>
|
||||
{showAutoDetectForm
|
||||
? "Cancel Auto-Detect"
|
||||
: '🔍 Auto-Detect LXC Containers (Must contain a tag with "community-script")'}
|
||||
: '🔍 Auto-Detect Containers & VMs (tag: community-script)'}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
@@ -1764,12 +1774,11 @@ export function InstalledScriptsTab() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Auto-Detect LXC Containers Form */}
|
||||
{/* Auto-Detect Containers & VMs Form */}
|
||||
{showAutoDetectForm && (
|
||||
<div className="bg-card border-border mb-6 rounded-lg border p-4 shadow-sm sm:p-6">
|
||||
<h3 className="text-foreground mb-4 text-lg font-semibold sm:mb-6">
|
||||
Auto-Detect LXC Containers (Must contain a tag with
|
||||
"community-script")
|
||||
Auto-Detect Containers & VMs (tag: community-script)
|
||||
</h3>
|
||||
<div className="space-y-4 sm:space-y-6">
|
||||
<div className="bg-muted/30 border-muted rounded-lg border p-4">
|
||||
@@ -1795,12 +1804,12 @@ export function InstalledScriptsTab() {
|
||||
<p>This feature will:</p>
|
||||
<ul className="mt-1 list-inside list-disc space-y-1">
|
||||
<li>Connect to the selected server via SSH</li>
|
||||
<li>Scan all LXC config files in /etc/pve/lxc/</li>
|
||||
<li>Scan LXC configs in /etc/pve/lxc/ and VM configs in /etc/pve/qemu-server/</li>
|
||||
<li>
|
||||
Find containers with "community-script" in
|
||||
Find containers and VMs with "community-script" in
|
||||
their tags
|
||||
</li>
|
||||
<li>Extract the container ID and hostname</li>
|
||||
<li>Extract the container/VM ID and hostname or name</li>
|
||||
<li>Add them as installed script entries</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -2302,6 +2311,11 @@ export function InstalledScriptsTab() {
|
||||
"stopped"
|
||||
}
|
||||
className="text-muted-foreground hover:text-foreground hover:bg-muted/20 focus:bg-muted/20"
|
||||
title={
|
||||
script.is_vm
|
||||
? "VM serial console (requires serial port; detach with Ctrl+O)"
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
Shell
|
||||
</DropdownMenuItem>
|
||||
|
||||
@@ -270,22 +270,21 @@ export function PBSCredentialsModal({
|
||||
htmlFor="pbs-fingerprint"
|
||||
className="text-foreground mb-1 block text-sm font-medium"
|
||||
>
|
||||
Fingerprint <span className="text-error">*</span>
|
||||
Fingerprint
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="pbs-fingerprint"
|
||||
value={pbsFingerprint}
|
||||
onChange={(e) => setPbsFingerprint(e.target.value)}
|
||||
required
|
||||
disabled={isLoading}
|
||||
className="bg-card text-foreground placeholder-muted-foreground focus:ring-ring focus:border-ring border-border w-full rounded-md border px-3 py-2 shadow-sm focus:ring-2 focus:outline-none"
|
||||
placeholder="e.g., 7b:e5:87:38:5e:16:05:d1:12:22:7f:73:d2:e2:d0:cf:8c:cb:28:e2:74:0c:78:91:1a:71:74:2e:79:20:5a:02"
|
||||
/>
|
||||
<p className="text-muted-foreground mt-1 text-xs">
|
||||
Server fingerprint for auto-acceptance. You can find this on
|
||||
your PBS dashboard by clicking the "Show Fingerprint"
|
||||
button.
|
||||
Leave empty if PBS uses a trusted CA (e.g. Let's Encrypt).
|
||||
For self-signed certificates, enter the server fingerprint from
|
||||
the PBS dashboard ("Show Fingerprint").
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -23,8 +23,11 @@ export const env = createEnv({
|
||||
ALLOWED_SCRIPT_PATHS: z.string().default("scripts/"),
|
||||
// WebSocket Configuration
|
||||
WEBSOCKET_PORT: z.string().default("3001"),
|
||||
// GitHub Configuration
|
||||
// Git provider tokens (optional, for private repos)
|
||||
GITHUB_TOKEN: z.string().optional(),
|
||||
GITLAB_TOKEN: z.string().optional(),
|
||||
BITBUCKET_APP_PASSWORD: z.string().optional(),
|
||||
BITBUCKET_TOKEN: z.string().optional(),
|
||||
// Authentication Configuration
|
||||
AUTH_USERNAME: z.string().optional(),
|
||||
AUTH_PASSWORD_HASH: z.string().optional(),
|
||||
@@ -62,8 +65,10 @@ export const env = createEnv({
|
||||
ALLOWED_SCRIPT_PATHS: process.env.ALLOWED_SCRIPT_PATHS,
|
||||
// WebSocket Configuration
|
||||
WEBSOCKET_PORT: process.env.WEBSOCKET_PORT,
|
||||
// GitHub Configuration
|
||||
GITHUB_TOKEN: process.env.GITHUB_TOKEN,
|
||||
GITLAB_TOKEN: process.env.GITLAB_TOKEN,
|
||||
BITBUCKET_APP_PASSWORD: process.env.BITBUCKET_APP_PASSWORD,
|
||||
BITBUCKET_TOKEN: process.env.BITBUCKET_TOKEN,
|
||||
// Authentication Configuration
|
||||
AUTH_USERNAME: process.env.AUTH_USERNAME,
|
||||
AUTH_PASSWORD_HASH: process.env.AUTH_PASSWORD_HASH,
|
||||
|
||||
@@ -1060,7 +1060,7 @@ export const installedScriptsRouter = createTRPCRouter({
|
||||
reject(new Error(`pct list failed: ${error}`));
|
||||
},
|
||||
(_exitCode: number) => {
|
||||
resolve();
|
||||
setImmediate(() => resolve());
|
||||
}
|
||||
);
|
||||
});
|
||||
@@ -1079,7 +1079,7 @@ export const installedScriptsRouter = createTRPCRouter({
|
||||
reject(new Error(`qm list failed: ${error}`));
|
||||
},
|
||||
(_exitCode: number) => {
|
||||
resolve();
|
||||
setImmediate(() => resolve());
|
||||
}
|
||||
);
|
||||
});
|
||||
@@ -2068,32 +2068,72 @@ export const installedScriptsRouter = createTRPCRouter({
|
||||
};
|
||||
}
|
||||
|
||||
// Get the script's interface_port from metadata (prioritize metadata over existing database values)
|
||||
let detectedPort = 80; // Default fallback
|
||||
|
||||
// Resolve app slug from /usr/bin/update (community-scripts) when available; else from hostname/suffix.
|
||||
let slugFromUpdate: string | null = null;
|
||||
try {
|
||||
const updateCommand = `pct exec ${scriptData.container_id} -- cat /usr/bin/update 2>/dev/null`;
|
||||
let updateOutput = '';
|
||||
await new Promise<void>((resolve) => {
|
||||
void sshExecutionService.executeCommand(
|
||||
server as Server,
|
||||
updateCommand,
|
||||
(data: string) => { updateOutput += data; },
|
||||
() => {},
|
||||
() => resolve()
|
||||
);
|
||||
});
|
||||
const ctSlugMatch = /ct\/([a-zA-Z0-9_.-]+)\.sh/.exec(updateOutput);
|
||||
if (ctSlugMatch?.[1]) {
|
||||
slugFromUpdate = ctSlugMatch[1].trim().toLowerCase();
|
||||
console.log('🔍 Slug from /usr/bin/update:', slugFromUpdate);
|
||||
}
|
||||
} catch {
|
||||
// Container may not be from community-scripts; use hostname fallback
|
||||
}
|
||||
|
||||
// Get the script's interface_port from metadata. Primary: slug from /usr/bin/update; fallback: hostname/suffix.
|
||||
let detectedPort = 80; // Default fallback
|
||||
|
||||
try {
|
||||
// Import localScriptsService to get script metadata
|
||||
const { localScriptsService } = await import('~/server/services/localScripts');
|
||||
|
||||
// Get all scripts and find the one matching our script name
|
||||
const allScripts = await localScriptsService.getAllScripts();
|
||||
|
||||
// Extract script slug from script_name (remove .sh extension)
|
||||
const scriptSlug = scriptData.script_name.replace(/\.sh$/, '');
|
||||
console.log('🔍 Looking for script with slug:', scriptSlug);
|
||||
|
||||
const scriptMetadata = allScripts.find(script => script.slug === scriptSlug);
|
||||
|
||||
|
||||
const nameFromHostname = scriptData.script_name.replace(/\.sh$/, '').toLowerCase();
|
||||
|
||||
// Primary: slug from /usr/bin/update (community-scripts)
|
||||
let scriptMetadata =
|
||||
slugFromUpdate != null
|
||||
? allScripts.find((s) => s.slug === slugFromUpdate)
|
||||
: undefined;
|
||||
if (scriptMetadata) {
|
||||
console.log('🔍 Using slug from /usr/bin/update for metadata:', scriptMetadata.slug);
|
||||
}
|
||||
|
||||
// Fallback: exact hostname then hostname ends with slug (longest wins)
|
||||
if (!scriptMetadata) {
|
||||
scriptMetadata = allScripts.find((script) => script.slug === nameFromHostname);
|
||||
if (!scriptMetadata) {
|
||||
const suffixMatches = allScripts.filter((script) => nameFromHostname.endsWith(script.slug));
|
||||
scriptMetadata =
|
||||
suffixMatches.length > 0
|
||||
? suffixMatches.reduce((a, b) => (a.slug.length >= b.slug.length ? a : b))
|
||||
: undefined;
|
||||
if (scriptMetadata) {
|
||||
console.log('🔍 Matched metadata by slug suffix in hostname:', scriptMetadata.slug);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (scriptMetadata?.interface_port) {
|
||||
detectedPort = scriptMetadata.interface_port;
|
||||
console.log('📋 Found interface_port in metadata:', detectedPort);
|
||||
} else {
|
||||
console.log('📋 No interface_port found in metadata, using default port 80');
|
||||
detectedPort = 80; // Default to port 80 if no metadata port found
|
||||
detectedPort = 80;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('⚠️ Error getting script metadata, using default port 80:', error);
|
||||
detectedPort = 80; // Default to port 80 if metadata lookup fails
|
||||
detectedPort = 80;
|
||||
}
|
||||
|
||||
console.log('🎯 Final detected port:', detectedPort);
|
||||
|
||||
55
src/server/lib/gitProvider/bitbucket.ts
Normal file
55
src/server/lib/gitProvider/bitbucket.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import type { DirEntry, GitProvider } from './types';
|
||||
import { parseRepoUrl } from '../repositoryUrlValidation';
|
||||
|
||||
export class BitbucketProvider implements GitProvider {
|
||||
async listDirectory(repoUrl: string, path: string, branch: string): Promise<DirEntry[]> {
|
||||
const { owner, repo } = parseRepoUrl(repoUrl);
|
||||
const listUrl = `https://api.bitbucket.org/2.0/repositories/${owner}/${repo}/src/${encodeURIComponent(branch)}/${path}`;
|
||||
const headers: Record<string, string> = {
|
||||
'User-Agent': 'PVEScripts-Local/1.0',
|
||||
};
|
||||
const token = process.env.BITBUCKET_APP_PASSWORD ?? process.env.BITBUCKET_TOKEN;
|
||||
if (token) {
|
||||
const auth = Buffer.from(`:${token}`).toString('base64');
|
||||
headers.Authorization = `Basic ${auth}`;
|
||||
}
|
||||
|
||||
const response = await fetch(listUrl, { headers });
|
||||
if (!response.ok) {
|
||||
throw new Error(`Bitbucket API error: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const body = (await response.json()) as { values?: { path: string; type: string }[] };
|
||||
const data = body.values ?? (Array.isArray(body) ? body : []);
|
||||
if (!Array.isArray(data)) {
|
||||
throw new Error('Bitbucket API returned unexpected response');
|
||||
}
|
||||
return data.map((item: { path: string; type: string }) => {
|
||||
const name = item.path.split('/').pop() ?? item.path;
|
||||
return {
|
||||
name,
|
||||
path: item.path,
|
||||
type: item.type === 'commit_directory' ? ('dir' as const) : ('file' as const),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async downloadRawFile(repoUrl: string, filePath: string, branch: string): Promise<string> {
|
||||
const { owner, repo } = parseRepoUrl(repoUrl);
|
||||
const rawUrl = `https://api.bitbucket.org/2.0/repositories/${owner}/${repo}/src/${encodeURIComponent(branch)}/${filePath}`;
|
||||
const headers: Record<string, string> = {
|
||||
'User-Agent': 'PVEScripts-Local/1.0',
|
||||
};
|
||||
const token = process.env.BITBUCKET_APP_PASSWORD ?? process.env.BITBUCKET_TOKEN;
|
||||
if (token) {
|
||||
const auth = Buffer.from(`:${token}`).toString('base64');
|
||||
headers.Authorization = `Basic ${auth}`;
|
||||
}
|
||||
|
||||
const response = await fetch(rawUrl, { headers });
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to download ${filePath}: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
return response.text();
|
||||
}
|
||||
}
|
||||
44
src/server/lib/gitProvider/custom.ts
Normal file
44
src/server/lib/gitProvider/custom.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import type { DirEntry, GitProvider } from "./types";
|
||||
import { parseRepoUrl } from "../repositoryUrlValidation";
|
||||
|
||||
export class CustomProvider implements GitProvider {
|
||||
async listDirectory(repoUrl: string, path: string, branch: string): Promise<DirEntry[]> {
|
||||
const { origin, owner, repo } = parseRepoUrl(repoUrl);
|
||||
const apiUrl = `${origin}/api/v1/repos/${owner}/${repo}/contents/${path}?ref=${encodeURIComponent(branch)}`;
|
||||
const headers: Record<string, string> = { "User-Agent": "PVEScripts-Local/1.0" };
|
||||
const token = process.env.GITEA_TOKEN ?? process.env.GIT_TOKEN;
|
||||
if (token) headers.Authorization = `token ${token}`;
|
||||
|
||||
const response = await fetch(apiUrl, { headers });
|
||||
if (!response.ok) {
|
||||
throw new Error(`Custom Git server: list directory failed (${response.status}).`);
|
||||
}
|
||||
const data = (await response.json()) as { type: string; name: string; path: string }[];
|
||||
if (!Array.isArray(data)) {
|
||||
const single = data as unknown as { type?: string; name?: string; path?: string };
|
||||
if (single?.name) {
|
||||
return [{ name: single.name, path: single.path ?? path, type: single.type === "dir" ? "dir" : "file" }];
|
||||
}
|
||||
throw new Error("Custom Git server returned unexpected response");
|
||||
}
|
||||
return data.map((item) => ({
|
||||
name: item.name,
|
||||
path: item.path,
|
||||
type: item.type === "dir" ? ("dir" as const) : ("file" as const),
|
||||
}));
|
||||
}
|
||||
|
||||
async downloadRawFile(repoUrl: string, filePath: string, branch: string): Promise<string> {
|
||||
const { origin, owner, repo } = parseRepoUrl(repoUrl);
|
||||
const rawUrl = `${origin}/${owner}/${repo}/raw/${encodeURIComponent(branch)}/${filePath}`;
|
||||
const headers: Record<string, string> = { "User-Agent": "PVEScripts-Local/1.0" };
|
||||
const token = process.env.GITEA_TOKEN ?? process.env.GIT_TOKEN;
|
||||
if (token) headers.Authorization = `token ${token}`;
|
||||
|
||||
const response = await fetch(rawUrl, { headers });
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to download ${filePath} from custom Git server (${response.status}).`);
|
||||
}
|
||||
return response.text();
|
||||
}
|
||||
}
|
||||
60
src/server/lib/gitProvider/github.ts
Normal file
60
src/server/lib/gitProvider/github.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import type { DirEntry, GitProvider } from './types';
|
||||
import { parseRepoUrl } from '../repositoryUrlValidation';
|
||||
|
||||
export class GitHubProvider implements GitProvider {
|
||||
async listDirectory(repoUrl: string, path: string, branch: string): Promise<DirEntry[]> {
|
||||
const { owner, repo } = parseRepoUrl(repoUrl);
|
||||
const apiUrl = `https://api.github.com/repos/${owner}/${repo}/contents/${path}?ref=${encodeURIComponent(branch)}`;
|
||||
const headers: Record<string, string> = {
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
'User-Agent': 'PVEScripts-Local/1.0',
|
||||
};
|
||||
const token = process.env.GITHUB_TOKEN;
|
||||
if (token) headers.Authorization = `token ${token}`;
|
||||
|
||||
const response = await fetch(apiUrl, { headers });
|
||||
if (!response.ok) {
|
||||
if (response.status === 403) {
|
||||
const err = new Error(
|
||||
`GitHub API rate limit exceeded. Consider setting GITHUB_TOKEN. Status: ${response.status} ${response.statusText}`
|
||||
);
|
||||
(err as Error & { name: string }).name = 'RateLimitError';
|
||||
throw err;
|
||||
}
|
||||
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = (await response.json()) as { type: string; name: string; path: string }[];
|
||||
if (!Array.isArray(data)) {
|
||||
throw new Error('GitHub API returned unexpected response');
|
||||
}
|
||||
return data.map((item) => ({
|
||||
name: item.name,
|
||||
path: item.path,
|
||||
type: item.type === 'dir' ? ('dir' as const) : ('file' as const),
|
||||
}));
|
||||
}
|
||||
|
||||
async downloadRawFile(repoUrl: string, filePath: string, branch: string): Promise<string> {
|
||||
const { owner, repo } = parseRepoUrl(repoUrl);
|
||||
const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${encodeURIComponent(branch)}/${filePath}`;
|
||||
const headers: Record<string, string> = {
|
||||
'User-Agent': 'PVEScripts-Local/1.0',
|
||||
};
|
||||
const token = process.env.GITHUB_TOKEN;
|
||||
if (token) headers.Authorization = `token ${token}`;
|
||||
|
||||
const response = await fetch(rawUrl, { headers });
|
||||
if (!response.ok) {
|
||||
if (response.status === 403) {
|
||||
const err = new Error(
|
||||
`GitHub rate limit exceeded while downloading ${filePath}. Consider setting GITHUB_TOKEN.`
|
||||
);
|
||||
(err as Error & { name: string }).name = 'RateLimitError';
|
||||
throw err;
|
||||
}
|
||||
throw new Error(`Failed to download ${filePath}: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
return response.text();
|
||||
}
|
||||
}
|
||||
58
src/server/lib/gitProvider/gitlab.ts
Normal file
58
src/server/lib/gitProvider/gitlab.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import type { DirEntry, GitProvider } from './types';
|
||||
import { parseRepoUrl } from '../repositoryUrlValidation';
|
||||
|
||||
export class GitLabProvider implements GitProvider {
|
||||
private getBaseUrl(repoUrl: string): string {
|
||||
const { origin } = parseRepoUrl(repoUrl);
|
||||
return origin;
|
||||
}
|
||||
|
||||
private getProjectId(repoUrl: string): string {
|
||||
const { owner, repo } = parseRepoUrl(repoUrl);
|
||||
return encodeURIComponent(`${owner}/${repo}`);
|
||||
}
|
||||
|
||||
async listDirectory(repoUrl: string, path: string, branch: string): Promise<DirEntry[]> {
|
||||
const baseUrl = this.getBaseUrl(repoUrl);
|
||||
const projectId = this.getProjectId(repoUrl);
|
||||
const apiUrl = `${baseUrl}/api/v4/projects/${projectId}/repository/tree?path=${encodeURIComponent(path)}&ref=${encodeURIComponent(branch)}&per_page=100`;
|
||||
const headers: Record<string, string> = {
|
||||
'User-Agent': 'PVEScripts-Local/1.0',
|
||||
};
|
||||
const token = process.env.GITLAB_TOKEN;
|
||||
if (token) headers['PRIVATE-TOKEN'] = token;
|
||||
|
||||
const response = await fetch(apiUrl, { headers });
|
||||
if (!response.ok) {
|
||||
throw new Error(`GitLab API error: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = (await response.json()) as { type: string; name: string; path: string }[];
|
||||
if (!Array.isArray(data)) {
|
||||
throw new Error('GitLab API returned unexpected response');
|
||||
}
|
||||
return data.map((item) => ({
|
||||
name: item.name,
|
||||
path: item.path,
|
||||
type: item.type === 'tree' ? ('dir' as const) : ('file' as const),
|
||||
}));
|
||||
}
|
||||
|
||||
async downloadRawFile(repoUrl: string, filePath: string, branch: string): Promise<string> {
|
||||
const baseUrl = this.getBaseUrl(repoUrl);
|
||||
const projectId = this.getProjectId(repoUrl);
|
||||
const encodedPath = encodeURIComponent(filePath);
|
||||
const rawUrl = `${baseUrl}/api/v4/projects/${projectId}/repository/files/${encodedPath}/raw?ref=${encodeURIComponent(branch)}`;
|
||||
const headers: Record<string, string> = {
|
||||
'User-Agent': 'PVEScripts-Local/1.0',
|
||||
};
|
||||
const token = process.env.GITLAB_TOKEN;
|
||||
if (token) headers['PRIVATE-TOKEN'] = token;
|
||||
|
||||
const response = await fetch(rawUrl, { headers });
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to download ${filePath}: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
return response.text();
|
||||
}
|
||||
}
|
||||
1
src/server/lib/gitProvider/index.js
Normal file
1
src/server/lib/gitProvider/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export { listDirectory, downloadRawFile, getRepoProvider } from "./index.ts";
|
||||
28
src/server/lib/gitProvider/index.ts
Normal file
28
src/server/lib/gitProvider/index.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import type { DirEntry, GitProvider } from "./types";
|
||||
import { getRepoProvider } from "../repositoryUrlValidation";
|
||||
import { GitHubProvider } from "./github";
|
||||
import { GitLabProvider } from "./gitlab";
|
||||
import { BitbucketProvider } from "./bitbucket";
|
||||
import { CustomProvider } from "./custom";
|
||||
|
||||
const providers: Record<string, GitProvider> = {
|
||||
github: new GitHubProvider(),
|
||||
gitlab: new GitLabProvider(),
|
||||
bitbucket: new BitbucketProvider(),
|
||||
custom: new CustomProvider(),
|
||||
};
|
||||
|
||||
export type { DirEntry, GitProvider };
|
||||
export { getRepoProvider };
|
||||
|
||||
export function getGitProvider(repoUrl: string): GitProvider {
|
||||
return providers[getRepoProvider(repoUrl)]!;
|
||||
}
|
||||
|
||||
export async function listDirectory(repoUrl: string, path: string, branch: string): Promise<DirEntry[]> {
|
||||
return getGitProvider(repoUrl).listDirectory(repoUrl, path, branch);
|
||||
}
|
||||
|
||||
export async function downloadRawFile(repoUrl: string, filePath: string, branch: string): Promise<string> {
|
||||
return getGitProvider(repoUrl).downloadRawFile(repoUrl, filePath, branch);
|
||||
}
|
||||
14
src/server/lib/gitProvider/types.ts
Normal file
14
src/server/lib/gitProvider/types.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Git provider interface for listing and downloading repository files.
|
||||
*/
|
||||
|
||||
export type DirEntry = {
|
||||
name: string;
|
||||
path: string;
|
||||
type: 'file' | 'dir';
|
||||
};
|
||||
|
||||
export interface GitProvider {
|
||||
listDirectory(repoUrl: string, path: string, branch: string): Promise<DirEntry[]>;
|
||||
downloadRawFile(repoUrl: string, filePath: string, branch: string): Promise<string>;
|
||||
}
|
||||
37
src/server/lib/repositoryUrlValidation.js
Normal file
37
src/server/lib/repositoryUrlValidation.js
Normal file
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Repository URL validation (JS mirror for server.js).
|
||||
*/
|
||||
const VALID_REPO_URL =
|
||||
/^(https?:\/\/)(github\.com|gitlab\.com|bitbucket\.org|[^/]+)\/[^/]+\/[^/]+$/;
|
||||
|
||||
export const REPO_URL_ERROR_MESSAGE =
|
||||
'Invalid repository URL. Supported: GitHub, GitLab, Bitbucket, and custom Git servers (e.g. https://host/owner/repo).';
|
||||
|
||||
export function isValidRepositoryUrl(url) {
|
||||
if (typeof url !== 'string' || !url.trim()) return false;
|
||||
return VALID_REPO_URL.test(url.trim());
|
||||
}
|
||||
|
||||
export function getRepoProvider(url) {
|
||||
if (!isValidRepositoryUrl(url)) throw new Error(REPO_URL_ERROR_MESSAGE);
|
||||
const normalized = url.trim().toLowerCase();
|
||||
if (normalized.includes('github.com')) return 'github';
|
||||
if (normalized.includes('gitlab.com')) return 'gitlab';
|
||||
if (normalized.includes('bitbucket.org')) return 'bitbucket';
|
||||
return 'custom';
|
||||
}
|
||||
|
||||
export function parseRepoUrl(url) {
|
||||
if (!isValidRepositoryUrl(url)) throw new Error(REPO_URL_ERROR_MESSAGE);
|
||||
try {
|
||||
const u = new URL(url.trim());
|
||||
const pathParts = u.pathname.replace(/^\/+/, '').replace(/\.git\/?$/, '').split('/');
|
||||
return {
|
||||
origin: u.origin,
|
||||
owner: pathParts[0] ?? '',
|
||||
repo: pathParts[1] ?? '',
|
||||
};
|
||||
} catch {
|
||||
throw new Error(REPO_URL_ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
57
src/server/lib/repositoryUrlValidation.ts
Normal file
57
src/server/lib/repositoryUrlValidation.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Repository URL validation and provider detection.
|
||||
* Supports GitHub, GitLab, Bitbucket, and custom Git servers.
|
||||
*/
|
||||
|
||||
const VALID_REPO_URL =
|
||||
/^(https?:\/\/)(github\.com|gitlab\.com|bitbucket\.org|[^/]+)\/[^/]+\/[^/]+$/;
|
||||
|
||||
export const REPO_URL_ERROR_MESSAGE =
|
||||
'Invalid repository URL. Supported: GitHub, GitLab, Bitbucket, and custom Git servers (e.g. https://host/owner/repo).';
|
||||
|
||||
export type RepoProvider = 'github' | 'gitlab' | 'bitbucket' | 'custom';
|
||||
|
||||
/**
|
||||
* Check if a string is a valid repository URL (format only).
|
||||
*/
|
||||
export function isValidRepositoryUrl(url: string): boolean {
|
||||
if (typeof url !== 'string' || !url.trim()) return false;
|
||||
return VALID_REPO_URL.test(url.trim());
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect the Git provider from a repository URL.
|
||||
*/
|
||||
export function getRepoProvider(url: string): RepoProvider {
|
||||
if (!isValidRepositoryUrl(url)) {
|
||||
throw new Error(REPO_URL_ERROR_MESSAGE);
|
||||
}
|
||||
const normalized = url.trim().toLowerCase();
|
||||
if (normalized.includes('github.com')) return 'github';
|
||||
if (normalized.includes('gitlab.com')) return 'gitlab';
|
||||
if (normalized.includes('bitbucket.org')) return 'bitbucket';
|
||||
return 'custom';
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse owner and repo from a repository URL (path segments).
|
||||
* Works for GitHub, GitLab, Bitbucket, and custom (host/owner/repo).
|
||||
*/
|
||||
export function parseRepoUrl(url: string): { origin: string; owner: string; repo: string } {
|
||||
if (!isValidRepositoryUrl(url)) {
|
||||
throw new Error(REPO_URL_ERROR_MESSAGE);
|
||||
}
|
||||
try {
|
||||
const u = new URL(url.trim());
|
||||
const pathParts = u.pathname.replace(/^\/+/, '').replace(/\.git\/?$/, '').split('/');
|
||||
const owner = pathParts[0] ?? '';
|
||||
const repo = pathParts[1] ?? '';
|
||||
return {
|
||||
origin: u.origin,
|
||||
owner,
|
||||
repo,
|
||||
};
|
||||
} catch {
|
||||
throw new Error(REPO_URL_ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
@@ -327,13 +327,16 @@ class BackupService {
|
||||
// PBS supports PBS_PASSWORD and PBS_REPOSITORY environment variables for non-interactive login
|
||||
const repository = `root@pam@${pbsIp}:${pbsDatastore}`;
|
||||
|
||||
// Escape password for shell safety (single quotes)
|
||||
// Escape password and fingerprint for shell safety (single quotes)
|
||||
const escapedPassword = credential.pbs_password.replace(/'/g, "'\\''");
|
||||
|
||||
// Use PBS_PASSWORD environment variable for non-interactive authentication
|
||||
// Auto-accept fingerprint by piping "y" to stdin
|
||||
// PBS will use PBS_PASSWORD env var if available, avoiding interactive prompt
|
||||
const fullCommand = `echo "y" | PBS_PASSWORD='${escapedPassword}' PBS_REPOSITORY='${repository}' timeout 10 proxmox-backup-client login --repository ${repository} 2>&1`;
|
||||
const fingerprint = credential.pbs_fingerprint?.trim() ?? '';
|
||||
const escapedFingerprint = fingerprint ? fingerprint.replace(/'/g, "'\\''") : '';
|
||||
const envParts = [`PBS_PASSWORD='${escapedPassword}'`, `PBS_REPOSITORY='${repository}'`];
|
||||
if (escapedFingerprint) {
|
||||
envParts.push(`PBS_FINGERPRINT='${escapedFingerprint}'`);
|
||||
}
|
||||
const envStr = envParts.join(' ');
|
||||
const fullCommand = `${envStr} timeout 10 proxmox-backup-client login --repository ${repository} 2>&1`;
|
||||
|
||||
console.log(`[BackupService] Logging into PBS: ${repository}`);
|
||||
|
||||
@@ -419,9 +422,12 @@ class BackupService {
|
||||
|
||||
// Build full repository string: root@pam@<IP>:<DATASTORE>
|
||||
const repository = `root@pam@${pbsIp}:${pbsDatastore}`;
|
||||
|
||||
const fingerprint = credential.pbs_fingerprint?.trim() ?? '';
|
||||
const escapedFingerprint = fingerprint ? fingerprint.replace(/'/g, "'\\''") : '';
|
||||
const snapshotEnvParts = escapedFingerprint ? [`PBS_FINGERPRINT='${escapedFingerprint}'`] : [];
|
||||
const snapshotEnvStr = snapshotEnvParts.length ? snapshotEnvParts.join(' ') + ' ' : '';
|
||||
// Use correct command: snapshot list ct/<CT_ID> --repository <full_repo_string>
|
||||
const command = `timeout 30 proxmox-backup-client snapshot list ct/${ctId} --repository ${repository} 2>&1 || echo "PBS_ERROR"`;
|
||||
const command = `${snapshotEnvStr}timeout 30 proxmox-backup-client snapshot list ct/${ctId} --repository ${repository} 2>&1 || echo "PBS_ERROR"`;
|
||||
let output = '';
|
||||
|
||||
console.log(`[BackupService] Discovering PBS backups for CT ${ctId} on repository ${repository}`);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { writeFile, mkdir, readdir, readFile } from 'fs/promises';
|
||||
import { join } from 'path';
|
||||
import { repositoryService } from './repositoryService.js';
|
||||
import { listDirectory, downloadRawFile } from '../lib/gitProvider/index.js';
|
||||
|
||||
// Get environment variables
|
||||
const getEnv = () => ({
|
||||
@@ -28,76 +29,9 @@ class GitHubJsonService {
|
||||
}
|
||||
}
|
||||
|
||||
getBaseUrl(repoUrl) {
|
||||
const urlMatch = /github\.com\/([^\/]+)\/([^\/]+)/.exec(repoUrl);
|
||||
if (!urlMatch) {
|
||||
throw new Error(`Invalid GitHub repository URL: ${repoUrl}`);
|
||||
}
|
||||
|
||||
const [, owner, repo] = urlMatch;
|
||||
return `https://api.github.com/repos/${owner}/${repo}`;
|
||||
}
|
||||
|
||||
extractRepoPath(repoUrl) {
|
||||
const match = /github\.com\/([^\/]+)\/([^\/]+)/.exec(repoUrl);
|
||||
if (!match) {
|
||||
throw new Error('Invalid GitHub repository URL');
|
||||
}
|
||||
return `${match[1]}/${match[2]}`;
|
||||
}
|
||||
|
||||
async fetchFromGitHub(repoUrl, endpoint) {
|
||||
const baseUrl = this.getBaseUrl(repoUrl);
|
||||
const env = getEnv();
|
||||
|
||||
const headers = {
|
||||
'Accept': 'application/vnd.github.v3+json',
|
||||
'User-Agent': 'PVEScripts-Local/1.0',
|
||||
};
|
||||
|
||||
if (env.GITHUB_TOKEN) {
|
||||
headers.Authorization = `token ${env.GITHUB_TOKEN}`;
|
||||
}
|
||||
|
||||
const response = await fetch(`${baseUrl}${endpoint}`, { headers });
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 403) {
|
||||
const error = new Error(`GitHub API rate limit exceeded. Consider setting GITHUB_TOKEN for higher limits. Status: ${response.status} ${response.statusText}`);
|
||||
error.name = 'RateLimitError';
|
||||
throw error;
|
||||
}
|
||||
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async downloadJsonFile(repoUrl, filePath) {
|
||||
this.initializeConfig();
|
||||
const repoPath = this.extractRepoPath(repoUrl);
|
||||
const rawUrl = `https://raw.githubusercontent.com/${repoPath}/${this.branch}/${filePath}`;
|
||||
const env = getEnv();
|
||||
|
||||
const headers = {
|
||||
'User-Agent': 'PVEScripts-Local/1.0',
|
||||
};
|
||||
|
||||
if (env.GITHUB_TOKEN) {
|
||||
headers.Authorization = `token ${env.GITHUB_TOKEN}`;
|
||||
}
|
||||
|
||||
const response = await fetch(rawUrl, { headers });
|
||||
if (!response.ok) {
|
||||
if (response.status === 403) {
|
||||
const error = new Error(`GitHub rate limit exceeded while downloading ${filePath}. Consider setting GITHUB_TOKEN for higher limits.`);
|
||||
error.name = 'RateLimitError';
|
||||
throw error;
|
||||
}
|
||||
throw new Error(`Failed to download ${filePath}: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const content = await response.text();
|
||||
const content = await downloadRawFile(repoUrl, filePath, this.branch);
|
||||
const script = JSON.parse(content);
|
||||
script.repository_url = repoUrl;
|
||||
return script;
|
||||
@@ -105,16 +39,13 @@ class GitHubJsonService {
|
||||
|
||||
async getJsonFiles(repoUrl) {
|
||||
this.initializeConfig();
|
||||
|
||||
try {
|
||||
const files = await this.fetchFromGitHub(
|
||||
repoUrl,
|
||||
`/contents/${this.jsonFolder}?ref=${this.branch}`
|
||||
);
|
||||
|
||||
return files.filter(file => file.name.endsWith('.json'));
|
||||
const entries = await listDirectory(repoUrl, this.jsonFolder, this.branch);
|
||||
return entries
|
||||
.filter((e) => e.type === 'file' && e.name.endsWith('.json'))
|
||||
.map((e) => ({ name: e.name, path: e.path }));
|
||||
} catch (error) {
|
||||
console.error(`Error fetching JSON files from GitHub (${repoUrl}):`, error);
|
||||
console.error(`Error fetching JSON files from repository (${repoUrl}):`, error);
|
||||
throw new Error(`Failed to fetch script files from repository: ${repoUrl}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { join } from 'path';
|
||||
import { env } from '../../env.js';
|
||||
import type { Script, ScriptCard, GitHubFile } from '../../types/script';
|
||||
import { repositoryService } from './repositoryService';
|
||||
import { listDirectory, downloadRawFile } from '~/server/lib/gitProvider';
|
||||
|
||||
export class GitHubJsonService {
|
||||
private branch: string | null = null;
|
||||
@@ -22,96 +23,24 @@ export class GitHubJsonService {
|
||||
}
|
||||
}
|
||||
|
||||
private getBaseUrl(repoUrl: string): string {
|
||||
const urlMatch = /github\.com\/([^\/]+)\/([^\/]+)/.exec(repoUrl);
|
||||
if (!urlMatch) {
|
||||
throw new Error(`Invalid GitHub repository URL: ${repoUrl}`);
|
||||
}
|
||||
|
||||
const [, owner, repo] = urlMatch;
|
||||
return `https://api.github.com/repos/${owner}/${repo}`;
|
||||
}
|
||||
|
||||
private extractRepoPath(repoUrl: string): string {
|
||||
const match = /github\.com\/([^\/]+)\/([^\/]+)/.exec(repoUrl);
|
||||
if (!match) {
|
||||
throw new Error('Invalid GitHub repository URL');
|
||||
}
|
||||
return `${match[1]}/${match[2]}`;
|
||||
}
|
||||
|
||||
private async fetchFromGitHub<T>(repoUrl: string, endpoint: string): Promise<T> {
|
||||
const baseUrl = this.getBaseUrl(repoUrl);
|
||||
|
||||
const headers: HeadersInit = {
|
||||
'Accept': 'application/vnd.github.v3+json',
|
||||
'User-Agent': 'PVEScripts-Local/1.0',
|
||||
};
|
||||
|
||||
// Add GitHub token authentication if available
|
||||
if (env.GITHUB_TOKEN) {
|
||||
headers.Authorization = `token ${env.GITHUB_TOKEN}`;
|
||||
}
|
||||
|
||||
const response = await fetch(`${baseUrl}${endpoint}`, { headers });
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 403) {
|
||||
const error = new Error(`GitHub API rate limit exceeded. Consider setting GITHUB_TOKEN for higher limits. Status: ${response.status} ${response.statusText}`);
|
||||
error.name = 'RateLimitError';
|
||||
throw error;
|
||||
}
|
||||
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data as T;
|
||||
}
|
||||
|
||||
private async downloadJsonFile(repoUrl: string, filePath: string): Promise<Script> {
|
||||
this.initializeConfig();
|
||||
const repoPath = this.extractRepoPath(repoUrl);
|
||||
const rawUrl = `https://raw.githubusercontent.com/${repoPath}/${this.branch!}/${filePath}`;
|
||||
|
||||
const headers: HeadersInit = {
|
||||
'User-Agent': 'PVEScripts-Local/1.0',
|
||||
};
|
||||
|
||||
// Add GitHub token authentication if available
|
||||
if (env.GITHUB_TOKEN) {
|
||||
headers.Authorization = `token ${env.GITHUB_TOKEN}`;
|
||||
}
|
||||
|
||||
const response = await fetch(rawUrl, { headers });
|
||||
if (!response.ok) {
|
||||
if (response.status === 403) {
|
||||
const error = new Error(`GitHub rate limit exceeded while downloading ${filePath}. Consider setting GITHUB_TOKEN for higher limits. Status: ${response.status} ${response.statusText}`);
|
||||
error.name = 'RateLimitError';
|
||||
throw error;
|
||||
}
|
||||
throw new Error(`Failed to download ${filePath}: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const content = await response.text();
|
||||
const content = await downloadRawFile(repoUrl, filePath, this.branch!);
|
||||
const script = JSON.parse(content) as Script;
|
||||
// Add repository_url to script
|
||||
script.repository_url = repoUrl;
|
||||
return script;
|
||||
}
|
||||
|
||||
async getJsonFiles(repoUrl: string): Promise<GitHubFile[]> {
|
||||
this.initializeConfig();
|
||||
|
||||
try {
|
||||
const files = await this.fetchFromGitHub<GitHubFile[]>(
|
||||
repoUrl,
|
||||
`/contents/${this.jsonFolder!}?ref=${this.branch!}`
|
||||
);
|
||||
|
||||
// Filter for JSON files only
|
||||
return files.filter(file => file.name.endsWith('.json'));
|
||||
const entries = await listDirectory(repoUrl, this.jsonFolder!, this.branch!);
|
||||
const files: GitHubFile[] = entries
|
||||
.filter((e) => e.type === 'file' && e.name.endsWith('.json'))
|
||||
.map((e) => ({ name: e.name, path: e.path } as GitHubFile));
|
||||
return files;
|
||||
} catch (error) {
|
||||
console.error(`Error fetching JSON files from GitHub (${repoUrl}):`, error);
|
||||
console.error(`Error fetching JSON files from repository (${repoUrl}):`, error);
|
||||
throw new Error(`Failed to fetch script files from repository: ${repoUrl}`);
|
||||
}
|
||||
}
|
||||
@@ -233,8 +162,7 @@ export class GitHubJsonService {
|
||||
try {
|
||||
console.log(`Starting JSON sync from repository: ${repoUrl}`);
|
||||
|
||||
// Get file list from GitHub
|
||||
console.log(`Fetching file list from GitHub (${repoUrl})...`);
|
||||
console.log(`Fetching file list from repository (${repoUrl})...`);
|
||||
const githubFiles = await this.getJsonFiles(repoUrl);
|
||||
console.log(`Found ${githubFiles.length} JSON files in repository ${repoUrl}`);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// JavaScript wrapper for repositoryService (for use with node server.js)
|
||||
import { prisma } from '../db.js';
|
||||
import { isValidRepositoryUrl, REPO_URL_ERROR_MESSAGE } from '../lib/repositoryUrlValidation.js';
|
||||
|
||||
class RepositoryService {
|
||||
/**
|
||||
@@ -89,9 +90,8 @@ class RepositoryService {
|
||||
* Create a new repository
|
||||
*/
|
||||
async createRepository(data) {
|
||||
// Validate GitHub URL
|
||||
if (!data.url.match(/^https:\/\/github\.com\/[^\/]+\/[^\/]+$/)) {
|
||||
throw new Error('Invalid GitHub repository URL. Format: https://github.com/owner/repo');
|
||||
if (!isValidRepositoryUrl(data.url)) {
|
||||
throw new Error(REPO_URL_ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
// Check for duplicates
|
||||
@@ -122,10 +122,9 @@ class RepositoryService {
|
||||
* Update repository
|
||||
*/
|
||||
async updateRepository(id, data) {
|
||||
// If updating URL, validate it
|
||||
if (data.url) {
|
||||
if (!data.url.match(/^https:\/\/github\.com\/[^\/]+\/[^\/]+$/)) {
|
||||
throw new Error('Invalid GitHub repository URL. Format: https://github.com/owner/repo');
|
||||
if (!isValidRepositoryUrl(data.url)) {
|
||||
throw new Error(REPO_URL_ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
// Check for duplicates (excluding current repo)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/prefer-regexp-exec */
|
||||
import { prisma } from '../db';
|
||||
import { isValidRepositoryUrl, REPO_URL_ERROR_MESSAGE } from '../lib/repositoryUrlValidation';
|
||||
|
||||
export class RepositoryService {
|
||||
/**
|
||||
@@ -93,9 +93,8 @@ export class RepositoryService {
|
||||
enabled?: boolean;
|
||||
priority?: number;
|
||||
}) {
|
||||
// Validate GitHub URL
|
||||
if (!data.url.match(/^https:\/\/github\.com\/[^\/]+\/[^\/]+$/)) {
|
||||
throw new Error('Invalid GitHub repository URL. Format: https://github.com/owner/repo');
|
||||
if (!isValidRepositoryUrl(data.url)) {
|
||||
throw new Error(REPO_URL_ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
// Check for duplicates
|
||||
@@ -130,10 +129,9 @@ export class RepositoryService {
|
||||
url?: string;
|
||||
priority?: number;
|
||||
}) {
|
||||
// If updating URL, validate it
|
||||
if (data.url) {
|
||||
if (!data.url.match(/^https:\/\/github\.com\/[^\/]+\/[^\/]+$/)) {
|
||||
throw new Error('Invalid GitHub repository URL. Format: https://github.com/owner/repo');
|
||||
if (!isValidRepositoryUrl(data.url)) {
|
||||
throw new Error(REPO_URL_ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
// Check for duplicates (excluding current repo)
|
||||
|
||||
@@ -250,9 +250,16 @@ class RestoreService {
|
||||
const targetFolder = `/var/lib/vz/dump/vzdump-lxc-${ctId}-${snapshotNameForPath}`;
|
||||
const targetTar = `${targetFolder}.tar`;
|
||||
|
||||
// Use PBS_PASSWORD env var and add timeout for long downloads
|
||||
// Use PBS_PASSWORD env var and add timeout for long downloads; PBS_FINGERPRINT when set for cert validation
|
||||
const escapedPassword = credential.pbs_password.replace(/'/g, "'\\''");
|
||||
const restoreCommand = `PBS_PASSWORD='${escapedPassword}' PBS_REPOSITORY='${repository}' timeout 300 proxmox-backup-client restore "${snapshotPath}" root.pxar "${targetFolder}" --repository '${repository}' 2>&1`;
|
||||
const fingerprint = credential.pbs_fingerprint?.trim() ?? '';
|
||||
const escapedFingerprint = fingerprint ? fingerprint.replace(/'/g, "'\\''") : '';
|
||||
const restoreEnvParts = [`PBS_PASSWORD='${escapedPassword}'`, `PBS_REPOSITORY='${repository}'`];
|
||||
if (escapedFingerprint) {
|
||||
restoreEnvParts.push(`PBS_FINGERPRINT='${escapedFingerprint}'`);
|
||||
}
|
||||
const restoreEnvStr = restoreEnvParts.join(' ');
|
||||
const restoreCommand = `${restoreEnvStr} timeout 300 proxmox-backup-client restore "${snapshotPath}" root.pxar "${targetFolder}" --repository '${repository}' 2>&1`;
|
||||
|
||||
let output = '';
|
||||
let exitCode = 0;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Real JavaScript implementation for script downloading
|
||||
import { join } from 'path';
|
||||
import { writeFile, mkdir, access, readFile, unlink } from 'fs/promises';
|
||||
import { downloadRawFile } from '../lib/gitProvider/index.js';
|
||||
|
||||
export class ScriptDownloaderService {
|
||||
constructor() {
|
||||
@@ -82,51 +83,18 @@ export class ScriptDownloaderService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract repository path from GitHub URL
|
||||
* @param {string} repoUrl - The GitHub repository URL
|
||||
* @returns {string}
|
||||
*/
|
||||
extractRepoPath(repoUrl) {
|
||||
const match = /github\.com\/([^\/]+)\/([^\/]+)/.exec(repoUrl);
|
||||
if (!match) {
|
||||
throw new Error(`Invalid GitHub repository URL: ${repoUrl}`);
|
||||
}
|
||||
return `${match[1]}/${match[2]}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a file from GitHub
|
||||
* @param {string} repoUrl - The GitHub repository URL
|
||||
* Download a file from the repository (GitHub, GitLab, Bitbucket, or custom)
|
||||
* @param {string} repoUrl - The repository URL
|
||||
* @param {string} filePath - The file path within the repository
|
||||
* @param {string} [branch] - The branch to download from
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
async downloadFileFromGitHub(repoUrl, filePath, branch = 'main') {
|
||||
this.initializeConfig();
|
||||
async downloadFileFromRepo(repoUrl, filePath, branch = 'main') {
|
||||
if (!repoUrl) {
|
||||
throw new Error('Repository URL is not set');
|
||||
}
|
||||
|
||||
const repoPath = this.extractRepoPath(repoUrl);
|
||||
const url = `https://raw.githubusercontent.com/${repoPath}/${branch}/${filePath}`;
|
||||
|
||||
/** @type {Record<string, string>} */
|
||||
const headers = {
|
||||
'User-Agent': 'PVEScripts-Local/1.0',
|
||||
};
|
||||
|
||||
// Add GitHub token authentication if available
|
||||
if (process.env.GITHUB_TOKEN) {
|
||||
headers.Authorization = `token ${process.env.GITHUB_TOKEN}`;
|
||||
}
|
||||
|
||||
console.log(`Downloading from GitHub: ${url}`);
|
||||
const response = await fetch(url, { headers });
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to download ${filePath} from ${repoUrl}: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
return response.text();
|
||||
console.log(`Downloading from repository: ${repoUrl} (${filePath})`);
|
||||
return downloadRawFile(repoUrl, filePath, branch);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -184,9 +152,8 @@ export class ScriptDownloaderService {
|
||||
const fileName = scriptPath.split('/').pop();
|
||||
|
||||
if (fileName) {
|
||||
// Download from GitHub using the script's repository URL
|
||||
console.log(`Downloading script file: ${scriptPath} from ${repoUrl}`);
|
||||
const content = await this.downloadFileFromGitHub(repoUrl, scriptPath, branch);
|
||||
const content = await this.downloadFileFromRepo(repoUrl, scriptPath, branch);
|
||||
|
||||
// Determine target directory based on script path
|
||||
let targetDir;
|
||||
@@ -250,7 +217,7 @@ export class ScriptDownloaderService {
|
||||
const installScriptName = `${script.slug}-install.sh`;
|
||||
try {
|
||||
console.log(`Downloading install script: install/${installScriptName} from ${repoUrl}`);
|
||||
const installContent = await this.downloadFileFromGitHub(repoUrl, `install/${installScriptName}`, branch);
|
||||
const installContent = await this.downloadFileFromRepo(repoUrl, `install/${installScriptName}`, branch);
|
||||
const localInstallPath = join(this.scriptsDirectory, 'install', installScriptName);
|
||||
await writeFile(localInstallPath, installContent, 'utf-8');
|
||||
files.push(`install/${installScriptName}`);
|
||||
@@ -274,7 +241,7 @@ export class ScriptDownloaderService {
|
||||
const alpineInstallScriptName = `alpine-${script.slug}-install.sh`;
|
||||
try {
|
||||
console.log(`[${script.slug}] Downloading alpine install script: install/${alpineInstallScriptName} from ${repoUrl}`);
|
||||
const alpineInstallContent = await this.downloadFileFromGitHub(repoUrl, `install/${alpineInstallScriptName}`, branch);
|
||||
const alpineInstallContent = await this.downloadFileFromRepo(repoUrl, `install/${alpineInstallScriptName}`, branch);
|
||||
const localAlpineInstallPath = join(this.scriptsDirectory, 'install', alpineInstallScriptName);
|
||||
await writeFile(localAlpineInstallPath, alpineInstallContent, 'utf-8');
|
||||
files.push(`install/${alpineInstallScriptName}`);
|
||||
@@ -681,7 +648,7 @@ export class ScriptDownloaderService {
|
||||
console.log(`[Comparison] Local file size: ${localContent.length} bytes`);
|
||||
|
||||
// Download remote content from the script's repository
|
||||
const remoteContent = await this.downloadFileFromGitHub(repoUrl, remotePath, branch);
|
||||
const remoteContent = await this.downloadFileFromRepo(repoUrl, remotePath, branch);
|
||||
console.log(`[Comparison] Remote file size: ${remoteContent.length} bytes`);
|
||||
|
||||
// Apply modification only for CT scripts, not for other script types
|
||||
@@ -739,7 +706,7 @@ export class ScriptDownloaderService {
|
||||
// Find the corresponding script path in install_methods
|
||||
const method = script.install_methods?.find(m => m.script === filePath);
|
||||
if (method?.script) {
|
||||
const downloadedContent = await this.downloadFileFromGitHub(repoUrl, method.script, branch);
|
||||
const downloadedContent = await this.downloadFileFromRepo(repoUrl, method.script, branch);
|
||||
remoteContent = this.modifyScriptContent(downloadedContent);
|
||||
}
|
||||
} catch {
|
||||
@@ -756,7 +723,7 @@ export class ScriptDownloaderService {
|
||||
}
|
||||
|
||||
try {
|
||||
remoteContent = await this.downloadFileFromGitHub(repoUrl, filePath, branch);
|
||||
remoteContent = await this.downloadFileFromRepo(repoUrl, filePath, branch);
|
||||
} catch {
|
||||
// Error downloading remote install script
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user