diff --git a/Integrations/VS-Code/CHANGELOG.md b/Integrations/VS-Code/CHANGELOG.md index 42a80ad9..2d1a4ed2 100644 --- a/Integrations/VS-Code/CHANGELOG.md +++ b/Integrations/VS-Code/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - `scriptbee.enableLiveUpdates` to enable live updates for project script events of creation, update, and deletion. +- `scriptbee.enableAutoPush` to enable auto push to server when script is saved. ## [1.0.0] diff --git a/Integrations/VS-Code/README.md b/Integrations/VS-Code/README.md index e5352fed..013a291b 100644 --- a/Integrations/VS-Code/README.md +++ b/Integrations/VS-Code/README.md @@ -57,6 +57,7 @@ This extension can be configured via VS Code settings: | ----------------------------- | ---------------------------------------------------------------------------------------------- | -------------- | | `scriptbee.workspaceRoot` | The root folder where ScriptBee projects and generated code are stored. | `~/.scriptbee` | | `scriptbee.enableLiveUpdates` | Enable real-time updates for script lifecycle events (creation, update, deletion) via SignalR. | `true` | +| `scriptbee.enableAutoPush` | Automatically push script changes to the server upon saving. | `true` | ## Getting Started diff --git a/Integrations/VS-Code/package-lock.json b/Integrations/VS-Code/package-lock.json index 6d6c6c83..a61018a7 100644 --- a/Integrations/VS-Code/package-lock.json +++ b/Integrations/VS-Code/package-lock.json @@ -10,23 +10,23 @@ "license": "Apache-2.0", "dependencies": { "@microsoft/signalr": "^10.0.0", - "axios": "^1.15.0" + "axios": "^1.15.2" }, "devDependencies": { "@types/mocha": "^10.0.10", "@types/node": "25.x", "@types/sinon": "^21.0.1", - "@types/vscode": "^1.115.0", + "@types/vscode": "^1.118.0", "@vscode/test-cli": "^0.0.12", "@vscode/test-electron": "^2.5.2", "axios-mock-adapter": "^2.1.0", "esbuild": "^0.28.0", - "eslint": "^10.2.0", + "eslint": "^10.2.1", "mocha": "^11.7.5", - "prettier": "^3.8.2", + "prettier": "^3.8.3", "sinon": "^21.1.2", - "typescript": "^6.0.2", - "typescript-eslint": "^8.58.1" + "typescript": "^6.0.3", + "typescript-eslint": "^8.59.1" }, "engines": { "vscode": "^1.115.0" @@ -805,22 +805,22 @@ "dev": true }, "node_modules/@types/vscode": { - "version": "1.115.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.115.0.tgz", - "integrity": "sha512-/M8cdznOlqtMqduHKKlIF00v4eum4ZWKgn8YoPRKcN6PDdvoWeeqDaQSnw63ipDbq1Uzz78Wndk/d0uSPwORfA==", + "version": "1.118.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.118.0.tgz", + "integrity": "sha512-Ah6eTlqDcwIMELEVwQMO++rJAFBRz/oLluLD/vWdYrH1KuI9kfpaM+7pg0OvvascgcJy+ghLCERAYouM4QbzGw==", "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.1.tgz", - "integrity": "sha512-eSkwoemjo76bdXl2MYqtxg51HNwUSkWfODUOQ3PaTLZGh9uIWWFZIjyjaJnex7wXDu+TRx+ATsnSxdN9YWfRTQ==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.1.tgz", + "integrity": "sha512-BOziFIfE+6osHO9FoJG4zjoHUcvI7fTNBSpdAwrNH0/TLvzjsk2oo8XSSOT2HhqUyhZPfHv4UOffoJ9oEEQ7Ag==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.58.1", - "@typescript-eslint/type-utils": "8.58.1", - "@typescript-eslint/utils": "8.58.1", - "@typescript-eslint/visitor-keys": "8.58.1", + "@typescript-eslint/scope-manager": "8.59.1", + "@typescript-eslint/type-utils": "8.59.1", + "@typescript-eslint/utils": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -833,7 +833,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.58.1", + "@typescript-eslint/parser": "^8.59.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } @@ -848,15 +848,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.1.tgz", - "integrity": "sha512-gGkiNMPqerb2cJSVcruigx9eHBlLG14fSdPdqMoOcBfh+vvn4iCq2C8MzUB89PrxOXk0y3GZ1yIWb9aOzL93bw==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.1.tgz", + "integrity": "sha512-HDQH9O/47Dxi1ceDhBXdaldtf/WV9yRYMjbjCuNk3qnaTD564qwv61Y7+gTxwxRKzSrgO5uhtw584igXVuuZkA==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.58.1", - "@typescript-eslint/types": "8.58.1", - "@typescript-eslint/typescript-estree": "8.58.1", - "@typescript-eslint/visitor-keys": "8.58.1", + "@typescript-eslint/scope-manager": "8.59.1", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1", "debug": "^4.4.3" }, "engines": { @@ -872,13 +872,13 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.1.tgz", - "integrity": "sha512-gfQ8fk6cxhtptek+/8ZIqw8YrRW5048Gug8Ts5IYcMLCw18iUgrZAEY/D7s4hkI0FxEfGakKuPK/XUMPzPxi5g==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.1.tgz", + "integrity": "sha512-+MuHQlHiEr00Of/IQbE/MmEoi44znZHbR/Pz7Opq4HryUOlRi+/44dro9Ycy8Fyo+/024IWtw8m4JUMCGTYxDg==", "dev": true, "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.58.1", - "@typescript-eslint/types": "^8.58.1", + "@typescript-eslint/tsconfig-utils": "^8.59.1", + "@typescript-eslint/types": "^8.59.1", "debug": "^4.4.3" }, "engines": { @@ -893,13 +893,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.1.tgz", - "integrity": "sha512-TPYUEqJK6avLcEjumWsIuTpuYODTTDAtoMdt8ZZa93uWMTX13Nb8L5leSje1NluammvU+oI3QRr5lLXPgihX3w==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.1.tgz", + "integrity": "sha512-LwuHQI4pDOYVKvmH2dkaJo6YZCSgouVgnS/z7yBPKBMvgtBvyLqiLy9Z6b7+m/TRcX1NFYUqZetI5Y+aT4GEfg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.58.1", - "@typescript-eslint/visitor-keys": "8.58.1" + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -910,9 +910,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.1.tgz", - "integrity": "sha512-JAr2hOIct2Q+qk3G+8YFfqkqi7sC86uNryT+2i5HzMa2MPjw4qNFvtjnw1IiA1rP7QhNKVe21mSSLaSjwA1Olw==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.1.tgz", + "integrity": "sha512-/0nEyPbX7gRsk0Uwfe4ALwwgxuA66d/l2mhRDNlAvaj4U3juhUtJNq0DsY8M2AYwwb9rEq2hrC3IcIcEt++iJA==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -926,14 +926,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.1.tgz", - "integrity": "sha512-HUFxvTJVroT+0rXVJC7eD5zol6ID+Sn5npVPWoFuHGg9Ncq5Q4EYstqR+UOqaNRFXi5TYkpXXkLhoCHe3G0+7w==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.1.tgz", + "integrity": "sha512-klWPBR2ciQHS3f++ug/mVnWKPjBUo7icEL3FAO1lhAR1Z1i5NQYZ1EannMSRYcq5qCv5wNALlXr6fksRHyYl7w==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.58.1", - "@typescript-eslint/typescript-estree": "8.58.1", - "@typescript-eslint/utils": "8.58.1", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1", + "@typescript-eslint/utils": "8.59.1", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -950,9 +950,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.1.tgz", - "integrity": "sha512-io/dV5Aw5ezwzfPBBWLoT+5QfVtP8O7q4Kftjn5azJ88bYyp/ZMCsyW1lpKK46EXJcaYMZ1JtYj+s/7TdzmQMw==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.1.tgz", + "integrity": "sha512-ZDCjgccSdYPw5Bxh+my4Z0lJU96ZDN7jbBzvmEn0FZx3RtU1C7VWl6NbDx94bwY3V5YsgwRzJPOgeY2Q/nLG8A==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -963,15 +963,15 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.1.tgz", - "integrity": "sha512-w4w7WR7GHOjqqPnvAYbazq+Y5oS68b9CzasGtnd6jIeOIeKUzYzupGTB2T4LTPSv4d+WPeccbxuneTFHYgAAWg==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.1.tgz", + "integrity": "sha512-OUd+vJS05sSkOip+BkZ/2NS8RMxrAAJemsC6vU3kmfLyeaJT0TftHkV9mcx2107MmsBVXXexhVu4F0TZXyMl4g==", "dev": true, "dependencies": { - "@typescript-eslint/project-service": "8.58.1", - "@typescript-eslint/tsconfig-utils": "8.58.1", - "@typescript-eslint/types": "8.58.1", - "@typescript-eslint/visitor-keys": "8.58.1", + "@typescript-eslint/project-service": "8.59.1", + "@typescript-eslint/tsconfig-utils": "8.59.1", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -1026,15 +1026,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.1.tgz", - "integrity": "sha512-Ln8R0tmWC7pTtLOzgJzYTXSCjJ9rDNHAqTaVONF4FEi2qwce8mD9iSOxOpLFFvWp/wBFlew0mjM1L1ihYWfBdQ==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.1.tgz", + "integrity": "sha512-3pIeoXhCeYH9FSCBI8P3iNwJlGuzPlYKkTlen2O9T1DSeeg8UG8jstq6BLk+Mda0qup7mgk4z4XL4OzRaxZ8LA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.58.1", - "@typescript-eslint/types": "8.58.1", - "@typescript-eslint/typescript-estree": "8.58.1" + "@typescript-eslint/scope-manager": "8.59.1", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1049,12 +1049,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.1.tgz", - "integrity": "sha512-y+vH7QE8ycjoa0bWciFg7OpFcipUuem1ujhrdLtq1gByKwfbC7bPeKsiny9e0urg93DqwGcHey+bGRKCnF1nZQ==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.1.tgz", + "integrity": "sha512-LdDNl6C5iJExcM0Yh0PwAIBb9PrSiCsWamF/JyEZawm3kFDnRoaq3LGE4bpyRao/fWeGKKyw7icx0YxrLFC5Cg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.58.1", + "@typescript-eslint/types": "8.59.1", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -1213,9 +1213,9 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/axios": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz", - "integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==", + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.2.tgz", + "integrity": "sha512-wLrXxPtcrPTsNlJmKjkPnNPK2Ihe0hn0wGSaTEiHRPxwjvJwT3hKmXF4dpqxmPO9SoNb2FsYXj/xEo0gHN+D5A==", "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", @@ -1737,17 +1737,17 @@ } }, "node_modules/eslint": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.2.0.tgz", - "integrity": "sha512-+L0vBFYGIpSNIt/KWTpFonPrqYvgKw1eUI5Vn7mEogrQcWtWYtNQ7dNqC+px/J0idT3BAkiWrhfS7k+Tum8TUA==", + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.2.1.tgz", + "integrity": "sha512-wiyGaKsDgqXvF40P8mDwiUp/KQjE1FdrIEJsM8PZ3XCiniTMXS3OHWWUe5FI5agoCnr8x4xPrTDZuxsBlNHl+Q==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", - "@eslint/config-array": "^0.23.4", - "@eslint/config-helpers": "^0.5.4", - "@eslint/core": "^1.2.0", - "@eslint/plugin-kit": "^0.7.0", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.5.5", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", @@ -3065,9 +3065,9 @@ } }, "node_modules/prettier": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.2.tgz", - "integrity": "sha512-8c3mgTe0ASwWAJK+78dpviD+A8EqhndQPUBpNUIPt6+xWlIigCwfN01lWr9MAede4uqXGTEKeQWTvzb3vjia0Q==", + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -3593,9 +3593,9 @@ } }, "node_modules/typescript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", - "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -3606,15 +3606,15 @@ } }, "node_modules/typescript-eslint": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.58.1.tgz", - "integrity": "sha512-gf6/oHChByg9HJvhMO1iBexJh12AqqTfnuxscMDOVqfJW3htsdRJI/GfPpHTTcyeB8cSTUY2JcZmVgoyPqcrDg==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.59.1.tgz", + "integrity": "sha512-xqDcFVBmlrltH64lklOVp1wYxgJr6LVdg3NamBgH2OOQDLFdTKfIZXF5PfghrnXQKXZGTQs8tr1vL7fJvq8CTQ==", "dev": true, "dependencies": { - "@typescript-eslint/eslint-plugin": "8.58.1", - "@typescript-eslint/parser": "8.58.1", - "@typescript-eslint/typescript-estree": "8.58.1", - "@typescript-eslint/utils": "8.58.1" + "@typescript-eslint/eslint-plugin": "8.59.1", + "@typescript-eslint/parser": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1", + "@typescript-eslint/utils": "8.59.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" diff --git a/Integrations/VS-Code/package.json b/Integrations/VS-Code/package.json index fe52d71d..2cb5c5cc 100644 --- a/Integrations/VS-Code/package.json +++ b/Integrations/VS-Code/package.json @@ -35,6 +35,11 @@ "type": "boolean", "default": true, "description": "Enable real-time updates for script lifecycle events (creation, update, deletion)." + }, + "scriptbee.enableAutoPush": { + "type": "boolean", + "default": true, + "description": "Automatically push script changes to the server upon saving." } } }, @@ -241,22 +246,22 @@ }, "dependencies": { "@microsoft/signalr": "^10.0.0", - "axios": "^1.15.0" + "axios": "^1.15.2" }, "devDependencies": { "@types/mocha": "^10.0.10", "@types/node": "25.x", "@types/sinon": "^21.0.1", - "@types/vscode": "^1.115.0", + "@types/vscode": "^1.118.0", "@vscode/test-cli": "^0.0.12", "@vscode/test-electron": "^2.5.2", "axios-mock-adapter": "^2.1.0", "esbuild": "^0.28.0", - "eslint": "^10.2.0", + "eslint": "^10.2.1", "mocha": "^11.7.5", - "prettier": "^3.8.2", + "prettier": "^3.8.3", "sinon": "^21.1.2", - "typescript": "^6.0.2", - "typescript-eslint": "^8.58.1" + "typescript": "^6.0.3", + "typescript-eslint": "^8.59.1" } } diff --git a/Integrations/VS-Code/src/extension.ts b/Integrations/VS-Code/src/extension.ts index 519500a3..747ac8fc 100644 --- a/Integrations/VS-Code/src/extension.ts +++ b/Integrations/VS-Code/src/extension.ts @@ -7,6 +7,7 @@ import { logger } from './utils/logger'; import { hideMetaFiles } from './utils/workspaceUtils'; import { RemoteScriptProvider } from './providers/RemoteScriptProvider'; import { liveUpdatesService } from './services/liveUpdatesService'; +import { autoPushService } from './services/autoPushService'; import { COMMAND_ON_PROJECT_SELECTED } from './commands/commandIds'; export function activate(context: vscode.ExtensionContext) { @@ -22,12 +23,16 @@ export function activate(context: vscode.ExtensionContext) { initTreeView(context); void liveUpdatesService.start(); + autoPushService.start(); context.subscriptions.push( vscode.workspace.onDidChangeConfiguration((e) => { if (e.affectsConfiguration('scriptbee.enableLiveUpdates')) { void liveUpdatesService.start(); } + if (e.affectsConfiguration('scriptbee.enableAutoPush')) { + autoPushService.start(); + } }), vscode.commands.registerCommand(COMMAND_ON_PROJECT_SELECTED, () => { void liveUpdatesService.start(); @@ -37,4 +42,5 @@ export function activate(context: vscode.ExtensionContext) { export function deactivate() { void liveUpdatesService.stop(); + autoPushService.stop(); } diff --git a/Integrations/VS-Code/src/services/autoPushService.ts b/Integrations/VS-Code/src/services/autoPushService.ts new file mode 100644 index 00000000..366ccfd4 --- /dev/null +++ b/Integrations/VS-Code/src/services/autoPushService.ts @@ -0,0 +1,56 @@ +import * as vscode from 'vscode'; +import { scriptSyncService } from './scriptSyncService'; +import { connectionService } from './connectionService'; +import { getProjectSrcPath } from '../utils/workspaceUtils'; +import { logger } from '../utils/logger'; +import * as path from 'path'; + +export class AutoPushService { + private disposable?: vscode.Disposable; + + public start() { + this.stop(); + this.disposable = vscode.workspace.onDidSaveTextDocument(async (document) => { + const config = vscode.workspace.getConfiguration('scriptbee'); + if (!config.get('enableAutoPush', true)) { + return; + } + + if (document.uri.scheme !== 'file') { + return; + } + + try { + const connectionId = connectionService.getActiveConnectionId(); + if (!connectionId) { + return; + } + + const connections = await connectionService.getConnections(); + const connection = connections.find((c) => c.id === connectionId); + if (!connection || !connection.projectId) { + return; + } + + const srcPath = path.normalize(getProjectSrcPath(connection.projectId)).toLowerCase(); + const filePath = path.normalize(document.uri.fsPath).toLowerCase(); + + if (filePath.startsWith(srcPath)) { + await scriptSyncService.pushFileByUri(document.uri); + logger.log(`Auto-pushed ${document.fileName}`); + } + } catch (error) { + logger.error(`Auto-push failed for ${document.fileName}`, error); + } + }); + } + + public stop() { + if (this.disposable) { + this.disposable.dispose(); + this.disposable = undefined; + } + } +} + +export const autoPushService = new AutoPushService(); diff --git a/Integrations/VS-Code/src/test/services/autoPushService.test.ts b/Integrations/VS-Code/src/test/services/autoPushService.test.ts new file mode 100644 index 00000000..b3364e1a --- /dev/null +++ b/Integrations/VS-Code/src/test/services/autoPushService.test.ts @@ -0,0 +1,115 @@ +import * as assert from 'assert'; +import * as vscode from 'vscode'; +import * as sinon from 'sinon'; +import { autoPushService } from '../../services/autoPushService'; +import { connectionService } from '../../services/connectionService'; +import { scriptSyncService } from '../../services/scriptSyncService'; +import * as workspaceUtils from '../../utils/workspaceUtils'; + +suite('AutoPushService Tests', () => { + let onDidSaveStub: sinon.SinonStub; + let getConfigurationStub: sinon.SinonStub; + let getActiveConnectionIdStub: sinon.SinonStub; + let getConnectionsStub: sinon.SinonStub; + let pushFileByUriStub: sinon.SinonStub; + let getProjectSrcPathStub: sinon.SinonStub; + let mockConfig: { get: sinon.SinonStub }; + let saveCallback: (doc: vscode.TextDocument) => Promise; + + setup(() => { + mockConfig = { + get: sinon.stub(), + }; + getConfigurationStub = sinon.stub(vscode.workspace, 'getConfiguration').returns(mockConfig as unknown as vscode.WorkspaceConfiguration); + + onDidSaveStub = sinon.stub(vscode.workspace, 'onDidSaveTextDocument').callsFake((cb) => { + saveCallback = cb as (doc: vscode.TextDocument) => Promise; + return { dispose: sinon.stub() } as vscode.Disposable; + }); + + getActiveConnectionIdStub = sinon.stub(connectionService, 'getActiveConnectionId'); + getConnectionsStub = sinon.stub(connectionService, 'getConnections'); + pushFileByUriStub = sinon.stub(scriptSyncService, 'pushFileByUri').resolves(); + getProjectSrcPathStub = sinon.stub(workspaceUtils, 'getProjectSrcPath'); + + autoPushService.start(); + }); + + teardown(() => { + autoPushService.stop(); + sinon.restore(); + }); + + test('should not push if autoPush is disabled', async () => { + mockConfig.get.withArgs('enableAutoPush', true).returns(false); + + const doc = { + uri: vscode.Uri.file('/my/path/src/test.js'), + fileName: 'test.js', + } as vscode.TextDocument; + + await saveCallback(doc); + + assert.strictEqual(pushFileByUriStub.notCalled, true); + }); + + test('should not push if URI scheme is not file', async () => { + mockConfig.get.withArgs('enableAutoPush', true).returns(true); + + const doc = { + uri: vscode.Uri.parse('untitled:Untitled-1'), + fileName: 'Untitled-1', + } as vscode.TextDocument; + + await saveCallback(doc); + + assert.strictEqual(pushFileByUriStub.notCalled, true); + }); + + test('should not push if no active connection', async () => { + mockConfig.get.withArgs('enableAutoPush', true).returns(true); + getActiveConnectionIdStub.returns(undefined); + + const doc = { + uri: vscode.Uri.file('/my/path/src/test.js'), + fileName: 'test.js', + } as vscode.TextDocument; + + await saveCallback(doc); + + assert.strictEqual(pushFileByUriStub.notCalled, true); + }); + + test('should not push if file is outside project src directory', async () => { + mockConfig.get.withArgs('enableAutoPush', true).returns(true); + getActiveConnectionIdStub.returns('conn1'); + getConnectionsStub.resolves([{ id: 'conn1', projectId: 'proj1' }]); + getProjectSrcPathStub.withArgs('proj1').returns(vscode.Uri.file('/project/src').fsPath); + + const doc = { + uri: vscode.Uri.file('/outside/path/test.js'), + fileName: 'test.js', + } as vscode.TextDocument; + + await saveCallback(doc); + + assert.strictEqual(pushFileByUriStub.notCalled, true); + }); + + test('should push if file is inside project src directory and autoPush is enabled', async () => { + mockConfig.get.withArgs('enableAutoPush', true).returns(true); + getActiveConnectionIdStub.returns('conn1'); + getConnectionsStub.resolves([{ id: 'conn1', projectId: 'proj1' }]); + getProjectSrcPathStub.withArgs('proj1').returns(vscode.Uri.file('/project/src').fsPath); + + const uri = vscode.Uri.file('/project/src/folder/test.js'); + const doc = { + uri, + fileName: 'test.js', + } as vscode.TextDocument; + + await saveCallback(doc); + + assert.strictEqual(pushFileByUriStub.calledOnceWith(uri), true); + }); +}); diff --git a/docs/installation/vs_code_extension.md b/docs/installation/vs_code_extension.md index c4ee5ae6..796d9651 100644 --- a/docs/installation/vs_code_extension.md +++ b/docs/installation/vs_code_extension.md @@ -110,6 +110,7 @@ The extension exposes the following settings via **VS Code Settings** (`Ctrl+,` | ----------------------------- | ---------------------------------------------------------------------------------------------- | -------------- | | `scriptbee.workspaceRoot` | The root folder where ScriptBee projects and generated code are stored. | `~/.scriptbee` | | `scriptbee.enableLiveUpdates` | Enable real-time updates for script lifecycle events (creation, update, deletion) via SignalR. | `true` | +| `scriptbee.enableAutoPush` | Automatically push script changes to the server upon saving. | `true` | To change these settings, open VS Code settings, search for **ScriptBee**, and update the desired fields. You can set project-specific paths by saving the setting at the **Workspace** or **Folder** level.