11import { relative , resolve , sep } from 'path' ;
22import * as cp from 'child_process' ;
33import { readFile } from 'fs/promises' ;
4+ import { existsSync } from 'fs' ;
45
56import * as yaml from 'js-yaml' ;
67import * as vscode from 'vscode' ;
@@ -9,7 +10,8 @@ let channel: vscode.OutputChannel;
910
1011function run ( file : string | vscode . Uri | null | undefined ) {
1112 if ( ! file ) {
12- run ( vscode . window . activeTextEditor ?. document . uri ) ;
13+ if ( ! vscode . window . activeTextEditor ) return ;
14+ run ( vscode . window . activeTextEditor . document . uri ) ;
1315 return ;
1416 }
1517
@@ -200,6 +202,7 @@ class StatusProvider implements vscode.Disposable {
200202
201203 private _status : Status = 'idle' ;
202204 private _owner : Owner | undefined = undefined ;
205+ private _isConfigured : boolean | null = null ;
203206
204207 get status ( ) : Status {
205208 return this . _status ;
@@ -217,6 +220,14 @@ class StatusProvider implements vscode.Disposable {
217220 this . update ( ) ;
218221 }
219222
223+ get isConfigured ( ) : boolean | null {
224+ return this . _isConfigured ;
225+ }
226+ set isConfigured ( value : boolean | null ) {
227+ this . _isConfigured = value ;
228+ this . update ( ) ;
229+ }
230+
220231 private update ( ) {
221232 if ( this . status === 'error' ) {
222233 this . statusBarItem . command = 'code-ownership-vscode.showOutputChannel' ;
@@ -237,9 +248,14 @@ class StatusProvider implements vscode.Disposable {
237248 this . statusBarItem . text = `$(account) Owner: ${ this . owner . teamName } ` ;
238249 this . statusBarItem . tooltip = undefined ;
239250 this . statusBarItem . show ( ) ;
251+ } else if ( this . isConfigured === false ) {
252+ this . statusBarItem . text = `$(info) Ownership: not configured` ;
253+ this . statusBarItem . tooltip =
254+ 'This workspace is not configured for code ownership' ;
255+ this . statusBarItem . show ( ) ;
240256 } else {
241257 this . statusBarItem . text = `$(warning) Owner: none` ;
242- this . statusBarItem . tooltip = undefined ;
258+ this . statusBarItem . tooltip = 'This file has no assigned team ownership' ;
243259 this . statusBarItem . show ( ) ;
244260 }
245261 }
@@ -251,92 +267,129 @@ class StatusProvider implements vscode.Disposable {
251267}
252268
253269class Worker implements vscode . Disposable {
270+ private isConfigured : boolean | null = null ;
271+
254272 constructor (
255273 private readonly workspace : vscode . WorkspaceFolder ,
256274 private readonly statusProvider : StatusProvider ,
257- ) { }
275+ ) {
276+ this . checkConfiguration ( ) ;
277+ }
278+
279+ private async checkConfiguration ( ) : Promise < void > {
280+ const binaryPath = resolve ( this . workspace . uri . fsPath , 'bin/codeownership' ) ;
281+ this . isConfigured = existsSync ( binaryPath ) ;
282+ this . statusProvider . isConfigured = this . isConfigured ;
283+
284+ if ( ! this . isConfigured ) {
285+ log (
286+ 'info' ,
287+ `No code ownership binary found in workspace: ${ this . workspace . name } ` ,
288+ ) ;
289+ } else {
290+ log (
291+ 'info' ,
292+ `Code ownership binary found in workspace: ${ this . workspace . name } ` ,
293+ ) ;
294+ }
295+ }
258296
259297 workspaceHas ( file : vscode . Uri ) : boolean {
260298 return file . fsPath . startsWith ( this . workspace . uri . fsPath ) ;
261299 }
262300
263301 async run ( file : vscode . Uri ) : Promise < void > {
264- if ( this . workspaceHas ( file ) ) {
265- this . statusProvider . status = 'working' ;
266-
267- await new Promise ( ( r ) => setTimeout ( r , 50 ) ) ;
268-
269- // bin/codeownership currenlty wants relative paths
270- const cwd = this . workspace . uri . fsPath ;
271- const relativePath = relative ( cwd , file . fsPath ) ;
272-
273- logSpace ( ) ;
274- log ( 'debug' , `cwd: ${ cwd } ` ) ;
275- log ( 'debug' , `workspace: ${ this . workspace . uri . fsPath } ` ) ;
276- log ( 'debug' , `file path: ${ file . fsPath } ` ) ;
277- log ( 'debug' , `relative path: ${ relativePath } ` ) ;
278-
279- try {
280- const output = await runCommand (
281- cwd ,
282- `bin/codeownership for_file "${ relativePath } " --json` ,
283- this . statusProvider ,
284- ) ;
302+ if ( ! this . workspaceHas ( file ) ) return ;
285303
286- const obj = JSON . parse ( output ) ;
304+ if ( this . isConfigured === null ) {
305+ await this . checkConfiguration ( ) ;
306+ }
287307
288- if ( typeof obj . team_name !== 'string' ) {
289- log (
290- 'warning' ,
291- 'Missing expected property `team_name` in command output' ,
292- ) ;
293- }
294- if ( typeof obj . team_yml !== 'string' ) {
295- log (
296- 'warning' ,
297- 'Missing expected property `team_yml` in command output' ,
298- ) ;
299- }
308+ if ( ! this . isConfigured ) {
309+ this . statusProvider . owner = undefined ;
310+ this . statusProvider . status = 'idle' ;
311+ return ;
312+ }
300313
301- if (
302- typeof obj . team_name === 'string' &&
303- typeof obj . team_yml === 'string' &&
304- obj . team_name !== 'Unowned'
305- ) {
306- const teamConfig = resolve ( this . workspace . uri . fsPath , obj . team_yml ) ;
307-
308- const actions : UserAction [ ] = [ ] ;
309-
310- const slackChannel = await getSlackChannel ( teamConfig ) ;
311-
312- if ( slackChannel ) {
313- actions . push ( {
314- title : `Slack: #${ slackChannel } ` ,
315- uri : vscode . Uri . parse (
316- `https://slack.com/app_redirect?channel=${ slackChannel } ` ,
317- ) ,
318- } ) ;
319- }
320-
321- actions . push ( {
322- title : 'View team config' ,
323- uri : vscode . Uri . parse ( teamConfig ) ,
324- } ) ;
325-
326- this . statusProvider . owner = {
327- filepath : file . fsPath ,
328- teamName : obj . team_name ,
329- teamConfig,
330- actions,
331- } ;
332- } else {
333- this . statusProvider . owner = undefined ;
334- }
314+ this . statusProvider . status = 'working' ;
315+ await new Promise ( ( r ) => setTimeout ( r , 50 ) ) ;
316+
317+ const cwd = this . workspace . uri . fsPath ;
318+ const relativePath = relative ( cwd , file . fsPath ) ;
319+
320+ logSpace ( ) ;
321+ log ( 'info' , `Checking ownership for ${ relativePath } ` ) ;
322+ log ( 'debug' , `cwd: ${ cwd } ` ) ;
323+ log ( 'debug' , `workspace: ${ this . workspace . uri . fsPath } ` ) ;
324+ log ( 'debug' , `file path: ${ file . fsPath } ` ) ;
325+
326+ // Run ownership check
327+ const output = await runCommand (
328+ cwd ,
329+ `bin/codeownership for_file "${ relativePath } " --json` ,
330+ this . statusProvider ,
331+ ) ;
332+
333+ if ( ! output ) {
334+ log ( 'info' , 'Code ownership check returned no output' ) ;
335+ this . statusProvider . owner = undefined ;
336+ this . statusProvider . status = 'idle' ;
337+ return ;
338+ }
339+
340+ try {
341+ const obj = JSON . parse ( output ) ;
342+
343+ if ( ! obj . team_name ) {
344+ log ( 'info' , 'No team name found in ownership data' ) ;
345+ this . statusProvider . owner = undefined ;
335346 this . statusProvider . status = 'idle' ;
336- } catch {
337- this . statusProvider . status = 'error' ;
338- log ( 'error' , 'Error parsing command output' ) ;
347+ return ;
339348 }
349+
350+ if ( ! obj . team_yml ) {
351+ log ( 'info' , 'No team config file found in ownership data' ) ;
352+ this . statusProvider . owner = undefined ;
353+ this . statusProvider . status = 'idle' ;
354+ return ;
355+ }
356+
357+ if ( obj . team_name === 'Unowned' ) {
358+ log ( 'info' , 'File is explicitly unowned' ) ;
359+ this . statusProvider . owner = undefined ;
360+ this . statusProvider . status = 'idle' ;
361+ return ;
362+ }
363+
364+ const teamConfig = resolve ( this . workspace . uri . fsPath , obj . team_yml ) ;
365+ const actions : UserAction [ ] = [ ] ;
366+
367+ const slackChannel = await getSlackChannel ( teamConfig ) ;
368+ if ( slackChannel ) {
369+ actions . push ( {
370+ title : `Slack: #${ slackChannel } ` ,
371+ uri : vscode . Uri . parse (
372+ `https://slack.com/app_redirect?channel=${ slackChannel } ` ,
373+ ) ,
374+ } ) ;
375+ }
376+
377+ actions . push ( {
378+ title : 'View team config' ,
379+ uri : vscode . Uri . parse ( teamConfig ) ,
380+ } ) ;
381+
382+ this . statusProvider . owner = {
383+ filepath : file . fsPath ,
384+ teamName : obj . team_name ,
385+ teamConfig,
386+ actions,
387+ } ;
388+ this . statusProvider . status = 'idle' ;
389+ } catch ( error ) {
390+ log ( 'info' , `Invalid ownership data format: ${ error . message } ` ) ;
391+ this . statusProvider . owner = undefined ;
392+ this . statusProvider . status = 'idle' ;
340393 }
341394 }
342395
0 commit comments