@commercetools-frontend/application-config 22.7.0 → 22.8.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.
@@ -1,6 +1,3 @@
1
- import _defineProperty from '@babel/runtime-corejs3/helpers/esm/defineProperty';
2
- import _URL from '@babel/runtime-corejs3/core-js-stable/url';
3
- import _concatInstanceProperty from '@babel/runtime-corejs3/core-js-stable/instance/concat';
4
1
  import _Object$keys from '@babel/runtime-corejs3/core-js-stable/object/keys';
5
2
  import _Object$getOwnPropertySymbols from '@babel/runtime-corejs3/core-js-stable/object/get-own-property-symbols';
6
3
  import _filterInstanceProperty from '@babel/runtime-corejs3/core-js-stable/instance/filter';
@@ -9,10 +6,15 @@ import _forEachInstanceProperty from '@babel/runtime-corejs3/core-js-stable/inst
9
6
  import _Object$getOwnPropertyDescriptors from '@babel/runtime-corejs3/core-js-stable/object/get-own-property-descriptors';
10
7
  import _Object$defineProperties from '@babel/runtime-corejs3/core-js-stable/object/define-properties';
11
8
  import _Object$defineProperty from '@babel/runtime-corejs3/core-js-stable/object/define-property';
12
- import fs from 'fs';
9
+ import _defineProperty from '@babel/runtime-corejs3/helpers/esm/defineProperty';
10
+ import _includesInstanceProperty from '@babel/runtime-corejs3/core-js-stable/instance/includes';
11
+ import _concatInstanceProperty from '@babel/runtime-corejs3/core-js-stable/instance/concat';
12
+ import _URL from '@babel/runtime-corejs3/core-js-stable/url';
13
+ import fs from 'node:fs';
14
+ import path, { parse } from 'node:path';
13
15
  import omitEmpty from 'omit-empty-es';
14
- import { execFileSync } from 'child_process';
15
- import path from 'path';
16
+ import { CUSTOM_VIEW_HOST_ENTRY_POINT_URI_PATH } from '@commercetools-frontend/constants';
17
+ import { execFileSync } from 'node:child_process';
16
18
  import { cosmiconfigSync, defaultLoaders } from 'cosmiconfig';
17
19
  import _Reflect$construct from '@babel/runtime-corejs3/core-js-stable/reflect/construct';
18
20
  import _createClass from '@babel/runtime-corejs3/helpers/esm/createClass';
@@ -28,18 +30,48 @@ import _JSON$stringify from '@babel/runtime-corejs3/core-js-stable/json/stringif
28
30
  import _startsWithInstanceProperty from '@babel/runtime-corejs3/core-js-stable/instance/starts-with';
29
31
  import _reduceInstanceProperty from '@babel/runtime-corejs3/core-js-stable/instance/reduce';
30
32
  import _mapInstanceProperty from '@babel/runtime-corejs3/core-js-stable/instance/map';
31
- import { f as formatEntryPointUriPathToResourceAccessKey, e as entryPointUriPathToResourceAccesses } from './formatters-3609951b.esm.js';
33
+ import { f as formatEntryPointUriPathToResourceAccessKey, e as entryPointUriPathToResourceAccesses } from './formatters-ff577549.esm.js';
32
34
  import _Set from '@babel/runtime-corejs3/core-js-stable/set';
33
35
  import _Array$isArray from '@babel/runtime-corejs3/core-js-stable/array/is-array';
34
36
  import Ajv from 'ajv';
35
37
  import _Object$values from '@babel/runtime-corejs3/core-js-stable/object/values';
36
- import _includesInstanceProperty from '@babel/runtime-corejs3/core-js-stable/instance/includes';
37
38
  import uniq from 'lodash/uniq';
38
39
  import createDOMPurify from 'dompurify';
39
40
  import { JSDOM } from 'jsdom';
40
41
  import '@babel/runtime-corejs3/core-js-stable/object/entries';
41
42
  import 'lodash/upperFirst';
42
43
 
44
+ /**
45
+ * The entryPointUriPath may be between 2 and 64 characters and only contain alphabetic lowercase characters,
46
+ * non-consecutive underscores and hyphens. Leading and trailing underscore and hyphens are also not allowed.
47
+ */
48
+ const ENTRY_POINT_URI_PATH_REGEX = /^[^-_#]([0-9a-z]|[-_](?![-_])){0,62}[^-_#]$/g;
49
+
50
+ /**
51
+ * The permission group name may be between 2 and 64 characters and only contain alphanumeric lowercase characters and non-consecutive hyphens. Leading and trailing hyphens are also not allowed.
52
+ */
53
+ const PERMISSION_GROUP_NAME_REGEX = /^[^-#]([a-z]|[-](?![-])){0,62}[^-#]$/g;
54
+ const CLOUD_IDENTIFIERS = {
55
+ GCP_AU: 'gcp-au',
56
+ GCP_EU: 'gcp-eu',
57
+ GCP_US: 'gcp-us',
58
+ AWS_FRA: 'aws-fra',
59
+ AWS_OHIO: 'aws-ohio',
60
+ AWS_CN: 'aws-cn'
61
+ };
62
+ const MC_API_URLS = {
63
+ GCP_AU: 'https://mc-api.australia-southeast1.gcp.commercetools.com',
64
+ GCP_EU: 'https://mc-api.europe-west1.gcp.commercetools.com',
65
+ GCP_US: 'https://mc-api.us-central1.gcp.commercetools.com',
66
+ AWS_FRA: 'https://mc-api.eu-central-1.aws.commercetools.com',
67
+ AWS_OHIO: 'https://mc-api.us-east-2.aws.commercetools.com',
68
+ AWS_CN: 'https://mc-api.cn-northwest-1.aws.commercetools.cn'
69
+ };
70
+ const LOADED_CONFIG_TYPES = {
71
+ CUSTOM_APPLICATION: 'custom-application',
72
+ CUSTOM_VIEW: 'custom-view'
73
+ };
74
+
43
75
  function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = _Reflect$construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }
44
76
  function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !_Reflect$construct) return false; if (_Reflect$construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(_Reflect$construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
45
77
  let MissingOrInvalidConfigError = /*#__PURE__*/function (_Error) {
@@ -82,31 +114,39 @@ const loadJsModule = filePath => {
82
114
  });
83
115
  return JSON.parse(output);
84
116
  };
85
- const moduleName = 'custom-application-config';
86
- const explorer = cosmiconfigSync(moduleName, {
87
- // Restrict the supported file formats / names
88
- searchPlaces: [".".concat(moduleName, "rc"), ".".concat(moduleName, ".json"), ".".concat(moduleName, ".js"), ".".concat(moduleName, ".cjs"), ".".concat(moduleName, ".mjs"), ".".concat(moduleName, ".ts"), "".concat(moduleName, ".json"), "".concat(moduleName, ".js"), "".concat(moduleName, ".cjs"), "".concat(moduleName, ".mjs"), "".concat(moduleName, ".ts")],
89
- loaders: {
90
- noExt: defaultLoaders['.json'],
91
- '.js': loadJsModule,
92
- '.cjs': loadJsModule,
93
- '.mjs': loadJsModule,
94
- '.ts': loadJsModule
95
- }
96
- });
117
+ const createExplorerFor = configFileName => {
118
+ return cosmiconfigSync(configFileName, {
119
+ // Restrict the supported file formats / names
120
+ searchPlaces: [".".concat(configFileName, "rc"), ".".concat(configFileName, ".json"), ".".concat(configFileName, ".js"), ".".concat(configFileName, ".cjs"), ".".concat(configFileName, ".mjs"), ".".concat(configFileName, ".ts"), "".concat(configFileName, ".json"), "".concat(configFileName, ".js"), "".concat(configFileName, ".cjs"), "".concat(configFileName, ".mjs"), "".concat(configFileName, ".ts")],
121
+ loaders: {
122
+ noExt: defaultLoaders['.json'],
123
+ '.js': loadJsModule,
124
+ '.cjs': loadJsModule,
125
+ '.mjs': loadJsModule,
126
+ '.ts': loadJsModule
127
+ }
128
+ });
129
+ };
130
+ const customApplicationExplorer = createExplorerFor('custom-application-config');
131
+ const customViewExplorer = createExplorerFor('custom-view-config');
97
132
  const getConfigPath = () => {
98
- const configFile = explorer.search();
99
- if (!configFile) {
100
- throw new Error("Missing or invalid Custom Application configuration file.");
133
+ const customApplicationConfigFile = customApplicationExplorer.search();
134
+ const customViewConfigFile = customViewExplorer.search();
135
+ if (!customApplicationConfigFile && !customViewConfigFile) {
136
+ throw new Error("Missing or invalid configuration file.");
101
137
  }
102
- return configFile.filepath;
138
+ return (customApplicationConfigFile === null || customApplicationConfigFile === void 0 ? void 0 : customApplicationConfigFile.filepath) || (customViewConfigFile === null || customViewConfigFile === void 0 ? void 0 : customViewConfigFile.filepath);
103
139
  };
104
140
  const loadConfig = applicationPath => {
105
- const configFile = explorer.search(applicationPath);
106
- if (!configFile || !configFile.config) {
107
- throw new MissingOrInvalidConfigError("Missing or invalid Custom Application configuration file.");
141
+ const customApplicationConfigFile = customApplicationExplorer.search(applicationPath);
142
+ const customViewConfigFile = customViewExplorer.search(applicationPath);
143
+ if ((!customApplicationConfigFile || !customApplicationConfigFile.config) && (!customViewConfigFile || !customViewConfigFile.config)) {
144
+ throw new MissingOrInvalidConfigError("Missing or invalid configuration file.");
145
+ }
146
+ if (customApplicationConfigFile && customViewConfigFile) {
147
+ throw new MissingOrInvalidConfigError("Found configuration files for both Custom Application and Custom View. Please remove one of them.");
108
148
  }
109
- return configFile.config;
149
+ return customViewConfigFile || customApplicationConfigFile;
110
150
  };
111
151
 
112
152
  // Transifex's Structured JSON format.
@@ -199,7 +239,7 @@ const substituteVariablePlaceholders = (config, loadingOptions) => JSON.parse(_J
199
239
  return substitutedValue;
200
240
  });
201
241
 
202
- var schemaJson = {
242
+ var customApplicationSchemaJson = {
203
243
  $schema: "http://json-schema.org/draft-07/schema",
204
244
  $id: "https://docs.commercetools.com/custom-applications/schema.json",
205
245
  title: "JSON schema for Custom Application configuration files",
@@ -552,38 +592,300 @@ var schemaJson = {
552
592
  ]
553
593
  };
554
594
 
555
- /**
556
- * The entryPointUriPath may be between 2 and 64 characters and only contain alphabetic lowercase characters,
557
- * non-consecutive underscores and hyphens. Leading and trailing underscore and hyphens are also not allowed.
558
- */
559
- const ENTRY_POINT_URI_PATH_REGEX = /^[^-_#]([0-9a-z]|[-_](?![-_])){0,62}[^-_#]$/g;
560
-
561
- /**
562
- * The permission group name may be between 2 and 64 characters and only contain alphanumeric lowercase characters and non-consecutive hyphens. Leading and trailing hyphens are also not allowed.
563
- */
564
- const PERMISSION_GROUP_NAME_REGEX = /^[^-#]([a-z]|[-](?![-])){0,62}[^-#]$/g;
565
- const CLOUD_IDENTIFIERS = {
566
- GCP_AU: 'gcp-au',
567
- GCP_EU: 'gcp-eu',
568
- GCP_US: 'gcp-us',
569
- AWS_FRA: 'aws-fra',
570
- AWS_OHIO: 'aws-ohio',
571
- AWS_CN: 'aws-cn'
572
- };
573
- const MC_API_URLS = {
574
- GCP_AU: 'https://mc-api.australia-southeast1.gcp.commercetools.com',
575
- GCP_EU: 'https://mc-api.europe-west1.gcp.commercetools.com',
576
- GCP_US: 'https://mc-api.us-central1.gcp.commercetools.com',
577
- AWS_FRA: 'https://mc-api.eu-central-1.aws.commercetools.com',
578
- AWS_OHIO: 'https://mc-api.us-east-2.aws.commercetools.com',
579
- AWS_CN: 'https://mc-api.cn-northwest-1.aws.commercetools.cn'
595
+ var customViewSchemaJson = {
596
+ $schema: "http://json-schema.org/draft-07/schema",
597
+ $id: "https://docs.commercetools.com/custom-applications/custom-view.schema.json",
598
+ title: "JSON schema for Custom View configuration files",
599
+ type: "object",
600
+ definitions: {
601
+ cspDirective: {
602
+ type: "array",
603
+ items: {
604
+ type: "string"
605
+ },
606
+ uniqueItems: true
607
+ }
608
+ },
609
+ properties: {
610
+ name: {
611
+ description: "See https://docs.commercetools.com/TODO",
612
+ type: "string"
613
+ },
614
+ description: {
615
+ description: "See https://docs.commercetools.com/TODO",
616
+ type: "string"
617
+ },
618
+ cloudIdentifier: {
619
+ description: "See https://docs.commercetools.com/TODO",
620
+ type: "string"
621
+ },
622
+ mcApiUrl: {
623
+ description: "See https://docs.commercetools.com/TODO",
624
+ type: "string"
625
+ },
626
+ oAuthScopes: {
627
+ description: "See https://docs.commercetools.com/TODO",
628
+ type: "object",
629
+ properties: {
630
+ view: {
631
+ description: "See https://docs.commercetools.com/TODO",
632
+ type: "array",
633
+ "default": [
634
+ ],
635
+ items: {
636
+ type: "string",
637
+ pattern: "view_(.*)"
638
+ },
639
+ uniqueItems: true
640
+ },
641
+ manage: {
642
+ description: "See https://docs.commercetools.com/TODO",
643
+ type: "array",
644
+ "default": [
645
+ ],
646
+ items: {
647
+ type: "string",
648
+ pattern: "manage_(.*)"
649
+ },
650
+ uniqueItems: true
651
+ }
652
+ },
653
+ additionalProperties: false,
654
+ required: [
655
+ "view",
656
+ "manage"
657
+ ]
658
+ },
659
+ additionalOAuthScopes: {
660
+ description: "See https://docs.commercetools.com/TODO",
661
+ type: "array",
662
+ "default": [
663
+ ],
664
+ uniqueItems: true,
665
+ items: {
666
+ type: "object",
667
+ properties: {
668
+ name: {
669
+ description: "See https://docs.commercetools.com/TODO",
670
+ type: "string"
671
+ },
672
+ view: {
673
+ description: "See https://docs.commercetools.com/TODO",
674
+ type: "array",
675
+ "default": [
676
+ ],
677
+ items: {
678
+ type: "string",
679
+ pattern: "view_(.*)"
680
+ },
681
+ uniqueItems: true
682
+ },
683
+ manage: {
684
+ description: "See https://docs.commercetools.com/TODO",
685
+ type: "array",
686
+ "default": [
687
+ ],
688
+ items: {
689
+ type: "string",
690
+ pattern: "manage_(.*)"
691
+ },
692
+ uniqueItems: true
693
+ }
694
+ },
695
+ additionalProperties: false,
696
+ required: [
697
+ "name",
698
+ "view",
699
+ "manage"
700
+ ]
701
+ }
702
+ },
703
+ env: {
704
+ description: "See https://docs.commercetools.com/TODO",
705
+ type: "object",
706
+ properties: {
707
+ development: {
708
+ type: "object",
709
+ properties: {
710
+ initialProjectKey: {
711
+ description: "See https://docs.commercetools.com/TODO",
712
+ type: "string"
713
+ },
714
+ teamId: {
715
+ type: "string"
716
+ },
717
+ hostUriPath: {
718
+ description: "See https://docs.commercetools.com/TODO",
719
+ type: "string"
720
+ }
721
+ },
722
+ additionalProperties: false,
723
+ required: [
724
+ "initialProjectKey"
725
+ ]
726
+ },
727
+ production: {
728
+ type: "object",
729
+ properties: {
730
+ customViewId: {
731
+ description: "See https://docs.commercetools.com/TODO",
732
+ type: "string"
733
+ },
734
+ url: {
735
+ description: "See https://docs.commercetools.com/TODO",
736
+ type: "string"
737
+ },
738
+ cdnUrl: {
739
+ description: "See https://docs.commercetools.com/TODO",
740
+ type: "string"
741
+ }
742
+ },
743
+ additionalProperties: false,
744
+ required: [
745
+ "customViewId",
746
+ "url"
747
+ ]
748
+ }
749
+ },
750
+ additionalProperties: false,
751
+ required: [
752
+ "development",
753
+ "production"
754
+ ]
755
+ },
756
+ additionalEnv: {
757
+ description: "See https://docs.commercetools.com/TODO",
758
+ type: "object"
759
+ },
760
+ headers: {
761
+ description: "See https://docs.commercetools.com/TODO",
762
+ type: "object",
763
+ properties: {
764
+ csp: {
765
+ description: "See https://docs.commercetools.com/TODO",
766
+ type: "object",
767
+ properties: {
768
+ "connect-src": {
769
+ $ref: "#/definitions/cspDirective"
770
+ },
771
+ "font-src": {
772
+ $ref: "#/definitions/cspDirective"
773
+ },
774
+ "img-src": {
775
+ $ref: "#/definitions/cspDirective"
776
+ },
777
+ "script-src": {
778
+ $ref: "#/definitions/cspDirective"
779
+ },
780
+ "style-src": {
781
+ $ref: "#/definitions/cspDirective"
782
+ },
783
+ "frame-src": {
784
+ $ref: "#/definitions/cspDirective"
785
+ }
786
+ },
787
+ additionalProperties: false,
788
+ required: [
789
+ "connect-src"
790
+ ]
791
+ },
792
+ permissionsPolicies: {
793
+ description: "See https://docs.commercetools.com/TODO",
794
+ type: "object"
795
+ },
796
+ strictTransportSecurity: {
797
+ description: "See https://docs.commercetools.com/TODO",
798
+ type: "array",
799
+ items: {
800
+ "enum": [
801
+ "includeSubDomains",
802
+ "preload"
803
+ ]
804
+ },
805
+ uniqueItems: true
806
+ }
807
+ },
808
+ additionalProperties: false
809
+ },
810
+ labelAllLocales: {
811
+ description: "See https://docs.commercetools.com/TODO",
812
+ type: "array",
813
+ "default": [
814
+ ],
815
+ items: {
816
+ type: "object",
817
+ properties: {
818
+ locale: {
819
+ type: "string",
820
+ "enum": [
821
+ "en",
822
+ "de",
823
+ "es",
824
+ "fr-FR",
825
+ "pt-BR",
826
+ "zh-CN"
827
+ ]
828
+ },
829
+ value: {
830
+ type: "string"
831
+ }
832
+ },
833
+ additionalProperties: false,
834
+ required: [
835
+ "locale",
836
+ "value"
837
+ ]
838
+ }
839
+ },
840
+ type: {
841
+ description: "See https://docs.commercetools.com/TODO",
842
+ type: "string",
843
+ "enum": [
844
+ "CustomPanel"
845
+ ]
846
+ },
847
+ typeSettings: {
848
+ description: "See https://docs.commercetools.com/TODO",
849
+ type: "object",
850
+ properties: {
851
+ size: {
852
+ description: "See https://docs.commercetools.com/TODO",
853
+ type: "string",
854
+ "enum": [
855
+ "SMALL",
856
+ "LARGE"
857
+ ]
858
+ }
859
+ }
860
+ },
861
+ locators: {
862
+ description: "See https://docs.commercetools.com/TODO",
863
+ type: "array",
864
+ "default": [
865
+ ],
866
+ items: {
867
+ type: "string"
868
+ }
869
+ }
870
+ },
871
+ additionalProperties: true,
872
+ required: [
873
+ "name",
874
+ "cloudIdentifier",
875
+ "env",
876
+ "oAuthScopes",
877
+ "labelAllLocales",
878
+ "type",
879
+ "locators"
880
+ ]
580
881
  };
581
882
 
582
883
  const ajv = new Ajv({
583
884
  strict: true,
584
885
  useDefaults: true
585
886
  });
586
- const validate = ajv.compile(schemaJson);
887
+ const validateCustomApplicationConfig = ajv.compile(customApplicationSchemaJson);
888
+ const validateCustomViewConfig = ajv.compile(customViewSchemaJson);
587
889
  const printErrors = errors => {
588
890
  if (!errors) {
589
891
  return 'No errors';
@@ -601,10 +903,19 @@ const printErrors = errors => {
601
903
  }
602
904
  }).join('\n');
603
905
  };
604
- const validateConfig = config => {
605
- const valid = validate(config);
606
- if (!valid) {
607
- throw new Error(printErrors(validate.errors));
906
+ const validateConfig = (configType, config) => {
907
+ let validation;
908
+ if (configType === LOADED_CONFIG_TYPES.CUSTOM_APPLICATION) {
909
+ validation = validateCustomApplicationConfig;
910
+ } else if (configType === LOADED_CONFIG_TYPES.CUSTOM_VIEW) {
911
+ validation = validateCustomViewConfig;
912
+ } else {
913
+ var _context4;
914
+ throw new Error(_concatInstanceProperty(_context4 = "Invalid config type \"".concat(configType, "\", expected ")).call(_context4, _Object$keys(LOADED_CONFIG_TYPES).toString()));
915
+ }
916
+ const isValid = validation(config);
917
+ if (!isValid) {
918
+ throw new Error(printErrors(validation.errors));
608
919
  }
609
920
  };
610
921
  const validateEntryPointUriPath = config => {
@@ -613,9 +924,9 @@ const validateEntryPointUriPath = config => {
613
924
  }
614
925
  };
615
926
  const validateSubmenuLinks = config => {
616
- var _context4;
927
+ var _context5;
617
928
  const uriPathSet = new _Set();
618
- _forEachInstanceProperty(_context4 = config.submenuLinks).call(_context4, _ref => {
929
+ _forEachInstanceProperty(_context5 = config.submenuLinks).call(_context5, _ref => {
619
930
  let uriPath = _ref.uriPath;
620
931
  if (uriPathSet.has(uriPath)) {
621
932
  throw new Error('Duplicate URI path. Every submenu link must have a unique URI path value');
@@ -626,7 +937,7 @@ const validateSubmenuLinks = config => {
626
937
  const validateAdditionalOAuthScopes = config => {
627
938
  var _config$additionalOAu;
628
939
  const additionalPermissionNames = new _Set();
629
- (_config$additionalOAu = config.additionalOAuthScopes) === null || _config$additionalOAu === void 0 ? void 0 : _forEachInstanceProperty(_config$additionalOAu).call(_config$additionalOAu, _ref2 => {
940
+ (_config$additionalOAu = config.additionalOAuthScopes) === null || _config$additionalOAu === void 0 || _forEachInstanceProperty(_config$additionalOAu).call(_config$additionalOAu, _ref2 => {
630
941
  let name = _ref2.name,
631
942
  view = _ref2.view,
632
943
  manage = _ref2.manage;
@@ -642,8 +953,8 @@ const validateAdditionalOAuthScopes = config => {
642
953
  });
643
954
  };
644
955
 
645
- function ownKeys$1(object, enumerableOnly) { var keys = _Object$keys(object); if (_Object$getOwnPropertySymbols) { var symbols = _Object$getOwnPropertySymbols(object); enumerableOnly && (symbols = _filterInstanceProperty(symbols).call(symbols, function (sym) { return _Object$getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
646
- function _objectSpread$1(target) { for (var i = 1; i < arguments.length; i++) { var _context5, _context6; var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? _forEachInstanceProperty(_context5 = ownKeys$1(Object(source), !0)).call(_context5, function (key) { _defineProperty(target, key, source[key]); }) : _Object$getOwnPropertyDescriptors ? _Object$defineProperties(target, _Object$getOwnPropertyDescriptors(source)) : _forEachInstanceProperty(_context6 = ownKeys$1(Object(source))).call(_context6, function (key) { _Object$defineProperty(target, key, _Object$getOwnPropertyDescriptor(source, key)); }); } return target; }
956
+ function ownKeys$1(e, r) { var t = _Object$keys(e); if (_Object$getOwnPropertySymbols) { var o = _Object$getOwnPropertySymbols(e); r && (o = _filterInstanceProperty(o).call(o, function (r) { return _Object$getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
957
+ function _objectSpread$1(e) { for (var r = 1; r < arguments.length; r++) { var _context5, _context6; var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? _forEachInstanceProperty(_context5 = ownKeys$1(Object(t), !0)).call(_context5, function (r) { _defineProperty(e, r, t[r]); }) : _Object$getOwnPropertyDescriptors ? _Object$defineProperties(e, _Object$getOwnPropertyDescriptors(t)) : _forEachInstanceProperty(_context6 = ownKeys$1(Object(t))).call(_context6, function (r) { _Object$defineProperty(e, r, _Object$getOwnPropertyDescriptor(t, r)); }); } return e; }
647
958
 
648
959
  // The `uriPath` of each submenu link is supposed to be defined relative
649
960
  // to the `entryPointUriPath`. Computing the full path is done internally to keep
@@ -676,7 +987,7 @@ const getPermissions = appConfig => {
676
987
  let name = _ref2.name;
677
988
  return name;
678
989
  })) || [];
679
- const permissionKeys = entryPointUriPathToResourceAccesses(appConfig.entryPointUriPath, additionalPermissionNames);
990
+ const permissionKeys = entryPointUriPathToResourceAccesses(appConfig.entryPointUriPath || appConfig.env.production.customViewId, additionalPermissionNames);
680
991
  const additionalPermissions = _mapInstanceProperty(_context3 = _Object$keys(additionalResourceAccessKeyToOauthScopeMap)).call(_context3, additionalResourceAccessKey => ({
681
992
  name: permissionKeys[additionalResourceAccessKey],
682
993
  oAuthScopes: additionalResourceAccessKeyToOauthScopeMap[additionalResourceAccessKey]
@@ -708,6 +1019,29 @@ function transformCustomApplicationConfigToData(appConfig) {
708
1019
  }))
709
1020
  };
710
1021
  }
1022
+ function transformCustomViewConfigToData(customViewConfig) {
1023
+ validateAdditionalOAuthScopes(customViewConfig);
1024
+ return {
1025
+ id: customViewConfig.env.production.customViewId,
1026
+ defaultLabel: customViewConfig.name,
1027
+ labelAllLocales: customViewConfig.labelAllLocales,
1028
+ description: customViewConfig.description,
1029
+ url: customViewConfig.env.production.url,
1030
+ permissions: getPermissions(customViewConfig),
1031
+ locators: customViewConfig.locators,
1032
+ type: customViewConfig.type,
1033
+ typeSettings: customViewConfig.typeSettings
1034
+ };
1035
+ }
1036
+ function transformConfigurationToData(configType, configuration) {
1037
+ if (configType === LOADED_CONFIG_TYPES.CUSTOM_APPLICATION) {
1038
+ return transformCustomApplicationConfigToData(configuration);
1039
+ } else if (configType === LOADED_CONFIG_TYPES.CUSTOM_VIEW) {
1040
+ return transformCustomViewConfigToData(configuration);
1041
+ } else {
1042
+ throw new Error("Invalid config type: ".concat(configType));
1043
+ }
1044
+ }
711
1045
 
712
1046
  const mapCloudIdentifierToApiUrl = key => {
713
1047
  var _context;
@@ -750,47 +1084,154 @@ const getOrThrow = (fn, errorMessage) => {
750
1084
  }
751
1085
  };
752
1086
 
753
- function ownKeys(object, enumerableOnly) { var keys = _Object$keys(object); if (_Object$getOwnPropertySymbols) { var symbols = _Object$getOwnPropertySymbols(object); enumerableOnly && (symbols = _filterInstanceProperty(symbols).call(symbols, function (sym) { return _Object$getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
754
- function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var _context3, _context4; var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? _forEachInstanceProperty(_context3 = ownKeys(Object(source), !0)).call(_context3, function (key) { _defineProperty(target, key, source[key]); }) : _Object$getOwnPropertyDescriptors ? _Object$defineProperties(target, _Object$getOwnPropertyDescriptors(source)) : _forEachInstanceProperty(_context4 = ownKeys(Object(source))).call(_context4, function (key) { _Object$defineProperty(target, key, _Object$getOwnPropertyDescriptor(source, key)); }); } return target; }
1087
+ function ownKeys(e, r) { var t = _Object$keys(e); if (_Object$getOwnPropertySymbols) { var o = _Object$getOwnPropertySymbols(e); r && (o = _filterInstanceProperty(o).call(o, function (r) { return _Object$getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
1088
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var _context4, _context5; var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? _forEachInstanceProperty(_context4 = ownKeys(Object(t), !0)).call(_context4, function (r) { _defineProperty(e, r, t[r]); }) : _Object$getOwnPropertyDescriptors ? _Object$defineProperties(e, _Object$getOwnPropertyDescriptors(t)) : _forEachInstanceProperty(_context5 = ownKeys(Object(t))).call(_context5, function (r) { _Object$defineProperty(e, r, _Object$getOwnPropertyDescriptor(t, r)); }); } return e; }
755
1089
  // TODO: make it configurable.
756
1090
  const developmentPort = 3001;
757
1091
  const developmentAppUrl = "http://localhost:".concat(developmentPort);
1092
+ const getLoadedConfigurationType = configFileName => {
1093
+ if (_includesInstanceProperty(configFileName).call(configFileName, 'custom-view-config')) {
1094
+ return LOADED_CONFIG_TYPES.CUSTOM_VIEW;
1095
+ }
1096
+ return LOADED_CONFIG_TYPES.CUSTOM_APPLICATION;
1097
+ };
758
1098
  const trimTrailingSlash = value => value.replace(/\/$/, '');
759
1099
  const omitDevConfigIfEmpty = devConfig => {
760
- if (
761
- // @ts-expect-error: the `accountLinks` is not explicitly typed as it's only used by the account app.
762
- devConfig !== null && devConfig !== void 0 && devConfig.accountLinks || devConfig !== null && devConfig !== void 0 && devConfig.menuLinks || devConfig !== null && devConfig !== void 0 && devConfig.oidc) return devConfig;
1100
+ if (devConfig && (Object.hasOwn(devConfig, 'accountLinks') || Object.hasOwn(devConfig, 'menuLinks') || Object.hasOwn(devConfig, 'customViewHostUrl') || Object.hasOwn(devConfig, 'oidc'))) {
1101
+ return devConfig;
1102
+ }
763
1103
  return undefined;
764
1104
  };
1105
+ const isCustomViewData = data => data.entryPointUriPath === undefined;
1106
+ const getRuntimeEnvironmentConfigForDevelopment = _ref => {
1107
+ var _appConfig$env$develo;
1108
+ let isProd = _ref.isProd,
1109
+ configurationData = _ref.configurationData,
1110
+ mcApiUrl = _ref.mcApiUrl,
1111
+ appConfig = _ref.appConfig,
1112
+ entryPointUriPath = _ref.entryPointUriPath;
1113
+ if (isProd) {
1114
+ return undefined;
1115
+ }
1116
+ const oidcConfig = omitEmpty(_objectSpread(_objectSpread({
1117
+ authorizeUrl: [
1118
+ // In case the MC API url points to localhost, we need to point
1119
+ // to a local running dev login page to handle the workflow properly.
1120
+ mcApiUrl.hostname === 'localhost' ? mcApiUrl.origin.replace(mcApiUrl.port, String(developmentPort)) : mcApiUrl.origin.replace('mc-api', 'mc'), '/login/authorize'].join(''),
1121
+ initialProjectKey:
1122
+ // For the `account` application, we should unset the projectKey.
1123
+ entryPointUriPath === 'account' ? undefined : appConfig.env.development.initialProjectKey
1124
+ }, ((_appConfig$env$develo = appConfig.env.development) === null || _appConfig$env$develo === void 0 ? void 0 : _appConfig$env$develo.teamId) && _objectSpread({
1125
+ teamId: appConfig.env.development.teamId
1126
+ }, isCustomViewData(configurationData) ? {
1127
+ customViewId: configurationData.id
1128
+ } : {
1129
+ applicationId: configurationData.id
1130
+ })), {}, {
1131
+ oAuthScopes: appConfig.oAuthScopes,
1132
+ additionalOAuthScopes: appConfig === null || appConfig === void 0 ? void 0 : appConfig.additionalOAuthScopes
1133
+ }));
1134
+ if (isCustomViewData(configurationData)) {
1135
+ var _context;
1136
+ const hostUriPath = appConfig.env.development.hostUriPath;
1137
+ const defaultHostUriPath = oidcConfig.initialProjectKey ? _concatInstanceProperty(_context = "/".concat(oidcConfig.initialProjectKey, "/")).call(_context, entryPointUriPath) : "/".concat(entryPointUriPath);
1138
+ const hostUrl = new _URL(hostUriPath || defaultHostUriPath, developmentAppUrl);
1139
+ return omitDevConfigIfEmpty({
1140
+ oidc: oidcConfig,
1141
+ customViewConfig: configurationData,
1142
+ customViewHostUrl: hostUrl.href
1143
+ });
1144
+ }
1145
+ return omitDevConfigIfEmpty({
1146
+ oidc: oidcConfig,
1147
+ menuLinks: _objectSpread(_objectSpread({
1148
+ icon: configurationData.icon
1149
+ }, configurationData.mainMenuLink), {}, {
1150
+ submenuLinks: configurationData.submenuLinks
1151
+ }),
1152
+ // @ts-expect-error: the `accountLinks` is not explicitly typed as it's only used by the account app.
1153
+ accountLinks: configurationData.accountLinks
1154
+ });
1155
+ };
1156
+ const getRuntimeEnvironmentConfig = _ref2 => {
1157
+ var _context2;
1158
+ let isProd = _ref2.isProd,
1159
+ configurationData = _ref2.configurationData,
1160
+ additionalAppEnv = _ref2.additionalAppEnv,
1161
+ mcApiUrl = _ref2.mcApiUrl,
1162
+ cdnUrl = _ref2.cdnUrl,
1163
+ appUrl = _ref2.appUrl,
1164
+ appEnvKey = _ref2.appEnvKey,
1165
+ revision = _ref2.revision,
1166
+ appConfig = _ref2.appConfig;
1167
+ const entryPointUriPath = isCustomViewData(configurationData) ?
1168
+ // When the application acts as the host for Custom Views, there is no real
1169
+ // entry point to be used, therefore we use a special identifier.
1170
+ CUSTOM_VIEW_HOST_ENTRY_POINT_URI_PATH : configurationData.entryPointUriPath;
1171
+
1172
+ // The real application ID is only used in production.
1173
+ // In development, we prefix the entry point with the "__local" prefix.
1174
+ // This is important to determine to which URL the MC should redirect to
1175
+ // after successful login.
1176
+ const applicationIdentifier = isProd ? _concatInstanceProperty(_context2 = "".concat(configurationData.id, ":")).call(_context2, entryPointUriPath) : "__local:".concat(entryPointUriPath);
1177
+ const developmentConfig = getRuntimeEnvironmentConfigForDevelopment({
1178
+ isProd,
1179
+ configurationData,
1180
+ mcApiUrl,
1181
+ appConfig,
1182
+ entryPointUriPath
1183
+ });
1184
+ return _objectSpread(_objectSpread(_objectSpread({}, omitEmpty(additionalAppEnv)), {}, {
1185
+ cdnUrl: cdnUrl.href,
1186
+ env: appEnvKey,
1187
+ frontendHost: appUrl.host,
1188
+ location: appConfig.cloudIdentifier,
1189
+ mcApiUrl: mcApiUrl.origin,
1190
+ revision,
1191
+ servedByProxy: isProd,
1192
+ // Application config
1193
+ applicationId: applicationIdentifier,
1194
+ applicationIdentifier,
1195
+ applicationName: isCustomViewData(configurationData) ? configurationData.defaultLabel : configurationData.name,
1196
+ entryPointUriPath
1197
+ }, isCustomViewData(configurationData) ? {
1198
+ customViewId: configurationData.id
1199
+ } : {}), developmentConfig ? {
1200
+ __DEVELOPMENT__: developmentConfig
1201
+ } : {});
1202
+ };
765
1203
 
766
1204
  // Keep a reference to the config so that requiring the module
767
1205
  // again will result in returning the cached value.
768
1206
  let cachedConfig;
769
1207
  const processConfig = function () {
770
- var _ref2, _processEnv$MC_APP_EN, _appConfig$additional, _ref3, _context, _appConfig$env$develo, _appConfig$headers, _appConfig$headers2, _appConfig$headers2$c, _context2, _appConfig$headers3, _appConfig$headers3$c, _appConfig$headers4, _appConfig$headers4$c;
771
- let _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
772
- _ref$disableCache = _ref.disableCache,
773
- disableCache = _ref$disableCache === void 0 ? false : _ref$disableCache,
774
- _ref$processEnv = _ref.processEnv,
775
- processEnv = _ref$processEnv === void 0 ? process.env : _ref$processEnv,
776
- _ref$applicationPath = _ref.applicationPath,
777
- applicationPath = _ref$applicationPath === void 0 ? fs.realpathSync(process.cwd()) : _ref$applicationPath;
1208
+ var _ref4, _processEnv$MC_APP_EN, _appConfig$additional, _ref5, _appConfig$headers, _appConfig$headers2, _context3, _appConfig$headers3, _appConfig$headers4;
1209
+ let _ref3 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
1210
+ _ref3$disableCache = _ref3.disableCache,
1211
+ disableCache = _ref3$disableCache === void 0 ? false : _ref3$disableCache,
1212
+ _ref3$processEnv = _ref3.processEnv,
1213
+ processEnv = _ref3$processEnv === void 0 ? process.env : _ref3$processEnv,
1214
+ _ref3$applicationPath = _ref3.applicationPath,
1215
+ applicationPath = _ref3$applicationPath === void 0 ? fs.realpathSync(process.cwd()) : _ref3$applicationPath;
778
1216
  if (cachedConfig && !disableCache) return cachedConfig;
779
- const rawConfig = loadConfig(applicationPath);
780
- validateConfig(rawConfig);
1217
+ const _loadConfig = loadConfig(applicationPath),
1218
+ filepath = _loadConfig.filepath,
1219
+ rawConfig = _loadConfig.config;
1220
+ const configType = getLoadedConfigurationType(parse(filepath).name);
1221
+ validateConfig(configType, rawConfig);
781
1222
  const appConfig = substituteVariablePlaceholders(rawConfig, {
782
1223
  applicationPath,
783
1224
  processEnv
784
1225
  });
785
- const customApplicationData = transformCustomApplicationConfigToData(appConfig);
786
- const appEnvKey = (_ref2 = (_processEnv$MC_APP_EN = processEnv.MC_APP_ENV) !== null && _processEnv$MC_APP_EN !== void 0 ? _processEnv$MC_APP_EN : processEnv.NODE_ENV) !== null && _ref2 !== void 0 ? _ref2 : 'development';
1226
+ const configurationData = transformConfigurationToData(configType, appConfig);
1227
+ const appEnvKey = (_ref4 = (_processEnv$MC_APP_EN = processEnv.MC_APP_ENV) !== null && _processEnv$MC_APP_EN !== void 0 ? _processEnv$MC_APP_EN : processEnv.NODE_ENV) !== null && _ref4 !== void 0 ? _ref4 : 'development';
787
1228
  const isProd = getIsProd(processEnv);
788
1229
  const additionalAppEnv = (_appConfig$additional = appConfig.additionalEnv) !== null && _appConfig$additional !== void 0 ? _appConfig$additional : {};
789
- const revision = (_ref3 = additionalAppEnv.revision) !== null && _ref3 !== void 0 ? _ref3 : '';
1230
+ const revision = (_ref5 = additionalAppEnv.revision) !== null && _ref5 !== void 0 ? _ref5 : '';
790
1231
 
791
1232
  // Parse all the supported URLs, which gets implicitly validated
792
1233
 
793
- const envAppUrl = isProd ? customApplicationData.url : developmentAppUrl;
1234
+ const envAppUrl = isProd ? configurationData.url : developmentAppUrl;
794
1235
  const appUrl = getOrThrow(() => new _URL(envAppUrl), "Invalid application URL: \"".concat(envAppUrl, "\""));
795
1236
 
796
1237
  // Use `||` instead of `??` to include empty string values.
@@ -799,52 +1240,18 @@ const processConfig = function () {
799
1240
  const mcApiUrl = getOrThrow(() => new _URL(
800
1241
  // Use `||` instead of `??` to include empty string values.
801
1242
  appConfig.mcApiUrl || mapCloudIdentifierToApiUrl(appConfig.cloudIdentifier)), "Invalid MC API URL: \"".concat(appConfig.mcApiUrl, "\""));
802
-
803
- // The real application ID is only used in production.
804
- // In development, we prefix the entry point with the "__local" prefix.
805
- // This is important to determine to which URL the MC should redirect to
806
- // after successful login.
807
- const applicationId = isProd ? _concatInstanceProperty(_context = "".concat(customApplicationData.id, ":")).call(_context, customApplicationData.entryPointUriPath) : "__local:".concat(customApplicationData.entryPointUriPath);
808
- const developmentConfig = isProd ? undefined : omitDevConfigIfEmpty({
809
- oidc: omitEmpty(_objectSpread(_objectSpread({
810
- authorizeUrl: [
811
- // In case the MC API url points to localhost, we need to point
812
- // to a local running dev login page to handle the workflow properly.
813
- mcApiUrl.hostname === 'localhost' ? mcApiUrl.origin.replace(mcApiUrl.port, String(developmentPort)) : mcApiUrl.origin.replace('mc-api', 'mc'), '/login/authorize'].join(''),
814
- initialProjectKey:
815
- // For the `account` application, we should unset the projectKey.
816
- customApplicationData.entryPointUriPath === 'account' ? undefined : appConfig.env.development.initialProjectKey
817
- }, ((_appConfig$env$develo = appConfig.env.development) === null || _appConfig$env$develo === void 0 ? void 0 : _appConfig$env$develo.teamId) && {
818
- teamId: appConfig.env.development.teamId,
819
- applicationId: appConfig.env.production.applicationId
820
- }), {}, {
821
- oAuthScopes: appConfig.oAuthScopes,
822
- additionalOAuthScopes: appConfig === null || appConfig === void 0 ? void 0 : appConfig.additionalOAuthScopes
823
- })),
824
- menuLinks: _objectSpread(_objectSpread({
825
- icon: customApplicationData.icon
826
- }, customApplicationData.mainMenuLink), {}, {
827
- submenuLinks: customApplicationData.submenuLinks
828
- }),
829
- // @ts-expect-error: the `accountLinks` is not explicitly typed as it's only used by the account app.
830
- accountLinks: appConfig.accountLinks
831
- });
832
1243
  cachedConfig = {
833
- data: customApplicationData,
834
- env: _objectSpread(_objectSpread(_objectSpread({}, omitEmpty(additionalAppEnv)), {}, {
835
- applicationId,
836
- applicationName: customApplicationData.name,
837
- entryPointUriPath: customApplicationData.entryPointUriPath
838
- }, isProd || !developmentConfig ? {} : {
839
- __DEVELOPMENT__: developmentConfig
840
- }), {}, {
841
- cdnUrl: cdnUrl.href,
842
- env: appEnvKey,
843
- frontendHost: appUrl.host,
844
- location: appConfig.cloudIdentifier,
845
- mcApiUrl: mcApiUrl.origin,
846
- revision,
847
- servedByProxy: isProd
1244
+ data: configurationData,
1245
+ env: getRuntimeEnvironmentConfig({
1246
+ isProd,
1247
+ configurationData,
1248
+ additionalAppEnv,
1249
+ appConfig,
1250
+ appEnvKey,
1251
+ appUrl,
1252
+ cdnUrl,
1253
+ mcApiUrl,
1254
+ revision
848
1255
  }),
849
1256
  headers: _objectSpread(_objectSpread({}, appConfig.headers), {}, {
850
1257
  csp: _objectSpread(_objectSpread({}, (_appConfig$headers = appConfig.headers) === null || _appConfig$headers === void 0 ? void 0 : _appConfig$headers.csp), {}, {
@@ -854,9 +1261,9 @@ const processConfig = function () {
854
1261
  // the CSP point of view, it will say only the file `app` can be used as a source, so
855
1262
  // any other file from that domain will be forbidden. Using the slash (ex: https://www.my-domain.com/app/)
856
1263
  // at the end it's like using a wildcard so anything 'below' `app` will be allowed.
857
- 'connect-src': getUniqueValues((_appConfig$headers2 = appConfig.headers) === null || _appConfig$headers2 === void 0 ? void 0 : (_appConfig$headers2$c = _appConfig$headers2.csp) === null || _appConfig$headers2$c === void 0 ? void 0 : _appConfig$headers2$c['connect-src'], _concatInstanceProperty(_context2 = [mcApiUrl.origin]).call(_context2, isProd ? ["".concat(trimTrailingSlash(appUrl.href), "/")] : [])),
858
- 'script-src': getUniqueValues((_appConfig$headers3 = appConfig.headers) === null || _appConfig$headers3 === void 0 ? void 0 : (_appConfig$headers3$c = _appConfig$headers3.csp) === null || _appConfig$headers3$c === void 0 ? void 0 : _appConfig$headers3$c['script-src'], isProd ? ["".concat(trimTrailingSlash(appUrl.href), "/"), "".concat(trimTrailingSlash(cdnUrl.href), "/")] : []),
859
- 'style-src': getUniqueValues((_appConfig$headers4 = appConfig.headers) === null || _appConfig$headers4 === void 0 ? void 0 : (_appConfig$headers4$c = _appConfig$headers4.csp) === null || _appConfig$headers4$c === void 0 ? void 0 : _appConfig$headers4$c['style-src'], isProd ? ["".concat(trimTrailingSlash(appUrl.href), "/"), "".concat(trimTrailingSlash(cdnUrl.href), "/")] : [])
1264
+ 'connect-src': getUniqueValues((_appConfig$headers2 = appConfig.headers) === null || _appConfig$headers2 === void 0 || (_appConfig$headers2 = _appConfig$headers2.csp) === null || _appConfig$headers2 === void 0 ? void 0 : _appConfig$headers2['connect-src'], _concatInstanceProperty(_context3 = [mcApiUrl.origin]).call(_context3, isProd ? ["".concat(trimTrailingSlash(appUrl.href), "/")] : [])),
1265
+ 'script-src': getUniqueValues((_appConfig$headers3 = appConfig.headers) === null || _appConfig$headers3 === void 0 || (_appConfig$headers3 = _appConfig$headers3.csp) === null || _appConfig$headers3 === void 0 ? void 0 : _appConfig$headers3['script-src'], isProd ? ["".concat(trimTrailingSlash(appUrl.href), "/"), "".concat(trimTrailingSlash(cdnUrl.href), "/")] : []),
1266
+ 'style-src': getUniqueValues((_appConfig$headers4 = appConfig.headers) === null || _appConfig$headers4 === void 0 || (_appConfig$headers4 = _appConfig$headers4.csp) === null || _appConfig$headers4 === void 0 ? void 0 : _appConfig$headers4['style-src'], isProd ? ["".concat(trimTrailingSlash(appUrl.href), "/"), "".concat(trimTrailingSlash(cdnUrl.href), "/")] : [])
860
1267
  })
861
1268
  })
862
1269
  };
@@ -878,4 +1285,4 @@ function sanitizeSvg(data) {
878
1285
  }).innerHTML;
879
1286
  }
880
1287
 
881
- export { CLOUD_IDENTIFIERS, ENTRY_POINT_URI_PATH_REGEX, MC_API_URLS, MissingOrInvalidConfigError, PERMISSION_GROUP_NAME_REGEX, getConfigPath, processConfig, sanitizeSvg };
1288
+ export { CLOUD_IDENTIFIERS, ENTRY_POINT_URI_PATH_REGEX, LOADED_CONFIG_TYPES, MC_API_URLS, MissingOrInvalidConfigError, PERMISSION_GROUP_NAME_REGEX, getConfigPath, processConfig, sanitizeSvg };