@@ -11,7 +11,7 @@ import {
11
11
adderIds ,
12
12
getAdderDetails ,
13
13
communityAdderIds ,
14
- getCommunityAdders
14
+ getCommunityAdder
15
15
} from '@svelte-cli/adders' ;
16
16
import {
17
17
createOrUpdateFiles ,
@@ -39,7 +39,7 @@ const OptionsSchema = v.strictObject({
39
39
cwd : v . string ( ) ,
40
40
install : v . boolean ( ) ,
41
41
preconditions : v . boolean ( ) ,
42
- community : v . optional ( AddersSchema ) ,
42
+ community : v . optional ( v . union ( [ AddersSchema , v . boolean ( ) ] ) ) ,
43
43
...AdderOptionFlagsSchema . entries
44
44
} ) ;
45
45
type Options = v . InferOutput < typeof OptionsSchema > ;
@@ -76,17 +76,6 @@ export const add = new Command('add')
76
76
process . exit ( 1 ) ;
77
77
}
78
78
79
- // we'll print a list of available options when `--community` is specified with no args
80
- if ( opts . community === true ) {
81
- console . log ( 'Usage: sv add --community [adder...]\n' ) ;
82
- console . log ( 'Applies community made adders\n' ) ;
83
- console . log ( `Available options: ${ communityAdderIds . join ( ', ' ) } \n` ) ;
84
- console . warn (
85
- 'The Svelte maintainers have not reviewed community adders for malicious code. Use at your discretion.'
86
- ) ;
87
- process . exit ( 0 ) ;
88
- }
89
-
90
79
const adders = v . parse ( AddersSchema , adderArgs ) ;
91
80
const options = v . parse ( OptionsSchema , opts ) ;
92
81
@@ -185,8 +174,47 @@ export async function runAddCommand(options: Options, adders: string[]): Promise
185
174
}
186
175
}
187
176
177
+ type AdderChoices = Record < string , Array < { value : string ; label : string } > > ;
178
+
179
+ // we'll let the user choose community adders when `--community` is specified without args
180
+ if ( options . community === true ) {
181
+ const promptOptions : AdderChoices = { } ;
182
+ const communityAdders = await Promise . all (
183
+ communityAdderIds . map ( async ( id ) => ( { id, ...( await getCommunityAdder ( id ) ) } ) )
184
+ ) ;
185
+ const categories = new Set ( communityAdders . map ( ( adder ) => adder . category ) ) ;
186
+
187
+ for ( const category of categories ) {
188
+ promptOptions [ category ] = communityAdders
189
+ . filter ( ( adder ) => adder . category === category )
190
+ . map ( ( adder ) => ( {
191
+ value : adder . id ,
192
+ label : adder . name ,
193
+ hint : adder . repo
194
+ } ) ) ;
195
+ }
196
+
197
+ const selected = await p . groupMultiselect ( {
198
+ message : 'Which community tools would you like to add to your project?' ,
199
+ options : promptOptions ,
200
+ spacedGroups : true ,
201
+ selectableGroups : false ,
202
+ required : false
203
+ } ) ;
204
+
205
+ if ( p . isCancel ( selected ) ) {
206
+ p . cancel ( 'Operation cancelled.' ) ;
207
+ process . exit ( 1 ) ;
208
+ } else if ( selected . length === 0 ) {
209
+ p . cancel ( 'No adders selected. Exiting.' ) ;
210
+ process . exit ( 1 ) ;
211
+ }
212
+
213
+ options . community = selected ;
214
+ }
215
+
188
216
// validate and download community adders
189
- if ( options . community && options . community ? .length > 0 ) {
217
+ if ( Array . isArray ( options . community ) && options . community . length > 0 ) {
190
218
// validate adders
191
219
const adders = options . community . map ( ( id ) => {
192
220
// ids with directives are passed unmodified so they can be processed during downloads
@@ -208,16 +236,33 @@ export async function runAddCommand(options: Options, adders: string[]): Promise
208
236
start ( 'Resolving community adder packages' ) ;
209
237
const pkgs = await Promise . all (
210
238
adders . map ( async ( id ) => {
211
- const pkg = await getCommunityAdders ( id ) . catch ( ( ) => undefined ) ;
212
- const packageName = pkg ?. npm ?? id ;
213
- return getPackageJSON ( { cwd : options . cwd , packageName } ) ;
239
+ const adder = await getCommunityAdder ( id ) . catch ( ( ) => undefined ) ;
240
+ const packageName = adder ?. npm ?? id ;
241
+ const details = await getPackageJSON ( { cwd : options . cwd , packageName } ) ;
242
+ return {
243
+ ...details ,
244
+ // prioritize community adder defined repo urls
245
+ repo : adder ?. repo ?? details . repo
246
+ } ;
214
247
} )
215
248
) ;
216
249
stop ( 'Resolved community adder packages' ) ;
217
250
218
- const ids = pkgs . map ( ( { pkg } ) => pc . yellowBright ( pkg . name ) + pc . dim ( ` (v${ pkg . version } )` ) ) ;
219
- p . log . warn ( 'Community packages are not reviewed for malicious code:' ) ;
220
- p . log . message ( ids . join ( ', ' ) ) ;
251
+ p . log . warn (
252
+ 'The Svelte maintainers have not reviewed community adders for malicious code. Use at your discretion.'
253
+ ) ;
254
+
255
+ const paddingName = getPadding ( pkgs . map ( ( { pkg } ) => pkg . name ) ) ;
256
+ const paddingVersion = getPadding ( pkgs . map ( ( { pkg } ) => `(v${ pkg . version } )` ) ) ;
257
+
258
+ const packageInfos = pkgs . map ( ( { pkg, repo : _repo } ) => {
259
+ const name = pc . yellowBright ( pkg . name . padEnd ( paddingName ) ) ;
260
+ const version = pc . dim ( `(v${ pkg . version } )` . padEnd ( paddingVersion ) ) ;
261
+ const repo = pc . dim ( `(${ _repo } )` ) ;
262
+ return `${ name } ${ version } ${ repo } ` ;
263
+ } ) ;
264
+ p . log . message ( packageInfos . join ( '\n' ) ) ;
265
+
221
266
const confirm = await p . confirm ( { message : 'Would you like to continue?' } ) ;
222
267
if ( confirm !== true ) {
223
268
p . cancel ( 'Operation cancelled.' ) ;
@@ -241,24 +286,28 @@ export async function runAddCommand(options: Options, adders: string[]): Promise
241
286
242
287
// prompt which adders to apply
243
288
if ( selectedAdders . length === 0 ) {
244
- const adderOptions : Record < string , Array < { value : string ; label : string } > > = { } ;
289
+ const adderOptions : AdderChoices = { } ;
245
290
const workspace = createWorkspace ( options . cwd ) ;
246
291
const projectType = workspace . kit ? 'kit' : 'svelte' ;
247
- for ( const { id , name } of Object . values ( categories ) ) {
248
- const category = adderCategories [ id ] ;
249
- const categoryOptions = category
292
+ for ( const category of categories ) {
293
+ const adderIds = adderCategories [ category ] ;
294
+ const categoryOptions = adderIds
250
295
. map ( ( id ) => {
251
296
const config = getAdderDetails ( id ) . config ;
252
297
// we'll only display adders within their respective project types
253
298
if ( projectType === 'kit' && ! config . metadata . environments . kit ) return ;
254
299
if ( projectType === 'svelte' && ! config . metadata . environments . svelte ) return ;
255
300
256
- return { label : config . metadata . name , value : config . metadata . id } ;
301
+ return {
302
+ label : config . metadata . name ,
303
+ value : config . metadata . id ,
304
+ hint : config . metadata . website ?. documentation
305
+ } ;
257
306
} )
258
307
. filter ( ( c ) => ! ! c ) ;
259
308
260
309
if ( categoryOptions . length > 0 ) {
261
- adderOptions [ name ] = categoryOptions ;
310
+ adderOptions [ category ] = categoryOptions ;
262
311
}
263
312
}
264
313
@@ -526,12 +575,9 @@ async function processExternalAdder<Args extends OptionDefinition>(
526
575
if ( ! TESTING ) p . log . message ( `Executing external command ${ pc . gray ( `(${ config . metadata . id } )` ) } ` ) ;
527
576
528
577
try {
578
+ const env = { ...process . env , ...config . environment } ;
529
579
await exec ( 'npx' , config . command . split ( ' ' ) , {
530
- nodeOptions : {
531
- cwd,
532
- env : Object . assign ( process . env , config . environment ?? { } ) ,
533
- stdio : TESTING ? 'pipe' : 'inherit'
534
- }
580
+ nodeOptions : { cwd, env, stdio : TESTING ? 'pipe' : 'inherit' }
535
581
} ) ;
536
582
} catch ( error ) {
537
583
const typedError = error as Error ;
@@ -622,3 +668,8 @@ function getOptionChoices(details: AdderWithoutExplicitArgs) {
622
668
}
623
669
return { choices, defaults, groups } ;
624
670
}
671
+
672
+ function getPadding ( lines : string [ ] ) {
673
+ const lengths = lines . map ( ( s ) => s . length ) ;
674
+ return Math . max ( ...lengths ) ;
675
+ }
0 commit comments