@@ -12,6 +12,7 @@ import {
1212import { ProgressCallback } from "../progress" ;
1313import { DatabaseItem } from "../local-databases" ;
1414import { getQlPackPath , QLPACK_FILENAMES } from "../pure/ql" ;
15+ import { getErrorMessage } from "../pure/helpers-pure" ;
1516
1617const maxStep = 3 ;
1718
@@ -22,8 +23,14 @@ const packNameRegex = new RegExp(
2223const packNameLength = 128 ;
2324
2425export interface ExtensionPack {
25- name : string ;
2626 path : string ;
27+ yamlPath : string ;
28+
29+ name : string ;
30+ version : string ;
31+
32+ extensionTargets : Record < string , string > ;
33+ dataExtensions : string [ ] ;
2734}
2835
2936export interface ExtensionPackModelFile {
@@ -50,7 +57,7 @@ export async function pickExtensionPackModelFile(
5057 const modelFile = await pickModelFile (
5158 cliServer ,
5259 databaseItem ,
53- extensionPack . path ,
60+ extensionPack ,
5461 progress ,
5562 token ,
5663 ) ;
@@ -78,19 +85,72 @@ async function pickExtensionPack(
7885
7986 // Get all existing extension packs in the workspace
8087 const additionalPacks = getOnDiskWorkspaceFolders ( ) ;
81- const extensionPacks = await cliServer . resolveQlpacks ( additionalPacks , true ) ;
88+ const extensionPacksInfo = await cliServer . resolveQlpacks (
89+ additionalPacks ,
90+ true ,
91+ ) ;
8292
83- if ( Object . keys ( extensionPacks ) . length === 0 ) {
93+ if ( Object . keys ( extensionPacksInfo ) . length === 0 ) {
8494 return pickNewExtensionPack ( databaseItem , token ) ;
8595 }
8696
87- const options : Array < { label : string ; extensionPack : string | null } > =
88- Object . keys ( extensionPacks ) . map ( ( pack ) => ( {
89- label : pack ,
90- extensionPack : pack ,
91- } ) ) ;
97+ const extensionPacks = (
98+ await Promise . all (
99+ Object . entries ( extensionPacksInfo ) . map ( async ( [ name , paths ] ) => {
100+ if ( paths . length !== 1 ) {
101+ void showAndLogErrorMessage (
102+ `Extension pack ${ name } resolves to multiple paths` ,
103+ {
104+ fullMessage : `Extension pack ${ name } resolves to multiple paths: ${ paths . join (
105+ ", " ,
106+ ) } `,
107+ } ,
108+ ) ;
109+
110+ return undefined ;
111+ }
112+
113+ const path = paths [ 0 ] ;
114+
115+ let extensionPack : ExtensionPack ;
116+ try {
117+ extensionPack = await readExtensionPack ( path ) ;
118+ } catch ( e : unknown ) {
119+ void showAndLogErrorMessage ( `Could not read extension pack ${ name } ` , {
120+ fullMessage : `Could not read extension pack ${ name } at ${ path } : ${ getErrorMessage (
121+ e ,
122+ ) } `,
123+ } ) ;
124+
125+ return undefined ;
126+ }
127+
128+ return extensionPack ;
129+ } ) ,
130+ )
131+ ) . filter ( ( info ) : info is ExtensionPack => info !== undefined ) ;
132+
133+ const extensionPacksForLanguage = extensionPacks . filter (
134+ ( pack ) =>
135+ pack . extensionTargets [ `codeql/${ databaseItem . language } -all` ] !==
136+ undefined ,
137+ ) ;
138+
139+ const options : Array < {
140+ label : string ;
141+ description : string | undefined ;
142+ detail : string | undefined ;
143+ extensionPack : ExtensionPack | null ;
144+ } > = extensionPacksForLanguage . map ( ( pack ) => ( {
145+ label : pack . name ,
146+ description : pack . version ,
147+ detail : pack . path ,
148+ extensionPack : pack ,
149+ } ) ) ;
92150 options . push ( {
93151 label : "Create new extension pack" ,
152+ description : undefined ,
153+ detail : undefined ,
94154 extensionPack : null ,
95155 } ) ;
96156
@@ -115,57 +175,39 @@ async function pickExtensionPack(
115175 return pickNewExtensionPack ( databaseItem , token ) ;
116176 }
117177
118- const extensionPackPaths = extensionPacks [ extensionPackOption . extensionPack ] ;
119- if ( extensionPackPaths . length !== 1 ) {
120- void showAndLogErrorMessage (
121- `Extension pack ${ extensionPackOption . extensionPack } could not be resolved to a single location` ,
122- {
123- fullMessage : `Extension pack ${
124- extensionPackOption . extensionPack
125- } could not be resolved to a single location. Found ${
126- extensionPackPaths . length
127- } locations: ${ extensionPackPaths . join ( ", " ) } .`,
128- } ,
129- ) ;
130- return undefined ;
131- }
132-
133- return {
134- name : extensionPackOption . extensionPack ,
135- path : extensionPackPaths [ 0 ] ,
136- } ;
178+ return extensionPackOption . extensionPack ;
137179}
138180
139181async function pickModelFile (
140182 cliServer : Pick < CodeQLCliServer , "resolveExtensions" > ,
141183 databaseItem : Pick < DatabaseItem , "name" > ,
142- extensionPackPath : string ,
184+ extensionPack : ExtensionPack ,
143185 progress : ProgressCallback ,
144186 token : CancellationToken ,
145187) : Promise < string | undefined > {
146188 // Find the existing model files in the extension pack
147189 const additionalPacks = getOnDiskWorkspaceFolders ( ) ;
148190 const extensions = await cliServer . resolveExtensions (
149- extensionPackPath ,
191+ extensionPack . path ,
150192 additionalPacks ,
151193 ) ;
152194
153195 const modelFiles = new Set < string > ( ) ;
154196
155- if ( extensionPackPath in extensions . data ) {
156- for ( const extension of extensions . data [ extensionPackPath ] ) {
197+ if ( extensionPack . path in extensions . data ) {
198+ for ( const extension of extensions . data [ extensionPack . path ] ) {
157199 modelFiles . add ( extension . file ) ;
158200 }
159201 }
160202
161203 if ( modelFiles . size === 0 ) {
162- return pickNewModelFile ( databaseItem , extensionPackPath , token ) ;
204+ return pickNewModelFile ( databaseItem , extensionPack , token ) ;
163205 }
164206
165207 const fileOptions : Array < { label : string ; file : string | null } > = [ ] ;
166208 for ( const file of modelFiles ) {
167209 fileOptions . push ( {
168- label : relative ( extensionPackPath , file ) . replaceAll ( sep , "/" ) ,
210+ label : relative ( extensionPack . path , file ) . replaceAll ( sep , "/" ) ,
169211 file,
170212 } ) ;
171213 }
@@ -196,7 +238,7 @@ async function pickModelFile(
196238 return fileOption . file ;
197239 }
198240
199- return pickNewModelFile ( databaseItem , extensionPackPath , token ) ;
241+ return pickNewModelFile ( databaseItem , extensionPack , token ) ;
200242}
201243
202244async function pickNewExtensionPack (
@@ -266,66 +308,36 @@ async function pickNewExtensionPack(
266308
267309 const packYamlPath = join ( packPath , "codeql-pack.yml" ) ;
268310
311+ const extensionPack : ExtensionPack = {
312+ path : packPath ,
313+ yamlPath : packYamlPath ,
314+ name,
315+ version : "0.0.0" ,
316+ extensionTargets : {
317+ [ `codeql/${ databaseItem . language } -all` ] : "*" ,
318+ } ,
319+ dataExtensions : [ "models/**/*.yml" ] ,
320+ } ;
321+
269322 await outputFile (
270323 packYamlPath ,
271324 dumpYaml ( {
272- name,
273- version : "0.0.0" ,
325+ name : extensionPack . name ,
326+ version : extensionPack . version ,
274327 library : true ,
275- extensionTargets : {
276- [ `codeql/${ databaseItem . language } -all` ] : "*" ,
277- } ,
278- dataExtensions : [ "models/**/*.yml" ] ,
328+ extensionTargets : extensionPack . extensionTargets ,
329+ dataExtensions : extensionPack . dataExtensions ,
279330 } ) ,
280331 ) ;
281332
282- return {
283- name : packName ,
284- path : packPath ,
285- } ;
333+ return extensionPack ;
286334}
287335
288336async function pickNewModelFile (
289337 databaseItem : Pick < DatabaseItem , "name" > ,
290- extensionPackPath : string ,
338+ extensionPack : ExtensionPack ,
291339 token : CancellationToken ,
292340) {
293- const qlpackPath = await getQlPackPath ( extensionPackPath ) ;
294- if ( ! qlpackPath ) {
295- void showAndLogErrorMessage (
296- `Could not find any of ${ QLPACK_FILENAMES . join (
297- ", " ,
298- ) } in ${ extensionPackPath } `,
299- ) ;
300- return undefined ;
301- }
302-
303- const qlpack = await loadYaml ( await readFile ( qlpackPath , "utf8" ) , {
304- filename : qlpackPath ,
305- } ) ;
306- if ( typeof qlpack !== "object" || qlpack === null ) {
307- void showAndLogErrorMessage ( `Could not parse ${ qlpackPath } ` ) ;
308- return undefined ;
309- }
310-
311- const dataExtensionPatternsValue = qlpack . dataExtensions ;
312- if (
313- ! (
314- Array . isArray ( dataExtensionPatternsValue ) ||
315- typeof dataExtensionPatternsValue === "string"
316- )
317- ) {
318- void showAndLogErrorMessage (
319- `Expected 'dataExtensions' to be a string or an array in ${ qlpackPath } ` ,
320- ) ;
321- return undefined ;
322- }
323-
324- // The YAML allows either a string or an array of strings
325- const dataExtensionPatterns = Array . isArray ( dataExtensionPatternsValue )
326- ? dataExtensionPatternsValue
327- : [ dataExtensionPatternsValue ] ;
328-
329341 const filename = await window . showInputBox (
330342 {
331343 title : "Enter the name of the new model file" ,
@@ -335,24 +347,25 @@ async function pickNewModelFile(
335347 return "File name must not be empty" ;
336348 }
337349
338- const path = resolve ( extensionPackPath , value ) ;
350+ const path = resolve ( extensionPack . path , value ) ;
339351
340352 if ( await pathExists ( path ) ) {
341353 return "File already exists" ;
342354 }
343355
344- const notInExtensionPack = relative ( extensionPackPath , path ) . startsWith (
345- ".." ,
346- ) ;
356+ const notInExtensionPack = relative (
357+ extensionPack . path ,
358+ path ,
359+ ) . startsWith ( ".." ) ;
347360 if ( notInExtensionPack ) {
348361 return "File must be in the extension pack" ;
349362 }
350363
351- const matchesPattern = dataExtensionPatterns . some ( ( pattern ) =>
364+ const matchesPattern = extensionPack . dataExtensions . some ( ( pattern ) =>
352365 minimatch ( value , pattern , { matchBase : true } ) ,
353366 ) ;
354367 if ( ! matchesPattern ) {
355- return `File must match one of the patterns in 'dataExtensions' in ${ qlpackPath } ` ;
368+ return `File must match one of the patterns in 'dataExtensions' in ${ extensionPack . yamlPath } ` ;
356369 }
357370
358371 return undefined ;
@@ -364,5 +377,47 @@ async function pickNewModelFile(
364377 return undefined ;
365378 }
366379
367- return resolve ( extensionPackPath , filename ) ;
380+ return resolve ( extensionPack . path , filename ) ;
381+ }
382+
383+ async function readExtensionPack ( path : string ) : Promise < ExtensionPack > {
384+ const qlpackPath = await getQlPackPath ( path ) ;
385+ if ( ! qlpackPath ) {
386+ throw new Error (
387+ `Could not find any of ${ QLPACK_FILENAMES . join ( ", " ) } in ${ path } ` ,
388+ ) ;
389+ }
390+
391+ const qlpack = await loadYaml ( await readFile ( qlpackPath , "utf8" ) , {
392+ filename : qlpackPath ,
393+ } ) ;
394+ if ( typeof qlpack !== "object" || qlpack === null ) {
395+ throw new Error ( `Could not parse ${ qlpackPath } ` ) ;
396+ }
397+
398+ const dataExtensionValue = qlpack . dataExtensions ;
399+ if (
400+ ! (
401+ Array . isArray ( dataExtensionValue ) ||
402+ typeof dataExtensionValue === "string"
403+ )
404+ ) {
405+ throw new Error (
406+ `Expected 'dataExtensions' to be a string or an array in ${ qlpackPath } ` ,
407+ ) ;
408+ }
409+
410+ // The YAML allows either a string or an array of strings
411+ const dataExtensions = Array . isArray ( dataExtensionValue )
412+ ? dataExtensionValue
413+ : [ dataExtensionValue ] ;
414+
415+ return {
416+ path,
417+ yamlPath : qlpackPath ,
418+ name : qlpack . name ,
419+ version : qlpack . version ,
420+ extensionTargets : qlpack . extensionTargets ,
421+ dataExtensions,
422+ } ;
368423}
0 commit comments