Compare commits
9 Commits
fix/312
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b442bcf459 | ||
|
|
351ba09f4e | ||
|
|
580986abfa | ||
|
|
e1d270d52c | ||
|
|
20dbcae42a | ||
|
|
8e8c724392 | ||
|
|
201b33ec84 | ||
|
|
6d2df9929c | ||
|
|
f33504baf5 |
80
package-lock.json
generated
80
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "pve-scripts-local",
|
||||
"version": "0.1.0",
|
||||
"version": "0.5.6",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "pve-scripts-local",
|
||||
"version": "0.1.0",
|
||||
"version": "0.5.6",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@prisma/adapter-better-sqlite3": "^7.3.0",
|
||||
@@ -66,7 +66,7 @@
|
||||
"@vitest/ui": "^4.0.17",
|
||||
"baseline-browser-mapping": "^2.9.15",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-config-next": "^16.1.3",
|
||||
"eslint-config-next": "^16.1.6",
|
||||
"jsdom": "^27.4.0",
|
||||
"next": ">=16.1.5",
|
||||
"postcss": "^8.5.6",
|
||||
@@ -196,7 +196,6 @@
|
||||
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
"@babel/generator": "^7.28.5",
|
||||
@@ -606,7 +605,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
@@ -650,7 +648,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@@ -660,8 +657,7 @@
|
||||
"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
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@electric-sql/pglite-socket": {
|
||||
"version": "0.0.20",
|
||||
@@ -2003,9 +1999,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@next/eslint-plugin-next": {
|
||||
"version": "16.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.1.3.tgz",
|
||||
"integrity": "sha512-MqBh3ltFAy0AZCRFVdjVjjeV7nEszJDaVIpDAnkQcn8U9ib6OEwkSnuK6xdYxMGPhV/Y4IlY6RbDipPOpLfBqQ==",
|
||||
"version": "16.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.1.6.tgz",
|
||||
"integrity": "sha512-/Qq3PTagA6+nYVfryAtQ7/9FEr/6YVyvOtl6rZnGsbReGLf0jZU6gkpr1FuChAQpvV46a78p4cmHOVP8mbfSMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -2298,6 +2294,20 @@
|
||||
"url": "https://dotenvx.com"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/config/node_modules/magicast": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz",
|
||||
"integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.25.4",
|
||||
"@babel/types": "^7.25.4",
|
||||
"source-map-js": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/debug": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.2.0.tgz",
|
||||
@@ -3781,7 +3791,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.20.tgz",
|
||||
"integrity": "sha512-vXBxa+qeyveVO7OA0jX1z+DeyCA4JKnThKv411jd5SORpBKgkcVnYKCiBgECvADvniBX7tobwBmg01qq9JmMJw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@tanstack/query-core": "5.90.20"
|
||||
},
|
||||
@@ -3891,7 +3900,6 @@
|
||||
"https://trpc.io/sponsor"
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@trpc/server": "11.8.1",
|
||||
"typescript": ">=5.7.2"
|
||||
@@ -3922,7 +3930,6 @@
|
||||
"https://trpc.io/sponsor"
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"typescript": ">=5.7.2"
|
||||
}
|
||||
@@ -3943,7 +3950,8 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
|
||||
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@types/babel__core": {
|
||||
"version": "7.20.5",
|
||||
@@ -4129,7 +4137,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.8.tgz",
|
||||
"integrity": "sha512-3MbSL37jEchWZz2p2mjntRZtPt837ij10ApxKfgmXCTuHWagYg7iA5bqPw6C8BMPfwidlvfPI/fxOc42HLhcyg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.2.2"
|
||||
}
|
||||
@@ -4140,7 +4147,6 @@
|
||||
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19.2.0"
|
||||
}
|
||||
@@ -4214,7 +4220,6 @@
|
||||
"integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.54.0",
|
||||
"@typescript-eslint/types": "8.54.0",
|
||||
@@ -4869,7 +4874,6 @@
|
||||
"integrity": "sha512-hRDjg6dlDz7JlZAvjbiCdAJ3SDG+NH8tjZe21vjxfvT2ssYAn72SRXMge3dKKABm3bIJ3C+3wdunIdur8PHEAw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vitest/utils": "4.0.17",
|
||||
"fflate": "^0.8.2",
|
||||
@@ -4927,7 +4931,6 @@
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -4978,6 +4981,7 @@
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@@ -5440,7 +5444,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.8.25",
|
||||
"caniuse-lite": "^1.0.30001754",
|
||||
@@ -6175,7 +6178,8 @@
|
||||
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
|
||||
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "17.2.3",
|
||||
@@ -6534,7 +6538,6 @@
|
||||
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.8.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
@@ -6590,13 +6593,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-config-next": {
|
||||
"version": "16.1.3",
|
||||
"resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.1.3.tgz",
|
||||
"integrity": "sha512-q2Z87VSsoJcv+vgR+Dm8NPRf+rErXcRktuBR5y3umo/j5zLjIWH7rqBCh3X804gUGKbOrqbgsLUkqDE35C93Gw==",
|
||||
"version": "16.1.6",
|
||||
"resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.1.6.tgz",
|
||||
"integrity": "sha512-vKq40io2B0XtkkNDYyleATwblNt8xuh3FWp8SpSz3pt7P01OkBFlKsJZ2mWt5WsCySlDQLckb1zMY9yE9Qy0LA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@next/eslint-plugin-next": "16.1.3",
|
||||
"@next/eslint-plugin-next": "16.1.6",
|
||||
"eslint-import-resolver-node": "^0.3.6",
|
||||
"eslint-import-resolver-typescript": "^3.5.2",
|
||||
"eslint-plugin-import": "^2.32.0",
|
||||
@@ -6720,7 +6723,6 @@
|
||||
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rtsao/scc": "^1.1.0",
|
||||
"array-includes": "^3.1.9",
|
||||
@@ -7740,7 +7742,6 @@
|
||||
"integrity": "sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=16.9.0"
|
||||
}
|
||||
@@ -8514,7 +8515,6 @@
|
||||
"integrity": "sha512-mjzqwWRD9Y1J1KUi7W97Gja1bwOOM5Ug0EZ6UDK3xS7j7mndrkwozHtSblfomlzyB4NepioNt+B2sOSzczVgtQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@acemir/cssom": "^0.9.28",
|
||||
"@asamuzakjp/dom-selector": "^6.7.6",
|
||||
@@ -9132,6 +9132,7 @@
|
||||
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"lz-string": "bin/bin.js"
|
||||
}
|
||||
@@ -10899,7 +10900,6 @@
|
||||
"integrity": "sha512-yEPsovQfpxYfgWNhCfECjG5AQaO+K3dp6XERmOepyPDVqcJm+bjyCVO3pmU+nAPe0N5dDvekfGezt/EIiRe1TA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
@@ -10995,6 +10995,7 @@
|
||||
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1",
|
||||
"ansi-styles": "^5.0.0",
|
||||
@@ -11010,6 +11011,7 @@
|
||||
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
@@ -11024,7 +11026,6 @@
|
||||
"devOptional": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@prisma/config": "7.3.0",
|
||||
"@prisma/dev": "0.20.0",
|
||||
@@ -11213,7 +11214,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
|
||||
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -11223,7 +11223,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
|
||||
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.27.0"
|
||||
},
|
||||
@@ -11236,7 +11235,8 @@
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/react-markdown": {
|
||||
"version": "10.1.0",
|
||||
@@ -12463,8 +12463,7 @@
|
||||
"version": "4.1.18",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
|
||||
"integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
"version": "2.3.0",
|
||||
@@ -12566,7 +12565,6 @@
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -13330,7 +13328,6 @@
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -13606,7 +13603,6 @@
|
||||
"integrity": "sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"typescript": ">=5"
|
||||
},
|
||||
@@ -13650,7 +13646,6 @@
|
||||
"integrity": "sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.5.0",
|
||||
@@ -13744,7 +13739,6 @@
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -13758,7 +13752,6 @@
|
||||
"integrity": "sha512-FQMeF0DJdWY0iOnbv466n/0BudNdKj1l5jYgl5JVTwjSsZSlqyXFt/9+1sEyhR6CLowbZpV7O1sCHrzBhucKKg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vitest/expect": "4.0.17",
|
||||
"@vitest/mocker": "4.0.17",
|
||||
@@ -14103,7 +14096,6 @@
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz",
|
||||
"integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pve-scripts-local",
|
||||
"version": "0.1.0",
|
||||
"version": "0.5.6",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
@@ -83,7 +83,7 @@
|
||||
"@vitest/ui": "^4.0.17",
|
||||
"baseline-browser-mapping": "^2.9.15",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-config-next": "^16.1.3",
|
||||
"eslint-config-next": "^16.1.6",
|
||||
"jsdom": "^27.4.0",
|
||||
"postcss": "^8.5.6",
|
||||
"prettier": "^3.8.0",
|
||||
|
||||
39
server.js
39
server.js
@@ -3,6 +3,7 @@ import { parse } from 'url';
|
||||
import next from 'next';
|
||||
import { WebSocketServer } from 'ws';
|
||||
import { spawn } from 'child_process';
|
||||
import { existsSync } from 'fs';
|
||||
import { join, resolve } from 'path';
|
||||
import stripAnsi from 'strip-ansi';
|
||||
import { spawn as ptySpawn } from 'node-pty';
|
||||
@@ -56,6 +57,8 @@ const handle = app.getRequestHandler();
|
||||
* @property {string} user
|
||||
* @property {string} password
|
||||
* @property {number} [id]
|
||||
* @property {string} [auth_type]
|
||||
* @property {string} [ssh_key_path]
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -295,6 +298,20 @@ class ScriptExecutionHandler {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve full server from DB when client sends server with id but no ssh_key_path (e.g. for Shell/Update over SSH).
|
||||
* @param {ServerInfo|null} server - Server from WebSocket message
|
||||
* @returns {Promise<ServerInfo|null>} Same server or full server from DB
|
||||
*/
|
||||
async resolveServerForSSH(server) {
|
||||
if (!server?.id) return server;
|
||||
if (server.auth_type === 'key' && (!server.ssh_key_path || !existsSync(server.ssh_key_path))) {
|
||||
const full = await this.db.getServerById(server.id);
|
||||
return /** @type {ServerInfo|null} */ (full ?? server);
|
||||
}
|
||||
return server;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ExtendedWebSocket} ws
|
||||
* @param {WebSocketMessage} message
|
||||
@@ -305,16 +322,21 @@ class ScriptExecutionHandler {
|
||||
switch (action) {
|
||||
case 'start':
|
||||
if (scriptPath && executionId) {
|
||||
let serverToUse = server;
|
||||
if (serverToUse?.id) {
|
||||
serverToUse = await this.resolveServerForSSH(serverToUse) ?? serverToUse;
|
||||
}
|
||||
const resolved = serverToUse ?? server;
|
||||
if (isClone && containerId && storage && server && cloneCount && hostnames && containerType) {
|
||||
await this.startSSHCloneExecution(ws, containerId, executionId, storage, server, containerType, cloneCount, hostnames);
|
||||
await this.startSSHCloneExecution(ws, containerId, executionId, storage, /** @type {ServerInfo} */ (resolved), containerType, cloneCount, hostnames);
|
||||
} else if (isBackup && containerId && storage) {
|
||||
await this.startBackupExecution(ws, containerId, executionId, storage, mode, server);
|
||||
await this.startBackupExecution(ws, containerId, executionId, storage, mode, resolved);
|
||||
} else if (isUpdate && containerId) {
|
||||
await this.startUpdateExecution(ws, containerId, executionId, mode, server, backupStorage);
|
||||
await this.startUpdateExecution(ws, containerId, executionId, mode, resolved, backupStorage);
|
||||
} else if (isShell && containerId) {
|
||||
await this.startShellExecution(ws, containerId, executionId, mode, server, containerType);
|
||||
await this.startShellExecution(ws, containerId, executionId, mode, resolved, containerType);
|
||||
} else {
|
||||
await this.startScriptExecution(ws, scriptPath, executionId, mode, server, envVars);
|
||||
await this.startScriptExecution(ws, scriptPath, executionId, mode, resolved, envVars);
|
||||
}
|
||||
} else {
|
||||
this.sendMessage(ws, {
|
||||
@@ -1153,10 +1175,11 @@ class ScriptExecutionHandler {
|
||||
const hostname = hostnames[i];
|
||||
|
||||
try {
|
||||
// Read config file to get hostname/name
|
||||
// Read config file to get hostname/name (node-specific path)
|
||||
const nodeName = server.name;
|
||||
const configPath = containerType === 'lxc'
|
||||
? `/etc/pve/lxc/${nextId}.conf`
|
||||
: `/etc/pve/qemu-server/${nextId}.conf`;
|
||||
? `/etc/pve/nodes/${nodeName}/lxc/${nextId}.conf`
|
||||
: `/etc/pve/nodes/${nodeName}/qemu-server/${nextId}.conf`;
|
||||
|
||||
let configContent = '';
|
||||
await new Promise(/** @type {(resolve: (value?: void) => void) => void} */ ((resolve) => {
|
||||
|
||||
@@ -418,44 +418,46 @@ async function isVM(scriptId: number, containerId: string, serverId: number | nu
|
||||
return false; // Default to LXC if SSH fails
|
||||
}
|
||||
|
||||
// Check both config file paths
|
||||
const vmConfigPath = `/etc/pve/qemu-server/${containerId}.conf`;
|
||||
const lxcConfigPath = `/etc/pve/lxc/${containerId}.conf`;
|
||||
// Node-specific paths (multi-node Proxmox: /etc/pve/nodes/NODENAME/...)
|
||||
const nodeName = (server as Server).name;
|
||||
const vmConfigPathNode = `/etc/pve/nodes/${nodeName}/qemu-server/${containerId}.conf`;
|
||||
const lxcConfigPathNode = `/etc/pve/nodes/${nodeName}/lxc/${containerId}.conf`;
|
||||
// Fallback for single-node or when server.name is not the Proxmox node name
|
||||
const vmConfigPathFallback = `/etc/pve/qemu-server/${containerId}.conf`;
|
||||
const lxcConfigPathFallback = `/etc/pve/lxc/${containerId}.conf`;
|
||||
|
||||
// Check VM config file
|
||||
let vmConfigExists = false;
|
||||
await new Promise<void>((resolve) => {
|
||||
const checkPathExists = (path: string): Promise<boolean> =>
|
||||
new Promise<boolean>((resolve) => {
|
||||
let exists = false;
|
||||
void sshExecutionService.executeCommand(
|
||||
server as Server,
|
||||
`test -f "${vmConfigPath}" && echo "exists" || echo "not_exists"`,
|
||||
`test -f "${path}" && echo "exists" || echo "not_exists"`,
|
||||
(data: string) => {
|
||||
if (data.includes('exists')) {
|
||||
vmConfigExists = true;
|
||||
}
|
||||
if (data.includes('exists')) exists = true;
|
||||
},
|
||||
() => resolve(),
|
||||
() => resolve()
|
||||
() => resolve(exists),
|
||||
() => resolve(exists)
|
||||
);
|
||||
});
|
||||
|
||||
if (vmConfigExists) {
|
||||
return true; // VM config file exists
|
||||
// Prefer node-specific paths first
|
||||
const vmConfigExistsNode = await checkPathExists(vmConfigPathNode);
|
||||
if (vmConfigExistsNode) {
|
||||
return true; // VM config file exists on node
|
||||
}
|
||||
|
||||
// Check LXC config file (not needed for return value, but check for completeness)
|
||||
await new Promise<void>((resolve) => {
|
||||
void sshExecutionService.executeCommand(
|
||||
server as Server,
|
||||
`test -f "${lxcConfigPath}" && echo "exists" || echo "not_exists"`,
|
||||
(_data: string) => {
|
||||
// Data handler not needed - just checking if file exists
|
||||
},
|
||||
() => resolve(),
|
||||
() => resolve()
|
||||
);
|
||||
});
|
||||
const lxcConfigExistsNode = await checkPathExists(lxcConfigPathNode);
|
||||
if (lxcConfigExistsNode) {
|
||||
return false; // LXC config file exists on node
|
||||
}
|
||||
|
||||
return false; // Always LXC since VM config doesn't exist
|
||||
// Fallback: single-node or server.name not matching Proxmox node name
|
||||
const vmConfigExistsFallback = await checkPathExists(vmConfigPathFallback);
|
||||
if (vmConfigExistsFallback) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false; // LXC (or neither path exists)
|
||||
} catch (error) {
|
||||
console.error('Error determining container type:', error);
|
||||
return false; // Default to LXC on error
|
||||
@@ -971,10 +973,11 @@ export const installedScriptsRouter = createTRPCRouter({
|
||||
};
|
||||
|
||||
// Helper function to check config file for community-script tag and extract hostname/name
|
||||
const nodeName = (server as Server).name;
|
||||
const checkConfigAndExtractInfo = async (id: string, isVM: boolean): Promise<any> => {
|
||||
const configPath = isVM
|
||||
? `/etc/pve/qemu-server/${id}.conf`
|
||||
: `/etc/pve/lxc/${id}.conf`;
|
||||
? `/etc/pve/nodes/${nodeName}/qemu-server/${id}.conf`
|
||||
: `/etc/pve/nodes/${nodeName}/lxc/${id}.conf`;
|
||||
|
||||
const readCommand = `cat "${configPath}" 2>/dev/null`;
|
||||
|
||||
@@ -1318,10 +1321,10 @@ export const installedScriptsRouter = createTRPCRouter({
|
||||
|
||||
// Check if ID exists in either pct list (containers) or qm list (VMs)
|
||||
if (!existingIds.has(containerId)) {
|
||||
// Also verify config file doesn't exist as a double-check
|
||||
// Check both container and VM config paths
|
||||
const checkContainerCommand = `test -f "/etc/pve/lxc/${containerId}.conf" && echo "exists" || echo "not_found"`;
|
||||
const checkVMCommand = `test -f "/etc/pve/qemu-server/${containerId}.conf" && echo "exists" || echo "not_found"`;
|
||||
// Also verify config file doesn't exist as a double-check (node-specific paths)
|
||||
const nodeName = (server as Server).name;
|
||||
const checkContainerCommand = `test -f "/etc/pve/nodes/${nodeName}/lxc/${containerId}.conf" && echo "exists" || echo "not_found"`;
|
||||
const checkVMCommand = `test -f "/etc/pve/nodes/${nodeName}/qemu-server/${containerId}.conf" && echo "exists" || echo "not_found"`;
|
||||
|
||||
const configExists = await new Promise<boolean>((resolve) => {
|
||||
let combinedOutput = '';
|
||||
@@ -2237,8 +2240,9 @@ export const installedScriptsRouter = createTRPCRouter({
|
||||
};
|
||||
}
|
||||
|
||||
// Read config file
|
||||
const configPath = `/etc/pve/lxc/${script.container_id}.conf`;
|
||||
// Read config file (node-specific path)
|
||||
const nodeName = (server as Server).name;
|
||||
const configPath = `/etc/pve/nodes/${nodeName}/lxc/${script.container_id}.conf`;
|
||||
const readCommand = `cat "${configPath}" 2>/dev/null`;
|
||||
let rawConfig = '';
|
||||
|
||||
@@ -2368,8 +2372,9 @@ export const installedScriptsRouter = createTRPCRouter({
|
||||
};
|
||||
}
|
||||
|
||||
// Write config file using heredoc for safe escaping
|
||||
const configPath = `/etc/pve/lxc/${script.container_id}.conf`;
|
||||
// Write config file using heredoc for safe escaping (node-specific path)
|
||||
const nodeName = (server as Server).name;
|
||||
const configPath = `/etc/pve/nodes/${nodeName}/lxc/${script.container_id}.conf`;
|
||||
const writeCommand = `cat > "${configPath}" << 'EOFCONFIG'
|
||||
${rawConfig}
|
||||
EOFCONFIG`;
|
||||
@@ -2777,9 +2782,10 @@ EOFCONFIG`;
|
||||
const { getSSHExecutionService } = await import('~/server/ssh-execution-service');
|
||||
const sshExecutionService = getSSHExecutionService();
|
||||
|
||||
const nodeName = (server as Server).name;
|
||||
const configPath = input.containerType === 'lxc'
|
||||
? `/etc/pve/lxc/${input.containerId}.conf`
|
||||
: `/etc/pve/qemu-server/${input.containerId}.conf`;
|
||||
? `/etc/pve/nodes/${nodeName}/lxc/${input.containerId}.conf`
|
||||
: `/etc/pve/nodes/${nodeName}/qemu-server/${input.containerId}.conf`;
|
||||
|
||||
let configContent = '';
|
||||
await new Promise<void>((resolve) => {
|
||||
@@ -3171,10 +3177,11 @@ EOFCONFIG`;
|
||||
const { getSSHExecutionService } = await import('~/server/ssh-execution-service');
|
||||
const sshExecutionService = getSSHExecutionService();
|
||||
|
||||
// Read config file to get hostname/name
|
||||
// Read config file to get hostname/name (node-specific path)
|
||||
const nodeName = (server as Server).name;
|
||||
const configPath = input.containerType === 'lxc'
|
||||
? `/etc/pve/lxc/${input.containerId}.conf`
|
||||
: `/etc/pve/qemu-server/${input.containerId}.conf`;
|
||||
? `/etc/pve/nodes/${nodeName}/lxc/${input.containerId}.conf`
|
||||
: `/etc/pve/nodes/${nodeName}/qemu-server/${input.containerId}.conf`;
|
||||
|
||||
let configContent = '';
|
||||
await new Promise<void>((resolve) => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// JavaScript wrapper for githubJsonService (for use with node server.js)
|
||||
import { writeFile, mkdir, readdir, readFile } from 'fs/promises';
|
||||
import { writeFile, mkdir, readdir, readFile, unlink } from 'fs/promises';
|
||||
import { join } from 'path';
|
||||
import { repositoryService } from './repositoryService.js';
|
||||
import { listDirectory, downloadRawFile } from '../lib/gitProvider/index.js';
|
||||
@@ -163,25 +163,42 @@ class GitHubJsonService {
|
||||
const localFiles = await this.getLocalJsonFiles();
|
||||
console.log(`Found ${localFiles.length} local JSON files`);
|
||||
|
||||
// Delete local JSON files that belong to this repo but are no longer in the remote
|
||||
const remoteFilenames = new Set(githubFiles.map((f) => f.name));
|
||||
const deletedFiles = await this.deleteLocalFilesRemovedFromRepo(repoUrl, remoteFilenames);
|
||||
if (deletedFiles.length > 0) {
|
||||
console.log(`Removed ${deletedFiles.length} obsolete JSON file(s) no longer in ${repoUrl}`);
|
||||
}
|
||||
|
||||
const filesToSync = await this.findFilesToSyncForRepo(repoUrl, githubFiles, localFiles);
|
||||
console.log(`Found ${filesToSync.length} files that need syncing from ${repoUrl}`);
|
||||
|
||||
if (filesToSync.length === 0) {
|
||||
const msg =
|
||||
deletedFiles.length > 0
|
||||
? `All JSON files are up to date for repository: ${repoUrl}. Removed ${deletedFiles.length} obsolete file(s).`
|
||||
: `All JSON files are up to date for repository: ${repoUrl}`;
|
||||
return {
|
||||
success: true,
|
||||
message: `All JSON files are up to date for repository: ${repoUrl}`,
|
||||
message: msg,
|
||||
count: 0,
|
||||
syncedFiles: []
|
||||
syncedFiles: [],
|
||||
deletedFiles
|
||||
};
|
||||
}
|
||||
|
||||
const syncedFiles = await this.syncSpecificFiles(repoUrl, filesToSync);
|
||||
|
||||
const msg =
|
||||
deletedFiles.length > 0
|
||||
? `Successfully synced ${syncedFiles.length} JSON files from ${repoUrl}, removed ${deletedFiles.length} obsolete file(s).`
|
||||
: `Successfully synced ${syncedFiles.length} JSON files from ${repoUrl}`;
|
||||
return {
|
||||
success: true,
|
||||
message: `Successfully synced ${syncedFiles.length} JSON files from ${repoUrl}`,
|
||||
message: msg,
|
||||
count: syncedFiles.length,
|
||||
syncedFiles
|
||||
syncedFiles,
|
||||
deletedFiles
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`JSON sync failed for ${repoUrl}:`, error);
|
||||
@@ -189,7 +206,8 @@ class GitHubJsonService {
|
||||
success: false,
|
||||
message: `Failed to sync JSON files from ${repoUrl}: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
||||
count: 0,
|
||||
syncedFiles: []
|
||||
syncedFiles: [],
|
||||
deletedFiles: []
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -205,13 +223,15 @@ class GitHubJsonService {
|
||||
success: false,
|
||||
message: 'No enabled repositories found',
|
||||
count: 0,
|
||||
syncedFiles: []
|
||||
syncedFiles: [],
|
||||
deletedFiles: []
|
||||
};
|
||||
}
|
||||
|
||||
console.log(`Found ${enabledRepos.length} enabled repositories`);
|
||||
|
||||
const allSyncedFiles = [];
|
||||
const allDeletedFiles = [];
|
||||
const processedSlugs = new Set();
|
||||
let totalSynced = 0;
|
||||
|
||||
@@ -222,6 +242,7 @@ class GitHubJsonService {
|
||||
const result = await this.syncJsonFilesForRepo(repo.url);
|
||||
|
||||
if (result.success) {
|
||||
allDeletedFiles.push(...(result.deletedFiles ?? []));
|
||||
const newFiles = result.syncedFiles.filter(file => {
|
||||
const slug = file.replace('.json', '');
|
||||
if (processedSlugs.has(slug)) {
|
||||
@@ -243,11 +264,16 @@ class GitHubJsonService {
|
||||
|
||||
await this.updateExistingFilesWithRepositoryUrl();
|
||||
|
||||
const msg =
|
||||
allDeletedFiles.length > 0
|
||||
? `Successfully synced ${totalSynced} JSON files from ${enabledRepos.length} repositories, removed ${allDeletedFiles.length} obsolete file(s).`
|
||||
: `Successfully synced ${totalSynced} JSON files from ${enabledRepos.length} repositories`;
|
||||
return {
|
||||
success: true,
|
||||
message: `Successfully synced ${totalSynced} JSON files from ${enabledRepos.length} repositories`,
|
||||
message: msg,
|
||||
count: totalSynced,
|
||||
syncedFiles: allSyncedFiles
|
||||
syncedFiles: allSyncedFiles,
|
||||
deletedFiles: allDeletedFiles
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Multi-repository JSON sync failed:', error);
|
||||
@@ -255,7 +281,8 @@ class GitHubJsonService {
|
||||
success: false,
|
||||
message: `Failed to sync JSON files: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
||||
count: 0,
|
||||
syncedFiles: []
|
||||
syncedFiles: [],
|
||||
deletedFiles: []
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -297,6 +324,32 @@ class GitHubJsonService {
|
||||
}
|
||||
}
|
||||
|
||||
async deleteLocalFilesRemovedFromRepo(repoUrl, remoteFilenames) {
|
||||
this.initializeConfig();
|
||||
const localFiles = await this.getLocalJsonFiles();
|
||||
const deletedFiles = [];
|
||||
|
||||
for (const file of localFiles) {
|
||||
try {
|
||||
const filePath = join(this.localJsonDirectory, file);
|
||||
const content = await readFile(filePath, 'utf-8');
|
||||
const script = JSON.parse(content);
|
||||
|
||||
if (script.repository_url === repoUrl && !remoteFilenames.has(file)) {
|
||||
await unlink(filePath);
|
||||
const slug = file.replace(/\.json$/, '');
|
||||
this.scriptCache.delete(slug);
|
||||
deletedFiles.push(file);
|
||||
console.log(`Removed obsolete script JSON: ${file} (no longer in ${repoUrl})`);
|
||||
}
|
||||
} catch {
|
||||
// If we can't read or parse the file, skip (do not delete)
|
||||
}
|
||||
}
|
||||
|
||||
return deletedFiles;
|
||||
}
|
||||
|
||||
async findFilesToSyncForRepo(repoUrl, githubFiles, localFiles) {
|
||||
const filesToSync = [];
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { writeFile, mkdir, readdir, readFile } from 'fs/promises';
|
||||
import { writeFile, mkdir, readdir, readFile, unlink } from 'fs/promises';
|
||||
import { join } from 'path';
|
||||
import { env } from '../../env.js';
|
||||
import type { Script, ScriptCard, GitHubFile } from '../../types/script';
|
||||
@@ -158,7 +158,7 @@ export class GitHubJsonService {
|
||||
/**
|
||||
* Sync JSON files from a specific repository
|
||||
*/
|
||||
async syncJsonFilesForRepo(repoUrl: string): Promise<{ success: boolean; message: string; count: number; syncedFiles: string[] }> {
|
||||
async syncJsonFilesForRepo(repoUrl: string): Promise<{ success: boolean; message: string; count: number; syncedFiles: string[]; deletedFiles: string[] }> {
|
||||
try {
|
||||
console.log(`Starting JSON sync from repository: ${repoUrl}`);
|
||||
|
||||
@@ -170,28 +170,45 @@ export class GitHubJsonService {
|
||||
const localFiles = await this.getLocalJsonFiles();
|
||||
console.log(`Found ${localFiles.length} local JSON files`);
|
||||
|
||||
// Delete local JSON files that belong to this repo but are no longer in the remote
|
||||
const remoteFilenames = new Set(githubFiles.map((f) => f.name));
|
||||
const deletedFiles = await this.deleteLocalFilesRemovedFromRepo(repoUrl, remoteFilenames);
|
||||
if (deletedFiles.length > 0) {
|
||||
console.log(`Removed ${deletedFiles.length} obsolete JSON file(s) no longer in ${repoUrl}`);
|
||||
}
|
||||
|
||||
// Compare and find files that need syncing
|
||||
// For multi-repo support, we need to check if file exists AND if it's from this repo
|
||||
const filesToSync = await this.findFilesToSyncForRepo(repoUrl, githubFiles, localFiles);
|
||||
console.log(`Found ${filesToSync.length} files that need syncing from ${repoUrl}`);
|
||||
|
||||
if (filesToSync.length === 0) {
|
||||
const msg =
|
||||
deletedFiles.length > 0
|
||||
? `All JSON files are up to date for repository: ${repoUrl}. Removed ${deletedFiles.length} obsolete file(s).`
|
||||
: `All JSON files are up to date for repository: ${repoUrl}`;
|
||||
return {
|
||||
success: true,
|
||||
message: `All JSON files are up to date for repository: ${repoUrl}`,
|
||||
message: msg,
|
||||
count: 0,
|
||||
syncedFiles: []
|
||||
syncedFiles: [],
|
||||
deletedFiles
|
||||
};
|
||||
}
|
||||
|
||||
// Download and save only the files that need syncing
|
||||
const syncedFiles = await this.syncSpecificFiles(repoUrl, filesToSync);
|
||||
|
||||
const msg =
|
||||
deletedFiles.length > 0
|
||||
? `Successfully synced ${syncedFiles.length} JSON files from ${repoUrl}, removed ${deletedFiles.length} obsolete file(s).`
|
||||
: `Successfully synced ${syncedFiles.length} JSON files from ${repoUrl}`;
|
||||
return {
|
||||
success: true,
|
||||
message: `Successfully synced ${syncedFiles.length} JSON files from ${repoUrl}`,
|
||||
message: msg,
|
||||
count: syncedFiles.length,
|
||||
syncedFiles
|
||||
syncedFiles,
|
||||
deletedFiles
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`JSON sync failed for ${repoUrl}:`, error);
|
||||
@@ -199,7 +216,8 @@ export class GitHubJsonService {
|
||||
success: false,
|
||||
message: `Failed to sync JSON files from ${repoUrl}: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
||||
count: 0,
|
||||
syncedFiles: []
|
||||
syncedFiles: [],
|
||||
deletedFiles: []
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -207,7 +225,7 @@ export class GitHubJsonService {
|
||||
/**
|
||||
* Sync JSON files from all enabled repositories (main repo has priority)
|
||||
*/
|
||||
async syncJsonFiles(): Promise<{ success: boolean; message: string; count: number; syncedFiles: string[] }> {
|
||||
async syncJsonFiles(): Promise<{ success: boolean; message: string; count: number; syncedFiles: string[]; deletedFiles: string[] }> {
|
||||
try {
|
||||
console.log('Starting multi-repository JSON sync...');
|
||||
|
||||
@@ -218,13 +236,15 @@ export class GitHubJsonService {
|
||||
success: false,
|
||||
message: 'No enabled repositories found',
|
||||
count: 0,
|
||||
syncedFiles: []
|
||||
syncedFiles: [],
|
||||
deletedFiles: []
|
||||
};
|
||||
}
|
||||
|
||||
console.log(`Found ${enabledRepos.length} enabled repositories`);
|
||||
|
||||
const allSyncedFiles: string[] = [];
|
||||
const allDeletedFiles: string[] = [];
|
||||
const processedSlugs = new Set<string>(); // Track slugs we've already processed
|
||||
let totalSynced = 0;
|
||||
|
||||
@@ -236,6 +256,7 @@ export class GitHubJsonService {
|
||||
const result = await this.syncJsonFilesForRepo(repo.url);
|
||||
|
||||
if (result.success) {
|
||||
allDeletedFiles.push(...(result.deletedFiles ?? []));
|
||||
// Only count files that weren't already processed from a higher priority repo
|
||||
const newFiles = result.syncedFiles.filter(file => {
|
||||
const slug = file.replace('.json', '');
|
||||
@@ -259,11 +280,16 @@ export class GitHubJsonService {
|
||||
// Also update existing files that don't have repository_url set (backward compatibility)
|
||||
await this.updateExistingFilesWithRepositoryUrl();
|
||||
|
||||
const msg =
|
||||
allDeletedFiles.length > 0
|
||||
? `Successfully synced ${totalSynced} JSON files from ${enabledRepos.length} repositories, removed ${allDeletedFiles.length} obsolete file(s).`
|
||||
: `Successfully synced ${totalSynced} JSON files from ${enabledRepos.length} repositories`;
|
||||
return {
|
||||
success: true,
|
||||
message: `Successfully synced ${totalSynced} JSON files from ${enabledRepos.length} repositories`,
|
||||
message: msg,
|
||||
count: totalSynced,
|
||||
syncedFiles: allSyncedFiles
|
||||
syncedFiles: allSyncedFiles,
|
||||
deletedFiles: allDeletedFiles
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Multi-repository JSON sync failed:', error);
|
||||
@@ -271,7 +297,8 @@ export class GitHubJsonService {
|
||||
success: false,
|
||||
message: `Failed to sync JSON files: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
||||
count: 0,
|
||||
syncedFiles: []
|
||||
syncedFiles: [],
|
||||
deletedFiles: []
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -316,6 +343,36 @@ export class GitHubJsonService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete local JSON files that belong to this repo but are no longer in the remote list.
|
||||
* Returns the list of deleted filenames.
|
||||
*/
|
||||
private async deleteLocalFilesRemovedFromRepo(repoUrl: string, remoteFilenames: Set<string>): Promise<string[]> {
|
||||
this.initializeConfig();
|
||||
const localFiles = await this.getLocalJsonFiles();
|
||||
const deletedFiles: string[] = [];
|
||||
|
||||
for (const file of localFiles) {
|
||||
try {
|
||||
const filePath = join(this.localJsonDirectory!, file);
|
||||
const content = await readFile(filePath, 'utf-8');
|
||||
const script = JSON.parse(content) as Script;
|
||||
|
||||
if (script.repository_url === repoUrl && !remoteFilenames.has(file)) {
|
||||
await unlink(filePath);
|
||||
const slug = file.replace(/\.json$/, '');
|
||||
this.scriptCache.delete(slug);
|
||||
deletedFiles.push(file);
|
||||
console.log(`Removed obsolete script JSON: ${file} (no longer in ${repoUrl})`);
|
||||
}
|
||||
} catch {
|
||||
// If we can't read or parse the file, skip (do not delete)
|
||||
}
|
||||
}
|
||||
|
||||
return deletedFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find files that need syncing for a specific repository
|
||||
* This checks if file exists locally AND if it's from the same repository
|
||||
|
||||
Reference in New Issue
Block a user