@@ -3,11 +3,21 @@ import * as vscode from 'vscode';
33import * as models from '../models' ;
44import * as path from 'path' ;
55import * as fs from 'fs' ;
6+ import * as semver from 'semver' ;
7+ import { Octokit } from 'octokit' ;
8+ import { Endpoints } from "@octokit/types" ;
9+
10+ const octokit = new Octokit ( ) ;
11+ type ReleaseRequest = Endpoints [ "GET /repos/{owner}/{repo}/releases/latest" ] [ "parameters" ] ;
12+ type ReleaseResponse = Endpoints [ "GET /repos/{owner}/{repo}/releases/latest" ] [ "response" ] ;
13+
14+ const taskCommand = 'task' ;
15+ const minimumRequiredVersion = '3.19.1' ;
16+ const minimumRecommendedVersion = '3.23.0' ;
617
718class TaskfileService {
819 private static _instance : TaskfileService ;
920 private static outputChannel : vscode . OutputChannel ;
10- private static readonly taskCommand = 'task' ;
1121 private lastTaskName : string | undefined ;
1222 private lastTaskDir : string | undefined ;
1323
@@ -19,9 +29,87 @@ class TaskfileService {
1929 return this . _instance ?? ( this . _instance = new this ( ) ) ;
2030 }
2131
32+ public async checkInstallation ( ) : Promise < void > {
33+ return await new Promise ( ( resolve ) => {
34+ cp . exec ( `${ taskCommand } --version` , ( _ , stdout : string , stderr : string ) => {
35+
36+ // If the version is a devel version, ignore all version checks
37+ if ( stdout . includes ( "devel" ) ) {
38+ return resolve ( ) ;
39+ }
40+
41+ // Get the installed version of task (if any)
42+ let version = this . parseVersion ( stdout ) ;
43+
44+ // If there is an error fetching the version, assume task is not installed
45+ if ( stderr !== "" || version === undefined ) {
46+ vscode . window . showErrorMessage ( "Task command not found." , "Install" ) . then ( this . buttonCallback ) ;
47+ return resolve ( ) ;
48+ }
49+
50+ // If the current version is older than the minimum required version, show an error
51+ if ( version && version . compare ( minimumRequiredVersion ) < 0 ) {
52+ vscode . window . showErrorMessage ( `Task v${ minimumRequiredVersion } is required to run this extension. Your current version is v${ version } .` , "Update" ) . then ( this . buttonCallback ) ;
53+ return resolve ( ) ;
54+ }
55+
56+ // If the current version is older than the minimum recommended version, show a warning
57+ if ( version && version . compare ( minimumRecommendedVersion ) < 0 ) {
58+ vscode . window . showWarningMessage ( `Task v${ minimumRecommendedVersion } is recommended to run this extension. Your current version is v${ version } which doesn't support some features.` , "Update" ) . then ( this . buttonCallback ) ;
59+ return resolve ( ) ;
60+ }
61+
62+ // If a newer version is available, show a message
63+ // TODO: what happens if the user is offline?
64+ this . getLatestVersion ( ) . then ( ( latestVersion ) => {
65+ if ( version && latestVersion && version . compare ( latestVersion ) < 0 ) {
66+ vscode . window . showInformationMessage ( `A new version of Task is available. Current version: v${ version } , Latest version: v${ latestVersion } ` , "Update" ) . then ( this . buttonCallback ) ;
67+ }
68+ return resolve ( ) ;
69+ } ) . catch ( ( err ) => {
70+ console . error ( err ) ;
71+ return resolve ( ) ;
72+ } ) ;
73+ } ) ;
74+ } ) ;
75+ }
76+
77+ buttonCallback ( value : string | undefined ) {
78+ if ( value === undefined ) {
79+ return ;
80+ }
81+ if ( [ "Update" , "Install" ] . includes ( value ) ) {
82+ vscode . env . openExternal ( vscode . Uri . parse ( "https://taskfile.dev/installation" ) ) ;
83+ return ;
84+ }
85+ }
86+
87+ async getLatestVersion ( ) : Promise < semver . SemVer | null > {
88+ let request : ReleaseRequest = {
89+ owner : 'go-task' ,
90+ repo : 'task'
91+ } ;
92+ let response : ReleaseResponse = await octokit . rest . repos . getLatestRelease ( request ) ;
93+ return Promise . resolve ( semver . parse ( response . data . tag_name ) ) ;
94+ }
95+
96+ parseVersion ( stdout : string ) : semver . SemVer | undefined {
97+ // Extract the version string from the output
98+ let matches = stdout . match ( / v ( \d + \. \d + \. \d + ) / ) ;
99+ if ( ! matches || matches . length !== 2 ) {
100+ return undefined ;
101+ }
102+ // Parse the version string as a semver
103+ let version = semver . parse ( matches [ 1 ] ) ;
104+ if ( ! version ) {
105+ return undefined ;
106+ }
107+ return version ;
108+ }
109+
22110 public async init ( dir : string ) : Promise < void > {
23111 return await new Promise ( ( resolve ) => {
24- let command = 'task --init' ;
112+ let command = ` ${ taskCommand } --init` ;
25113 cp . exec ( command , { cwd : dir } , ( _ , stdout : string , stderr : string ) => {
26114 if ( stderr ) {
27115 vscode . window . showErrorMessage ( stderr ) ;
@@ -38,7 +126,6 @@ class TaskfileService {
38126 for ( let i = 0 ; i < filenames . length ; i ++ ) {
39127 let filename = path . join ( dir , filenames [ i ] ) ;
40128 if ( fs . existsSync ( filename ) ) {
41- console . log ( filename ) ;
42129 await vscode . commands . executeCommand ( 'vscode.open' , vscode . Uri . parse ( filename ) , { preview : false } ) ;
43130 return ;
44131 }
@@ -47,7 +134,7 @@ class TaskfileService {
47134
48135 public async read ( dir : string ) : Promise < models . Taskfile > {
49136 return await new Promise ( ( resolve ) => {
50- let command = 'task --list-all --json' ;
137+ let command = ` ${ taskCommand } --list-all --json` ;
51138 cp . exec ( command , { cwd : dir } , ( _ , stdout : string ) => {
52139 var taskfile : models . Taskfile = JSON . parse ( stdout ) ;
53140 taskfile . workspace = dir ;
@@ -67,7 +154,7 @@ class TaskfileService {
67154 public async runTask ( taskName : string , dir ?: string ) : Promise < void > {
68155 return await new Promise ( ( resolve ) => {
69156 // Spawn a child process
70- let child = cp . spawn ( TaskfileService . taskCommand , [ taskName ] , { cwd : dir } ) ;
157+ let child = cp . spawn ( taskCommand , [ taskName ] , { cwd : dir } ) ;
71158
72159 // Clear the output channel and show it
73160 TaskfileService . outputChannel . clear ( ) ;
@@ -97,7 +184,7 @@ class TaskfileService {
97184
98185 public async goToDefinition ( task : models . Task , preview : boolean = false ) : Promise < void > {
99186 if ( task . location === undefined ) {
100- vscode . window . showErrorMessage ( `Go to definition requires Task v3.23.0 or higher.` ) ;
187+ vscode . window . showErrorMessage ( `Go to definition requires Task v3.23.0 or higher.` , "Update" ) . then ( this . buttonCallback ) ;
101188 return ;
102189 }
103190
0 commit comments