diff --git a/package-lock.json b/package-lock.json index f0a4351..87e6b0c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,11 +12,13 @@ "@octokit/rest": "~19.0.4", "jsonc-parser": "^3.2.0", "micromatch": "~4.0.5", - "request-light": "^0.5.8" + "request-light": "^0.5.8", + "semver": "^7.5.4" }, "devDependencies": { "@types/micromatch": "4.0.2", "@types/node": "~16.11.62", + "@types/semver": "^7.5.5", "@types/vscode": "~1.66.0", "@typescript-eslint/eslint-plugin": "^5.38.1", "@typescript-eslint/parser": "^5.38.1", @@ -462,6 +464,12 @@ "integrity": "sha512-K/ggecSdwAAy2NUW4WKmF4Rc03GKbsfP+k326UWgckoS+Rzd2PaWbjk76dSmqdLQvLTJAO9axiTUJ6488mFsYQ==", "dev": true }, + "node_modules/@types/semver": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.5.tgz", + "integrity": "sha512-+d+WYC1BxJ6yVOgUgzK8gWvp5qF8ssV5r4nsDcZWKRWcDQLQ619tvWAxJQYGgBrO1MnLJC7a5GtiYsAoQ47dJg==", + "dev": true + }, "node_modules/@types/vscode": { "version": "1.66.0", "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.66.0.tgz", @@ -500,21 +508,6 @@ } } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@typescript-eslint/parser": { "version": "5.38.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.38.1.tgz", @@ -655,21 +648,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@typescript-eslint/utils": { "version": "5.38.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.38.1.tgz", @@ -1834,21 +1812,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint-plugin-jsdoc/node_modules/semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -2845,7 +2808,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -3022,30 +2984,6 @@ "node": ">=10" } }, - "node_modules/node-abi/node_modules/lru-cache": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.8.0.tgz", - "integrity": "sha512-AmXqneQZL3KZMIgBpaPTeI6pfwh+xQ2vutMsyqOu1TBdEXFZgpG/80wuJ531w2ZN7TI0/oc8CPxzh/DKQudZqg==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/node-abi/node_modules/semver": { - "version": "7.3.6", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.6.tgz", - "integrity": "sha512-HZWqcgwLsjaX1HBD31msI/rXktuIhS+lWvdE4kN9z+8IVT4Itc7vqU2WvYsyD6/sjYCt4dEKH/m1M3dwI9CC5w==", - "dev": true, - "dependencies": { - "lru-cache": "^7.4.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, "node_modules/node-addon-api": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", @@ -3234,6 +3172,15 @@ "semver": "^5.1.0" } }, + "node_modules/parse-semver/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, "node_modules/parse5": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", @@ -3669,12 +3616,17 @@ } }, "node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dependencies": { + "lru-cache": "^6.0.0" + }, "bin": { - "semver": "bin/semver" + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/serialize-javascript": { @@ -4082,21 +4034,6 @@ "webpack": "^5.0.0" } }, - "node_modules/ts-loader/node_modules/semver": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", - "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -4299,6 +4236,15 @@ "node": ">=4" } }, + "node_modules/vsce/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, "node_modules/watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", @@ -4526,8 +4472,7 @@ "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/yauzl": { "version": "2.10.0", @@ -4915,6 +4860,12 @@ "integrity": "sha512-K/ggecSdwAAy2NUW4WKmF4Rc03GKbsfP+k326UWgckoS+Rzd2PaWbjk76dSmqdLQvLTJAO9axiTUJ6488mFsYQ==", "dev": true }, + "@types/semver": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.5.tgz", + "integrity": "sha512-+d+WYC1BxJ6yVOgUgzK8gWvp5qF8ssV5r4nsDcZWKRWcDQLQ619tvWAxJQYGgBrO1MnLJC7a5GtiYsAoQ47dJg==", + "dev": true + }, "@types/vscode": { "version": "1.66.0", "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.66.0.tgz", @@ -4935,17 +4886,6 @@ "regexpp": "^3.2.0", "semver": "^7.3.7", "tsutils": "^3.21.0" - }, - "dependencies": { - "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } } }, "@typescript-eslint/parser": { @@ -5022,15 +4962,6 @@ "merge2": "^1.4.1", "slash": "^3.0.0" } - }, - "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } } } }, @@ -5997,15 +5928,6 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true - }, - "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } } } }, @@ -6696,7 +6618,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, "requires": { "yallist": "^4.0.0" } @@ -6834,23 +6755,6 @@ "dev": true, "requires": { "semver": "^7.3.5" - }, - "dependencies": { - "lru-cache": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.8.0.tgz", - "integrity": "sha512-AmXqneQZL3KZMIgBpaPTeI6pfwh+xQ2vutMsyqOu1TBdEXFZgpG/80wuJ531w2ZN7TI0/oc8CPxzh/DKQudZqg==", - "dev": true - }, - "semver": { - "version": "7.3.6", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.6.tgz", - "integrity": "sha512-HZWqcgwLsjaX1HBD31msI/rXktuIhS+lWvdE4kN9z+8IVT4Itc7vqU2WvYsyD6/sjYCt4dEKH/m1M3dwI9CC5w==", - "dev": true, - "requires": { - "lru-cache": "^7.4.0" - } - } } }, "node-addon-api": { @@ -6991,6 +6895,14 @@ "dev": true, "requires": { "semver": "^5.1.0" + }, + "dependencies": { + "semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true + } } }, "parse5": { @@ -7309,10 +7221,12 @@ } }, "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "requires": { + "lru-cache": "^6.0.0" + } }, "serialize-javascript": { "version": "6.0.0", @@ -7607,17 +7521,6 @@ "enhanced-resolve": "^5.0.0", "micromatch": "^4.0.0", "semver": "^7.3.4" - }, - "dependencies": { - "semver": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", - "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } } }, "tslib": { @@ -7768,6 +7671,12 @@ "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } + }, + "semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true } } }, @@ -7931,8 +7840,7 @@ "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "yauzl": { "version": "2.10.0", diff --git a/package.json b/package.json index e468d7e..add748b 100644 --- a/package.json +++ b/package.json @@ -32,11 +32,13 @@ "@octokit/rest": "~19.0.4", "jsonc-parser": "^3.2.0", "micromatch": "~4.0.5", - "request-light": "^0.5.8" + "request-light": "^0.5.8", + "semver": "^7.5.4" }, "devDependencies": { "@types/micromatch": "4.0.2", "@types/node": "~16.11.62", + "@types/semver": "^7.5.5", "@types/vscode": "~1.66.0", "@typescript-eslint/eslint-plugin": "^5.38.1", "@typescript-eslint/parser": "^5.38.1", @@ -301,6 +303,24 @@ "description": "The URL for the ForgeBox endpoint. Customize for ForgeBox Enterprise.", "default": "https://www.forgebox.io", "scope": "window" + }, + "commandbox.box.majorUpdateColor": { + "type": "string", + "description": "The color to use for highlighting major package updates", + "default": "", + "scope": "window" + }, + "commandbox.box.minorUpdateColor": { + "type": "string", + "description": "The color to use for highlighting minor package updates", + "default": "", + "scope": "window" + }, + "commandbox.box.patchUpdateColor": { + "type": "string", + "description": "The color to use for highlighting patch package updates", + "default": "", + "scope": "window" } } }, diff --git a/src/extension.ts b/src/extension.ts index 07eccdc..792f237 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -6,6 +6,7 @@ import { runSelectedScript, selectAndRunScriptFromFolder, turnAutoDetectOn } fro import { BoxScriptsTreeDataProvider } from "./commandboxView"; import { invalidateTasksCache, BoxTaskProvider, hasBoxJson } from "./tasks"; import { invalidateHoverScriptsCache, BoxScriptHoverProvider } from "./scriptHover"; +import { decorateBoxJSON } from "./features/boxJSONDecorations"; let treeDataProvider: BoxScriptsTreeDataProvider | undefined; const octokit = new Octokit(); @@ -107,6 +108,8 @@ export async function activate( context: vscode.ExtensionContext ): Promise { invalidateScriptCaches(); } ) ) ; + + context.subscriptions.push( vscode.window.onDidChangeActiveTextEditor( ( textEditor: vscode.TextEditor ) => decorateBoxJSON( textEditor ) ) ); + context.subscriptions.push( vscode.window.onDidChangeActiveTextEditor( ( textEditor: vscode.TextEditor ) => decorateBoxJSON( textEditor ) ) ); + + let timeout = null; + context.subscriptions.push( vscode.workspace.onDidChangeTextDocument( ( event: vscode.TextDocumentChangeEvent ) => { + const textEditor = vscode.window.visibleTextEditors.find( te => te.document === event.document ); + + if( !textEditor ){ + return; + } + + clearTimeout(timeout) + timeout = setTimeout(() => { + decorateBoxJSON( textEditor ); + }, 500) + })); + + vscode.window.visibleTextEditors.forEach( (textEditor:vscode.TextEditor) => { + decorateBoxJSON( textEditor ); + }); } let taskProvider: BoxTaskProvider; diff --git a/src/features/boxJSONContribution.ts b/src/features/boxJSONContribution.ts index f3fcaa1..a3f85ed 100644 --- a/src/features/boxJSONContribution.ts +++ b/src/features/boxJSONContribution.ts @@ -2,7 +2,7 @@ import { MarkdownString, CompletionItemKind, CompletionItem, DocumentSelector, S import { IJSONContribution, ISuggestionsCollector } from "./jsonContributions"; import { XHRRequest } from "request-light"; import { Location } from "jsonc-parser"; -// import * as cp from "child_process"; +import * as forgeboxAPI from "../forgeboxAPI"; const LIMIT = 50; const USER_AGENT = "Visual Studio Code"; @@ -178,55 +178,14 @@ export class BoxJSONContribution implements IJSONContribution { private async fetchPackageInfo( pack: string, _resource: Uri | undefined ): Promise { // TODO: Should check first if pack name is even valid based on ForgeBox rules. - const info = await this.forgeboxView( pack ); - if ( !info ) { - // info = await this.commandboxView( pack, resource ); - } - return info; - } + const info = await forgeboxAPI.getPackageInfo( pack ); - private async forgeboxView( pack: string ): Promise { - let endpointUrl: string = workspace.getConfiguration( "commandbox.forgebox" ).get( "endpointUrl" ); - if ( endpointUrl.endsWith( "/" ) ) { - endpointUrl = endpointUrl.slice( 0, -1 ); - } - const queryUrl = `${endpointUrl}/api/v1/entry/${encodeURIComponent( pack )}`; - try { - const success = await this.xhr( { - url : queryUrl, - headers : { agent: USER_AGENT } - } ); - if ( success.status === httpSuccessStatusCode ) { - const obj = JSON.parse( success.responseText ); - if ( obj?.data ) { - return obj.data as BoxPackageInfo; - } - } - } catch ( error ) { - // ignore + if ( info instanceof forgeboxAPI.ForgeBoxError ) { + return null; } - return undefined; - } - - /* - private async commandboxView( commandPath: string, pack: string, resource: Uri | undefined ): Promise { - return new Promise( ( resolve, _reject ) => { - const args = [ "show", "--json", pack ]; - let cwd = resource && resource.scheme === 'file' ? dirname(resource.fsPath) : undefined; - cp.execFile( commandPath, args, { cwd } ( error, stdout ) => { - if ( !error ) { - try { - resolve( JSON.parse( stdout ) as BoxPackageInfo ); - } catch ( e ) { - // ignore - } - } - resolve( undefined ); - } ); - } ); + return info; } - */ public async getInfoContribution( resource: Uri, location: Location ): Promise | null { if ( ( location.matches( [ "dependencies", "*" ] ) || location.matches( [ "devDependencies", "*" ] ) ) ) { diff --git a/src/features/boxJSONDecorations.ts b/src/features/boxJSONDecorations.ts new file mode 100644 index 0000000..bb68d19 --- /dev/null +++ b/src/features/boxJSONDecorations.ts @@ -0,0 +1,215 @@ +import * as vscode from "vscode"; +import { parse } from '@typescript-eslint/parser' +import { TSESTree } from '@typescript-eslint/types' +import { VariableDeclaration } from '@typescript-eslint/types/dist/generated/ast-spec' +import * as forgeboxAPI from "../forgeboxAPI"; +import * as semver from "semver"; + +type Decoration = { + lineNumber: number, + type: vscode.TextEditorDecorationType +}; + +type DependencyLine = { + line: vscode.TextLine, + name: string, + currentVersion: string +} + +let drawnDecorations: Record = {}; + +let forgeboxCache: Record = {}; + +const darkThemeDefaults : Record = { + major: "red", + premajor: "red", + minor: "yellow", + preminor: "yellow", + patch: "green", + prepatch: "green", + prerelease: "green" +}; + +const lightThemeDefaults : Record = { + major: "darkred", + premajor: "darkred", + minor: "darkorange", + preminor: "darkorange", + patch: "darkgreen", + prepatch: "darkgreen", + prerelease: "darkgreen" +}; + +export async function decorateBoxJSON(textEditor: vscode.TextEditor | undefined) { + console.log( 'running' ); + if (textEditor == undefined) { + return; + } + + if( !textEditor.document.fileName.endsWith( 'box.json' ) ){ + return; + } + + const linesWithDecorations = await Promise.all( getDependencyLines( textEditor.document ).map( async ( dependencyLine: DependencyLine ) => { + return [ dependencyLine, await getDecoration( dependencyLine ) ]; + })); + + linesWithDecorations.forEach( ( [ dependencyLine, decoration ] ) => { + decorateLine( textEditor, ( dependencyLine as DependencyLine).line.lineNumber, (decoration as vscode.DecorationRenderOptions) ); + }); +} + +async function getDecoration( dependencyLine: DependencyLine ) : Promise { + const forgeBoxVersion = await getForgeBoxVersion( dependencyLine.name ); + + if( forgeBoxVersion == null ){ + return { + after: { + color: "gray", + margin: "2em", + contentText: "Unable to retrieve package information" + } + }; + } + + console.log( forgeBoxVersion ); + + if( semver.satisfies( semver.minVersion( forgeBoxVersion ), dependencyLine.currentVersion ) ){ + return { + after: { + color: "gray", + margin: "2em", + contentText: `matches the latest version ${forgeBoxVersion}` + } + }; + } + + + const diff = semver.diff( semver.minVersion( dependencyLine.currentVersion ), semver.minVersion( forgeBoxVersion ) ); + + return { + light: { after: { color: getColor( diff, lightThemeDefaults ) } }, + dark: { after: { color: getColor( diff, darkThemeDefaults ) } }, + after: { + margin: "2em", + contentText: forgeBoxVersion + } + }; +} + +async function getForgeBoxVersion( slug: string ): Promise { + console.log( "getting version for " + slug ); + + if( forgeboxCache[ slug ] ){ + console.log( 'grabbing slug from cache' ); + return forgeboxCache[ slug ]; + } + + console.log( 'cache miss' ); + + const packageInfo = await forgeboxAPI.getPackageInfo( slug ); + + if( packageInfo instanceof forgeboxAPI.ForgeBoxError ){ + return null; + } + + packageInfo; + + forgeboxCache[ slug ] = packageInfo.latestVersion.version; + console.log( 'updating cache with: ' + forgeboxCache[ slug ] ); + return forgeboxCache[ slug ]; +} + +function decorateLine( textEditor: vscode.TextEditor, lineNumber: number, decorationOptions: vscode.DecorationRenderOptions ){ + const decoration = drawnDecorations[ lineNumber ]; + + if( decoration != null ){ + decoration.type.dispose(); + } + + const newDecoration = { + lineNumber, + type: vscode.window.createTextEditorDecorationType(decorationOptions) + }; + + const line = textEditor.document.lineAt( lineNumber ); + + textEditor.setDecorations( newDecoration.type, [ + new vscode.Range( line.range.end, line.range.end ) + ]); + + drawnDecorations[ lineNumber ] = newDecoration; +} + +export const getDependencyLines = (textDocument: vscode.TextDocument) : DependencyLine[] => { + const jsonAsTypescript = `let tmp=${textDocument.getText()}` + + const ast = parse(jsonAsTypescript, { + loc: true, + }) + + const variable = ast.body[0] as VariableDeclaration + + const tmp = variable.declarations[0] + + const init = tmp.init + if (init == null || init.type !== 'ObjectExpression') { + throw new Error(`unexpected type: ${init?.type}`) + } + + const properties = init.properties as TSESTree.Property[] + + const dependencies = properties.find( + (p) => (p.key as TSESTree.StringLiteral).value.toLowerCase() === 'dependencies', + ) + + const devDependencies = properties.find( + (p) => (p.key as TSESTree.StringLiteral).value.toLowerCase() === 'devdependencies', + ) + + return [ + ...extractDependencyLines( textDocument, dependencies ), + ...extractDependencyLines( textDocument, devDependencies ) + ]; +} + +function extractDependencyLines( textDocument, dependencyProperty ){ + if (dependencyProperty.value.type !== 'ObjectExpression') { + return []; + } + + return dependencyProperty.value.properties.map( ( dependency ) => { + return { + line: textDocument.lineAt( dependency.loc.end.line - 1 ), + name: dependency.key.value, + currentVersion: dependency.value.value + }; + }); +} + +function getColor( versionDiff: semver.ReleaseType, colorThemeDefaults ) : string { + + if( versionDiff === 'major' || versionDiff === "premajor" ){ + const colorOverride = vscode.workspace.getConfiguration( "commandbox.box" ).get( "majorUpdateColor" ); + + if( colorOverride ){ + return colorOverride; + } + } + else if( versionDiff === 'minor' || versionDiff === "preminor" ){ + const colorOverride = vscode.workspace.getConfiguration( "commandbox.box" ).get( "minorUpdateColor" ); + + if( colorOverride ){ + return colorOverride; + } + } + else if( versionDiff === 'patch' || versionDiff === "prepatch" || versionDiff === "prerelease" ){ + const colorOverride = vscode.workspace.getConfiguration( "commandbox.box" ).get( "patchUpdateColor" ); + + if( colorOverride ){ + return colorOverride; + } + } + + return colorThemeDefaults[ versionDiff ] ? colorThemeDefaults[ versionDiff ] : "gray"; +} \ No newline at end of file diff --git a/src/forgeboxAPI.ts b/src/forgeboxAPI.ts new file mode 100644 index 0000000..16ae678 --- /dev/null +++ b/src/forgeboxAPI.ts @@ -0,0 +1,73 @@ +import * as httpRequest from "request-light"; +import * as vscode from "vscode"; + +interface BoxPackageInfo { + title: string; + slug: string; + isActive: boolean; + latestVersion: PackageVersion; + summary?: string; + homeURL?: string; +} + +interface PackageVersion { + isActive: boolean; + version: string; + isStable: boolean; +} + +export class ForgeBoxError extends Error { + url: string; + response: httpRequest.XHRResponse; + + constructor( message: string, url: string, response?: httpRequest.XHRResponse ){ + super( message ); + + this.url = url; + this.response = response; + } +} + +const USER_AGENT = "Visual Studio Code"; +const httpSuccessStatusCode = 200; + +configureXHR(); + + +export async function getPackageInfo( packageName: string ): Promise { + const queryUrl = `${getEndpointURL()}/api/v1/entry/${encodeURIComponent( packageName )}`; + try{ + const response = await httpRequest.xhr( { + url : queryUrl, + headers : { agent: USER_AGENT } + } ); + + if ( response.status !== httpSuccessStatusCode ) { + return new ForgeBoxError( `Request for package ${packageName} was unsuccessful`, queryUrl, response ); + } + + const obj = JSON.parse( response.responseText ); + if ( !obj?.data ) { + return new ForgeBoxError( `Invalid API response for package ${packageName}`, queryUrl, response ); + } + + return obj.data as BoxPackageInfo; + } + catch( e ){ + return new ForgeBoxError( e.message, queryUrl ); + } +} + +function getEndpointURL() { + let endpointUrl: string = vscode.workspace.getConfiguration( "commandbox.forgebox" ).get( "endpointUrl" ); + + return endpointUrl.endsWith( "/" ) ? endpointUrl = endpointUrl.slice( 0, -1 ) : endpointUrl; +} + +function configureXHR(): void { + const httpSettings = vscode.workspace.getConfiguration( "http" ); + const proxyUrl = httpSettings.get( "proxy", "" ); + const strictSSL = httpSettings.get( "proxyStrictSSL", true ); + + httpRequest.configure( proxyUrl, strictSSL ); +} \ No newline at end of file