@lvce-editor/extension-detail-view 4.6.0 → 4.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -30,6 +30,7 @@ const Id = 'ID';
30
30
  const Identifier = 'Identifier';
31
31
  const ImportTime = 'Import Time: ';
32
32
  const Installation = 'Installation';
33
+ const InvalidLink = 'Invalid link';
33
34
  const Issues = 'Issues';
34
35
  const JsonValidation$1 = 'Json Validation';
35
36
  const Label = 'Label';
@@ -42,12 +43,14 @@ const NoReadmeFound = 'No Readme Found.';
42
43
  const OpenImageInNewTab = 'Open Image in New Tab';
43
44
  const OpenInNewTab = 'Open in New Tab';
44
45
  const ProgrammingLanguages$1 = 'Programming Languages';
46
+ const PropertyMustBeOfTypeString = 'Property must be a string';
45
47
  const Published = 'Published';
46
48
  const Repository = 'Repository';
47
49
  const Resources$1 = 'Resources';
48
50
  const RuntimeStatus$1 = 'Runtime Status';
49
51
  const SaveImageAs = 'Save Image as';
50
52
  const Schema = 'Schema';
53
+ const SchemaNotFound = 'Schema not found';
51
54
  const ScrollToTop$1 = 'Scroll to top';
52
55
  const SelectedFeatureUnknownOrUnsupported = 'Selected feature is unknown or unsupported';
53
56
  const Selector = 'Selector';
@@ -207,6 +210,15 @@ const repository = () => {
207
210
  const license = () => {
208
211
  return i18nString(License);
209
212
  };
213
+ const propertyMustBeOfTypeString = () => {
214
+ return i18nString(PropertyMustBeOfTypeString);
215
+ };
216
+ const schemaNotFound = () => {
217
+ return i18nString(SchemaNotFound);
218
+ };
219
+ const invalidLink = () => {
220
+ return i18nString(InvalidLink);
221
+ };
210
222
 
211
223
  const getActivationEventsDetails = async extension => {
212
224
  const activationEvents = extension.activation || [];
@@ -441,16 +453,19 @@ const getCellCodeVirtualDom = (value, props) => {
441
453
  }, text(value)];
442
454
  };
443
455
 
444
- const getCellLinkVirtualDom = (value, {
445
- href
446
- }) => {
456
+ const getCellLinkVirtualDom = (value, props) => {
457
+ const tdClassName = props?.className ? `${TableCell} ${props.className}` : TableCell;
447
458
  return [{
448
459
  type: Td,
449
- className: TableCell,
450
- childCount: 1
460
+ className: tdClassName,
461
+ childCount: 1,
462
+ ...(props?.title ? {
463
+ title: props.title
464
+ } : {})
451
465
  }, {
452
466
  type: A,
453
- href,
467
+ className: Link$1,
468
+ href: props?.href,
454
469
  childCount: 1
455
470
  }, text(value)];
456
471
  };
@@ -536,94 +551,187 @@ const getCommandsVirtualDom = state => {
536
551
  return getFeatureCommandsVirtualDom(state.commands);
537
552
  };
538
553
 
539
- const stringifyValue = value => {
540
- return JSON.stringify(value);
554
+ const isExternalLink = schema => {
555
+ return schema.startsWith('http://') || schema.startsWith('https://');
541
556
  };
542
- const getLinkOrTextEntry = (schema, schemaLinkUrl) => {
543
- if (typeof schema !== 'string') {
544
- return {
545
- type: Text,
546
- value: stringifyValue(schema)
547
- };
548
- }
549
- if (schemaLinkUrl) {
550
- return {
551
- type: Link,
552
- value: schema,
553
- href: schemaLinkUrl
554
- };
557
+ const hasWhitespace = value => {
558
+ return /\s/.test(value);
559
+ };
560
+ const isOnlyDotsOrEmpty = value => {
561
+ const trimmed = value.trim();
562
+ return trimmed === '' || /^\.*$/.test(trimmed);
563
+ };
564
+ const isValidHttpUrl = value => {
565
+ try {
566
+ const url = new URL(value);
567
+ return (url.protocol === 'http:' || url.protocol === 'https:') && !!url.hostname;
568
+ } catch {
569
+ return false;
555
570
  }
556
- return {
557
- type: Text,
558
- value: schema
559
- };
560
571
  };
561
-
562
- const isExternalLink = schema => {
563
- return schema.startsWith('http://') || schema.startsWith('https://');
572
+ const isValidRelativePath = value => {
573
+ // Disallow schemes and whitespace
574
+ if (hasWhitespace(value)) {
575
+ return false;
576
+ }
577
+ if (value.includes('://')) {
578
+ return false;
579
+ }
580
+ if (isOnlyDotsOrEmpty(value)) {
581
+ return false;
582
+ }
583
+ // Allow paths like ./a.json, ../a.json, /a.json, schemas/a.json, a/b.json
584
+ if (!/^[./]?[A-Za-z0-9._\-/]+$/.test(value)) {
585
+ return false;
586
+ }
587
+ // Must contain at least one alphanumeric character
588
+ if (!/[A-Za-z0-9]/.test(value)) {
589
+ return false;
590
+ }
591
+ return true;
564
592
  };
565
593
  const getSchemaLinkUrl = (schema, extensionUri) => {
566
- if (!schema) {
594
+ if (!schema || typeof schema !== 'string') {
567
595
  return '';
568
596
  }
569
- if (typeof schema !== 'string') {
597
+ const trimmed = schema.trim();
598
+ if (trimmed !== schema) {
570
599
  return '';
571
600
  }
572
601
  if (isExternalLink(schema)) {
573
- return schema;
602
+ return isValidHttpUrl(schema) ? schema : '';
603
+ }
604
+ if (!isValidRelativePath(schema)) {
605
+ return '';
606
+ }
607
+ try {
608
+ return new URL(schema, extensionUri).toString();
609
+ } catch {
610
+ return '';
574
611
  }
575
- return new URL(schema, extensionUri).toString();
576
612
  };
577
613
 
578
- const stringify = value => {
614
+ const existsJson = async schemaUrl => {
579
615
  try {
580
- return JSON.stringify(value);
616
+ // TODO verify that response header is json
617
+ const response = await fetch(schemaUrl, {
618
+ method: 'HEAD'
619
+ });
620
+ return response.ok;
581
621
  } catch {
582
- return String(value);
622
+ return false;
583
623
  }
584
624
  };
585
- const getJsonValidationTableEntry = (validation, extensionUri) => {
586
- const invalidProps = {
587
- className: TableCellInvalid,
588
- title: 'property must be a string'
589
- };
590
- if (!validation || typeof validation !== 'object' || Array.isArray(validation)) {
591
- const shown = stringify(validation);
625
+ const getJsonValidationInfos = async (extensionUri, validations) => {
626
+ const validationInfos = [];
627
+ for (const validation of validations) {
628
+ const schema = validation.schema ?? validation.url;
629
+ const schemaLinkUrl = getSchemaLinkUrl(schema, extensionUri);
630
+ const {
631
+ fileMatch
632
+ } = validation;
633
+ if (typeof schema !== 'string') {
634
+ validationInfos.push({
635
+ isValid: false,
636
+ stringValue: JSON.stringify(schema),
637
+ schemaUrl: '',
638
+ errorMessage: propertyMustBeOfTypeString(),
639
+ fileMatch
640
+ });
641
+ } else if (schema && !schemaLinkUrl) {
642
+ validationInfos.push({
643
+ isValid: false,
644
+ stringValue: schema,
645
+ schemaUrl: schemaLinkUrl,
646
+ errorMessage: invalidLink(),
647
+ fileMatch
648
+ });
649
+ } else if (schemaLinkUrl) {
650
+ if (await existsJson(schemaLinkUrl)) {
651
+ validationInfos.push({
652
+ isValid: true,
653
+ stringValue: schema,
654
+ schemaUrl: schemaLinkUrl,
655
+ errorMessage: '',
656
+ fileMatch
657
+ });
658
+ } else {
659
+ validationInfos.push({
660
+ isValid: false,
661
+ stringValue: schema,
662
+ schemaUrl: schemaLinkUrl,
663
+ errorMessage: schemaNotFound(),
664
+ fileMatch
665
+ });
666
+ }
667
+ } else {
668
+ validationInfos.push({
669
+ isValid: true,
670
+ stringValue: schema,
671
+ schemaUrl: schemaLinkUrl,
672
+ errorMessage: '',
673
+ fileMatch
674
+ });
675
+ }
676
+ }
677
+ return validationInfos;
678
+ };
679
+
680
+ const getJsonValidationTableEntry = validationInfo => {
681
+ const {
682
+ isValid,
683
+ errorMessage,
684
+ schemaUrl,
685
+ stringValue,
686
+ fileMatch
687
+ } = validationInfo;
688
+ if (!isValid && schemaUrl) {
689
+ return [{
690
+ type: Code,
691
+ value: fileMatch
692
+ }, {
693
+ type: Link,
694
+ value: stringValue,
695
+ href: schemaUrl,
696
+ className: TableCellInvalid,
697
+ title: errorMessage
698
+ }];
699
+ }
700
+ if (!isValid) {
592
701
  return [{
593
702
  type: Text,
594
- value: shown,
595
- ...invalidProps
703
+ value: fileMatch
596
704
  }, {
597
705
  type: Text,
598
- value: shown,
599
- ...invalidProps
706
+ value: stringValue,
707
+ className: TableCellInvalid,
708
+ title: errorMessage
600
709
  }];
601
710
  }
602
- const {
603
- fileMatch
604
- } = validation;
605
- const schema = validation.schema ?? validation.url;
606
- const schemaLinkUrl = getSchemaLinkUrl(schema, extensionUri);
607
- const leftCell = typeof fileMatch === 'string' || Array.isArray(fileMatch) ? {
711
+ if (schemaUrl) {
712
+ return [{
713
+ type: Code,
714
+ value: fileMatch
715
+ }, {
716
+ type: Link,
717
+ value: stringValue,
718
+ href: schemaUrl
719
+ }];
720
+ }
721
+ return [{
608
722
  type: Code,
609
723
  value: fileMatch
610
- } : {
611
- type: Text,
612
- value: stringify(fileMatch),
613
- ...invalidProps
614
- };
615
- const rightEntry = typeof schema === 'string' ? getLinkOrTextEntry(schema, schemaLinkUrl) : {
724
+ }, {
616
725
  type: Text,
617
- value: stringify(schema),
618
- ...invalidProps
619
- };
620
- return [leftCell, rightEntry];
726
+ value: stringValue
727
+ }];
621
728
  };
622
729
 
623
730
  const getJsonValidationDetails = async extension => {
624
731
  const validations = extension.jsonValidation || [];
625
732
  const extensionUri = extension.uri || '';
626
- const rows = validations.map(validation => getJsonValidationTableEntry(validation, extensionUri));
733
+ const validationInfos = await getJsonValidationInfos(extensionUri, validations);
734
+ const rows = validationInfos.map(getJsonValidationTableEntry);
627
735
  return {
628
736
  jsonValidation: rows
629
737
  };
@@ -835,7 +943,7 @@ class AssertionError extends Error {
835
943
  const Object$1 = 1;
836
944
  const Number$1 = 2;
837
945
  const Array$1 = 3;
838
- const String$1 = 4;
946
+ const String = 4;
839
947
  const Boolean$1 = 5;
840
948
  const Function = 6;
841
949
  const Null = 7;
@@ -847,7 +955,7 @@ const getType = value => {
847
955
  case 'function':
848
956
  return Function;
849
957
  case 'string':
850
- return String$1;
958
+ return String;
851
959
  case 'object':
852
960
  if (value === null) {
853
961
  return Null;
@@ -864,7 +972,7 @@ const getType = value => {
864
972
  };
865
973
  const string = value => {
866
974
  const type = getType(value);
867
- if (type !== String$1) {
975
+ if (type !== String) {
868
976
  throw new AssertionError('expected value to be of type string');
869
977
  }
870
978
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/extension-detail-view",
3
- "version": "4.6.0",
3
+ "version": "4.7.0",
4
4
  "description": "Extension Detail View Worker",
5
5
  "repository": {
6
6
  "type": "git",