1
1
import React , { useState , useEffect } from 'react' ;
2
2
import { createRoot } from 'react-dom/client' ;
3
- import ReactMarkdown from 'react-markdown' ;
4
3
import { SemVer } from 'semver' ;
5
-
6
- interface Package {
7
- name : string ;
8
- version : string ;
9
- location : string ;
10
- }
11
-
12
- type ReleaseType =
13
- | 'major'
14
- | 'minor'
15
- | 'patch'
16
- | 'intentionally-skip'
17
- | 'custom'
18
- | string ;
4
+ import { ErrorMessage } from './ErrorMessage.js' ;
5
+ import { PackageItem } from './PackageItem.js' ;
6
+ import { Package , ReleaseType } from './types.js' ;
19
7
20
8
function App ( ) {
21
9
const [ packages , setPackages ] = useState < Package [ ] > ( [ ] ) ;
22
- const [ selections , setSelections ] = useState < Record < string , any > > ( { } ) ;
10
+ const [ selections , setSelections ] = useState < Record < string , string > > ( { } ) ;
23
11
const [ isSubmitting , setIsSubmitting ] = useState ( false ) ;
24
12
const [ error , setError ] = useState < string | null > ( null ) ;
25
13
const [ changelogs , setChangelogs ] = useState < Record < string , string > > ( { } ) ;
@@ -61,7 +49,7 @@ function App() {
61
49
} ) ;
62
50
} , [ ] ) ;
63
51
64
- const checkDependencies = async ( selectionData : Record < string , any > ) => {
52
+ const checkDependencies = async ( selectionData : Record < string , string > ) => {
65
53
if ( Object . keys ( selectionData ) . length === 0 ) return ;
66
54
67
55
try {
@@ -145,6 +133,9 @@ function App() {
145
133
setSelections ( ( prev ) => {
146
134
if ( value === '' ) {
147
135
const { [ packageName ] : _ , ...rest } = prev ;
136
+ const { [ packageName ] : __ , ...remainingErrors } =
137
+ packageDependencyErrors ;
138
+ setPackageDependencyErrors ( remainingErrors ) ;
148
139
return rest ;
149
140
}
150
141
return {
@@ -246,296 +237,20 @@ function App() {
246
237
247
238
< div className = "space-y-4" >
248
239
{ packages . map ( ( pkg ) => (
249
- < div
240
+ < PackageItem
250
241
key = { pkg . name }
251
- id = { `package-${ pkg . name } ` }
252
- className = { `border p-4 rounded-lg ${
253
- selections [ pkg . name ] &&
254
- selections [ pkg . name ] !== 'intentionally-skip'
255
- ? 'border-gray-500'
256
- : 'border-gray-200'
257
- } ${
258
- packageDependencyErrors [ pkg . name ] &&
259
- packageDependencyErrors [ pkg . name ] . missingDependencies . length > 0
260
- ? 'border-red-500'
261
- : ''
262
- } `}
263
- >
264
- < h2 className = "text-xl font-semibold" > { pkg . name } </ h2 >
265
- < div className = "flex items-center justify-between" >
266
- < div >
267
- < p className = "text-gray-600" > Current version: { pkg . version } </ p >
268
- { selections [ pkg . name ] &&
269
- selections [ pkg . name ] !== 'intentionally-skip' &&
270
- selections [ pkg . name ] !== 'custom' &&
271
- ! versionErrors [ pkg . name ] && (
272
- < p className = "text-yellow-700" >
273
- New version:{ ' ' }
274
- { ! [ 'patch' , 'minor' , 'major' ] . includes (
275
- selections [ pkg . name ] ,
276
- )
277
- ? selections [ pkg . name ]
278
- : new SemVer ( pkg . version )
279
- . inc (
280
- selections [ pkg . name ] as Exclude <
281
- ReleaseType ,
282
- 'intentionally-skip' | 'custom' | string
283
- > ,
284
- )
285
- . toString ( ) }
286
- </ p >
287
- ) }
288
- { versionErrors [ pkg . name ] && (
289
- < p className = "text-red-500 text-sm mt-1" >
290
- { versionErrors [ pkg . name ] }
291
- </ p >
292
- ) }
293
- </ div >
294
- < div className = "flex items-center space-x-2" >
295
- < select
296
- value = { selections [ pkg . name ] }
297
- onChange = { ( e ) =>
298
- handleSelectionChange (
299
- pkg . name ,
300
- e . target . value as ReleaseType ,
301
- )
302
- }
303
- className = "border rounded px-2 py-1"
304
- >
305
- < option value = "" > Select version bump</ option >
306
- < option value = "major" > Major</ option >
307
- < option value = "minor" > Minor</ option >
308
- < option value = "patch" > Patch</ option >
309
- < option value = "intentionally-skip" > Skip</ option >
310
- < option value = "custom" > Custom Version</ option >
311
- { selections [ pkg . name ] &&
312
- ! [
313
- 'major' ,
314
- 'minor' ,
315
- 'patch' ,
316
- 'intentionally-skip' ,
317
- 'custom' ,
318
- '' ,
319
- ] . includes ( selections [ pkg . name ] ) && (
320
- < option value = { selections [ pkg . name ] } >
321
- Current: { selections [ pkg . name ] }
322
- </ option >
323
- ) }
324
- </ select >
325
- { selections [ pkg . name ] === 'custom' && (
326
- < input
327
- type = "text"
328
- placeholder = "Enter version (e.g., 1.2.3)"
329
- onChange = { ( e ) =>
330
- handleCustomVersionChange ( pkg . name , e . target . value )
331
- }
332
- className = "border rounded px-2 py-1"
333
- />
334
- ) }
335
- < button
336
- onClick = { ( ) => void fetchChangelog ( pkg . name ) }
337
- disabled = { loadingChangelogs [ pkg . name ] === true }
338
- className = "bg-gray-500 text-white px-3 py-1 rounded hover:bg-gray-600 disabled:bg-gray-400"
339
- >
340
- { loadingChangelogs [ pkg . name ]
341
- ? 'Loading...'
342
- : 'View Changelog' }
343
- </ button >
344
- </ div >
345
- </ div >
346
-
347
- { packageDependencyErrors [ pkg . name ] && (
348
- < div className = "mt-2 p-3 bg-red-50 border border-red-200 rounded" >
349
- < div className = "flex-grow" >
350
- { packageDependencyErrors [ pkg . name ] . missingDependencies
351
- . length > 0 && (
352
- < div className = "text-red-800" >
353
- < div className = "flex justify-between items-center mb-2" >
354
- < p className = "font-semibold" > Missing Dependencies:</ p >
355
- < button
356
- onClick = { ( ) => {
357
- const missingDeps =
358
- packageDependencyErrors [ pkg . name ]
359
- . missingDependencies ;
360
- setSelections ( ( prev ) => ( {
361
- ...prev ,
362
- ...missingDeps . reduce (
363
- ( acc , dep ) => ( {
364
- ...acc ,
365
- [ dep ] : 'intentionally-skip' ,
366
- } ) ,
367
- { } ,
368
- ) ,
369
- } ) ) ;
370
- } }
371
- className = "px-3 py-1 bg-gray-500 text-white text-sm rounded hover:bg-gray-600"
372
- >
373
- Skip All
374
- </ button >
375
- </ div >
376
- < p className = "text-sm mb-2" >
377
- Please either include these packages in your release
378
- selections, or choose "Skip" if you are absolutely sure
379
- they are safe to omit:
380
- </ p >
381
- < ul className = "list-disc ml-4" >
382
- { packageDependencyErrors [
383
- pkg . name
384
- ] . missingDependencies . map ( ( dep ) => (
385
- < li
386
- key = { dep }
387
- className = "flex justify-between items-center mb-2"
388
- >
389
- < span
390
- onClick = { ( ) => {
391
- document
392
- . getElementById ( `package-${ dep } ` )
393
- ?. scrollIntoView ( { behavior : 'smooth' } ) ;
394
- } }
395
- className = "cursor-pointer hover:underline"
396
- >
397
- { dep }
398
- </ span >
399
- < button
400
- onClick = { ( ) =>
401
- setSelections ( ( prev ) => ( {
402
- ...prev ,
403
- [ dep ] : 'intentionally-skip' ,
404
- } ) )
405
- }
406
- className = "ml-4 px-2 py-0.5 text-sm bg-gray-500 text-white rounded hover:bg-gray-600"
407
- >
408
- Skip
409
- </ button >
410
- </ li >
411
- ) ) }
412
- </ ul >
413
- </ div >
414
- ) }
415
- { packageDependencyErrors [ pkg . name ] . missingDependentNames
416
- . length > 0 && (
417
- < div className = "text-red-800 mt-4" >
418
- < div className = "flex justify-between items-center mb-2" >
419
- < p className = "font-semibold" > Missing Dependents:</ p >
420
- < button
421
- onClick = { ( ) => {
422
- const missingDependents =
423
- packageDependencyErrors [ pkg . name ]
424
- . missingDependentNames ;
425
- setSelections ( ( prev ) => ( {
426
- ...prev ,
427
- ...missingDependents . reduce (
428
- ( acc , dep ) => ( {
429
- ...acc ,
430
- [ dep ] : 'intentionally-skip' ,
431
- } ) ,
432
- { } ,
433
- ) ,
434
- } ) ) ;
435
- } }
436
- className = "px-3 py-1 bg-gray-500 text-white text-sm rounded hover:bg-gray-600"
437
- >
438
- Skip All
439
- </ button >
440
- </ div >
441
- < p className = "text-sm mb-2" >
442
- Please either include these packages in your release
443
- selections, or choose "Skip" if you are absolutely sure
444
- they are safe to omit:
445
- </ p >
446
- < ul className = "list-disc ml-4" >
447
- { packageDependencyErrors [
448
- pkg . name
449
- ] . missingDependentNames . map ( ( dep ) => (
450
- < li
451
- key = { dep }
452
- className = "flex justify-between items-center mb-2"
453
- >
454
- < span
455
- onClick = { ( ) => {
456
- document
457
- . getElementById ( `package-${ dep } ` )
458
- ?. scrollIntoView ( { behavior : 'smooth' } ) ;
459
- } }
460
- className = "cursor-pointer hover:underline"
461
- >
462
- { dep }
463
- </ span >
464
- < button
465
- onClick = { ( ) =>
466
- setSelections ( ( prev ) => ( {
467
- ...prev ,
468
- [ dep ] : 'intentionally-skip' ,
469
- } ) )
470
- }
471
- className = "ml-4 px-2 py-0.5 text-sm bg-gray-500 text-white rounded hover:bg-gray-600"
472
- >
473
- Skip
474
- </ button >
475
- </ li >
476
- ) ) }
477
- </ ul >
478
- </ div >
479
- ) }
480
- </ div >
481
- </ div >
482
- ) }
483
-
484
- < div className = "mt-2 space-y-2" >
485
- { changelogs [ pkg . name ] && (
486
- < div className = "mt-4 p-4 bg-gray-50 rounded-lg border" >
487
- < div className = "flex justify-end mb-2" >
488
- < button
489
- onClick = { ( ) =>
490
- setChangelogs ( ( prev ) => ( { ...prev , [ pkg . name ] : '' } ) )
491
- }
492
- className = "text-gray-500 hover:text-gray-700"
493
- >
494
- ✕
495
- </ button >
496
- </ div >
497
- < ReactMarkdown
498
- children = { changelogs [ pkg . name ] }
499
- components = { {
500
- h1 : ( { node, ...props } ) => (
501
- < h1 className = "text-2xl font-bold my-4" { ...props } />
502
- ) ,
503
- h2 : ( { node, ...props } ) => (
504
- < h2 className = "text-xl font-bold my-3" { ...props } />
505
- ) ,
506
- h3 : ( { node, ...props } ) => (
507
- < h3 className = "text-lg font-bold my-2" { ...props } />
508
- ) ,
509
- p : ( { node, ...props } ) => (
510
- < p className = "my-2" { ...props } />
511
- ) ,
512
- ul : ( { node, ...props } ) => (
513
- < ul className = "list-disc ml-4 my-2" { ...props } />
514
- ) ,
515
- ol : ( { node, ...props } ) => (
516
- < ol className = "list-decimal ml-4 my-2" { ...props } />
517
- ) ,
518
- li : ( { node, ...props } ) => (
519
- < li className = "my-1" { ...props } />
520
- ) ,
521
- code : ( { node, ...props } ) => (
522
- < code
523
- className = "bg-gray-100 rounded px-1 py-0.5 text-sm font-mono"
524
- { ...props }
525
- />
526
- ) ,
527
- pre : ( { node, ...props } ) => (
528
- < pre
529
- className = "bg-gray-100 rounded p-2 my-2 overflow-x-auto font-mono text-sm"
530
- { ...props }
531
- />
532
- ) ,
533
- } }
534
- />
535
- </ div >
536
- ) }
537
- </ div >
538
- </ div >
242
+ pkg = { pkg }
243
+ selections = { selections }
244
+ versionErrors = { versionErrors }
245
+ packageDependencyErrors = { packageDependencyErrors }
246
+ loadingChangelogs = { loadingChangelogs }
247
+ changelogs = { changelogs }
248
+ onSelectionChange = { handleSelectionChange }
249
+ onCustomVersionChange = { handleCustomVersionChange }
250
+ onFetchChangelog = { fetchChangelog }
251
+ setSelections = { setSelections }
252
+ setChangelogs = { setChangelogs }
253
+ />
539
254
) ) }
540
255
</ div >
541
256
@@ -561,24 +276,7 @@ function App() {
561
276
562
277
{ error && < div className = "text-red-600 p-4" > Error: { error } </ div > }
563
278
564
- { submitErrors . length > 0 && (
565
- < div className = "mt-4 p-4 bg-red-50 border border-red-200 rounded-lg" >
566
- < h3 className = "text-red-700 font-semibold mb-2" >
567
- Your release spec could not be processed due to the following
568
- issues:
569
- </ h3 >
570
- < ul className = "list-disc pl-5" >
571
- { submitErrors . map ( ( error , index ) => (
572
- < li
573
- key = { index }
574
- className = "text-red-600 whitespace-pre-wrap font-mono mb-2"
575
- >
576
- { error }
577
- </ li >
578
- ) ) }
579
- </ ul >
580
- </ div >
581
- ) }
279
+ { submitErrors . length > 0 && < ErrorMessage errors = { submitErrors } /> }
582
280
</ div >
583
281
) ;
584
282
}
0 commit comments