@voilabs/plugins 0.0.1-beta.0 → 0.0.1-beta.1
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.
- package/README.md +107 -30
- package/dist/client.d.ts +10 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +19 -0
- package/dist/client.js.map +1 -1
- package/dist/http.js +19 -3
- package/dist/http.js.map +1 -1
- package/dist/injection.d.ts +1 -1
- package/dist/injection.d.ts.map +1 -1
- package/dist/injection.js +66 -22
- package/dist/injection.js.map +1 -1
- package/dist/manager.d.ts +12 -13
- package/dist/manager.d.ts.map +1 -1
- package/dist/manager.js +310 -14
- package/dist/manager.js.map +1 -1
- package/dist/react.d.ts +35 -3
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +165 -1
- package/dist/react.js.map +1 -1
- package/dist/types.d.ts +13 -1
- package/dist/types.d.ts.map +1 -1
- package/github-examples/README.md +23 -0
- package/github-examples/google-tag-manager/assets/admin.css +27 -0
- package/github-examples/google-tag-manager/components/dashboard-card.js +10 -0
- package/github-examples/google-tag-manager/components/settings.js +21 -0
- package/github-examples/google-tag-manager/schema.json +139 -0
- package/package.json +2 -1
package/dist/manager.js
CHANGED
|
@@ -63,6 +63,7 @@ export class PluginManager {
|
|
|
63
63
|
autoSyncMarketplaces;
|
|
64
64
|
marketplaceRefreshIntervalMs;
|
|
65
65
|
github;
|
|
66
|
+
remotePluginAssets = new Map();
|
|
66
67
|
constructor(options = {}) {
|
|
67
68
|
this.marketplaces = options.marketplaces ?? [];
|
|
68
69
|
this.redisUrl = options.redisUrl;
|
|
@@ -306,9 +307,70 @@ export class PluginManager {
|
|
|
306
307
|
.map((component) => ({
|
|
307
308
|
...component,
|
|
308
309
|
pluginId: plugin.id,
|
|
310
|
+
pluginName: plugin.name,
|
|
311
|
+
pluginVersion: plugin.version,
|
|
309
312
|
}));
|
|
310
313
|
});
|
|
311
314
|
}
|
|
315
|
+
async listFrontendComponents(options = {}) {
|
|
316
|
+
await this.ready();
|
|
317
|
+
const installedOnly = options.installedOnly ?? true;
|
|
318
|
+
const enabledOnly = options.enabledOnly ?? true;
|
|
319
|
+
const tenantId = this.resolveTenantId(options);
|
|
320
|
+
const components = this.getFrontendComponents(options.slot).filter((component) => !options.pluginId || component.pluginId === options.pluginId);
|
|
321
|
+
if (!installedOnly && !enabledOnly) {
|
|
322
|
+
return components;
|
|
323
|
+
}
|
|
324
|
+
const installations = await this.listInstallations({ tenantId });
|
|
325
|
+
const installationByPluginId = new Map(installations.map((installation) => [installation.pluginId, installation]));
|
|
326
|
+
return components.filter((component) => {
|
|
327
|
+
const installation = installationByPluginId.get(component.pluginId);
|
|
328
|
+
if (installedOnly && !installation) {
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
if (enabledOnly && !installation?.enabled) {
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
return true;
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
async fetchPluginAsset(pluginId, assetPath) {
|
|
338
|
+
await this.ready();
|
|
339
|
+
if (!this.fetcher) {
|
|
340
|
+
throw new PluginManagerError("No fetch implementation is available.");
|
|
341
|
+
}
|
|
342
|
+
this.require(pluginId);
|
|
343
|
+
const source = this.remotePluginAssets.get(pluginId);
|
|
344
|
+
if (!source) {
|
|
345
|
+
throw new PluginManagerError(`Plugin "${pluginId}" does not expose remote marketplace assets.`);
|
|
346
|
+
}
|
|
347
|
+
const normalizedAssetPath = normalizeSafeAssetPath(assetPath);
|
|
348
|
+
const githubPath = joinGitHubPath(source.rootPath, normalizedAssetPath);
|
|
349
|
+
const response = await this.fetcher(githubRawFileUrl(source, githubPath), {
|
|
350
|
+
headers: source.headers,
|
|
351
|
+
});
|
|
352
|
+
if (!response.ok) {
|
|
353
|
+
return new Response(null, {
|
|
354
|
+
status: response.status,
|
|
355
|
+
statusText: response.statusText,
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
const headers = new Headers();
|
|
359
|
+
const contentType = guessContentType(normalizedAssetPath, response.headers.get("content-type") ?? undefined);
|
|
360
|
+
if (contentType) {
|
|
361
|
+
headers.set("content-type", contentType);
|
|
362
|
+
}
|
|
363
|
+
const cacheControl = response.headers.get("cache-control");
|
|
364
|
+
if (cacheControl) {
|
|
365
|
+
headers.set("cache-control", cacheControl);
|
|
366
|
+
}
|
|
367
|
+
headers.set("x-content-type-options", "nosniff");
|
|
368
|
+
return new Response(response.body, {
|
|
369
|
+
status: response.status,
|
|
370
|
+
statusText: response.statusText,
|
|
371
|
+
headers,
|
|
372
|
+
});
|
|
373
|
+
}
|
|
312
374
|
async getInjections(options = {}) {
|
|
313
375
|
await this.ready();
|
|
314
376
|
const tenantId = this.resolveTenantId(options);
|
|
@@ -437,8 +499,8 @@ export class PluginManager {
|
|
|
437
499
|
}
|
|
438
500
|
successfulCandidates += 1;
|
|
439
501
|
if (candidate.parser === "github-tree") {
|
|
440
|
-
const
|
|
441
|
-
for (const pluginFileCandidate of
|
|
502
|
+
const pluginSchemaCandidates = await readGitHubPluginSchemaCandidates(response, candidate);
|
|
503
|
+
for (const pluginFileCandidate of pluginSchemaCandidates) {
|
|
442
504
|
try {
|
|
443
505
|
const pluginFileResponse = await this.fetcher(pluginFileCandidate.url, {
|
|
444
506
|
headers: pluginFileCandidate.headers,
|
|
@@ -451,6 +513,7 @@ export class PluginManager {
|
|
|
451
513
|
const payload = await readMarketplacePayload(pluginFileResponse, pluginFileCandidate);
|
|
452
514
|
for (const plugin of extractMarketplacePlugins(payload)) {
|
|
453
515
|
const normalizedPlugin = applyMarketplaceDefaults(plugin, pluginFileCandidate);
|
|
516
|
+
this.rememberRemotePluginAssetSource(normalizedPlugin, pluginFileCandidate);
|
|
454
517
|
pluginsById.set(normalizedPlugin.id, normalizedPlugin);
|
|
455
518
|
}
|
|
456
519
|
}
|
|
@@ -463,6 +526,7 @@ export class PluginManager {
|
|
|
463
526
|
const payload = await readMarketplacePayload(response, candidate);
|
|
464
527
|
for (const plugin of extractMarketplacePlugins(payload)) {
|
|
465
528
|
const normalizedPlugin = applyMarketplaceDefaults(plugin, candidate);
|
|
529
|
+
this.rememberRemotePluginAssetSource(normalizedPlugin, candidate);
|
|
466
530
|
pluginsById.set(normalizedPlugin.id, normalizedPlugin);
|
|
467
531
|
}
|
|
468
532
|
}
|
|
@@ -479,6 +543,15 @@ export class PluginManager {
|
|
|
479
543
|
}
|
|
480
544
|
throw new PluginManagerError(`Marketplace "${marketplaceLabel(marketplaceSource)}" could not be loaded: ${String(lastError)}`);
|
|
481
545
|
}
|
|
546
|
+
rememberRemotePluginAssetSource(plugin, candidate) {
|
|
547
|
+
if (!candidate.github) {
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
this.remotePluginAssets.set(plugin.id, {
|
|
551
|
+
...candidate.github,
|
|
552
|
+
rootPath: pluginRootPath(candidate),
|
|
553
|
+
});
|
|
554
|
+
}
|
|
482
555
|
shouldSyncMarketplaces() {
|
|
483
556
|
return Boolean(this.autoSyncMarketplaces &&
|
|
484
557
|
this.marketplaces.length &&
|
|
@@ -740,7 +813,10 @@ function githubMarketplaceCandidates(location, options) {
|
|
|
740
813
|
"plugins.json",
|
|
741
814
|
"index.json",
|
|
742
815
|
];
|
|
743
|
-
const
|
|
816
|
+
const schemaFileNames = normalizeSchemaFileNames(options.schemaFileNames);
|
|
817
|
+
const legacyPluginFileExtensions = normalizePluginFileExtensions(options.pluginFileExtensions);
|
|
818
|
+
const includeSchemaFiles = options.schemaFiles !== false;
|
|
819
|
+
const includeLegacyPluginFiles = options.pluginFiles === true;
|
|
744
820
|
const branchWasExplicit = Boolean(location.branch ?? options.branch);
|
|
745
821
|
const branches = branchWasExplicit
|
|
746
822
|
? [location.branch ?? options.branch ?? "main"]
|
|
@@ -756,7 +832,7 @@ function githubMarketplaceCandidates(location, options) {
|
|
|
756
832
|
: undefined);
|
|
757
833
|
const candidates = [];
|
|
758
834
|
for (const branch of branches) {
|
|
759
|
-
if (!location.exactPath &&
|
|
835
|
+
if (!location.exactPath && (includeSchemaFiles || includeLegacyPluginFiles)) {
|
|
760
836
|
candidates.push({
|
|
761
837
|
url: `${apiBaseUrl}/repos/${location.owner}/${location.repo}/git/trees/${encodeURIComponent(branch)}?recursive=${options.recursivePluginFiles === false ? "0" : "1"}`,
|
|
762
838
|
headers,
|
|
@@ -768,7 +844,9 @@ function githubMarketplaceCandidates(location, options) {
|
|
|
768
844
|
pathPrefix: normalizePathPrefix(location.path),
|
|
769
845
|
rawBaseUrl,
|
|
770
846
|
apiBaseUrl,
|
|
771
|
-
|
|
847
|
+
schemaFileNames,
|
|
848
|
+
legacyPluginFileExtensions,
|
|
849
|
+
includeLegacyPluginFiles,
|
|
772
850
|
avatarSize: options.avatarSize ?? 128,
|
|
773
851
|
headers,
|
|
774
852
|
},
|
|
@@ -776,10 +854,13 @@ function githubMarketplaceCandidates(location, options) {
|
|
|
776
854
|
}
|
|
777
855
|
for (const path of paths) {
|
|
778
856
|
const normalizedPath = path.replace(/^\/+/, "");
|
|
857
|
+
const sourceKind = githubSourceKindForPath(normalizedPath, schemaFileNames, legacyPluginFileExtensions, includeLegacyPluginFiles);
|
|
779
858
|
candidates.push({
|
|
780
859
|
url: `${rawBaseUrl}/${location.owner}/${location.repo}/${branch}/${normalizedPath}`,
|
|
781
860
|
headers,
|
|
782
861
|
parser: "json",
|
|
862
|
+
sourcePath: normalizedPath,
|
|
863
|
+
sourceKind,
|
|
783
864
|
github: {
|
|
784
865
|
owner: location.owner,
|
|
785
866
|
repo: location.repo,
|
|
@@ -787,7 +868,9 @@ function githubMarketplaceCandidates(location, options) {
|
|
|
787
868
|
pathPrefix: normalizePathPrefix(location.path),
|
|
788
869
|
rawBaseUrl,
|
|
789
870
|
apiBaseUrl,
|
|
790
|
-
|
|
871
|
+
schemaFileNames,
|
|
872
|
+
legacyPluginFileExtensions,
|
|
873
|
+
includeLegacyPluginFiles,
|
|
791
874
|
avatarSize: options.avatarSize ?? 128,
|
|
792
875
|
headers,
|
|
793
876
|
},
|
|
@@ -796,6 +879,8 @@ function githubMarketplaceCandidates(location, options) {
|
|
|
796
879
|
url: `${apiBaseUrl}/repos/${location.owner}/${location.repo}/contents/${encodeUrlPath(normalizedPath)}?ref=${encodeURIComponent(branch)}`,
|
|
797
880
|
headers,
|
|
798
881
|
parser: "github-content",
|
|
882
|
+
sourcePath: normalizedPath,
|
|
883
|
+
sourceKind,
|
|
799
884
|
github: {
|
|
800
885
|
owner: location.owner,
|
|
801
886
|
repo: location.repo,
|
|
@@ -803,7 +888,9 @@ function githubMarketplaceCandidates(location, options) {
|
|
|
803
888
|
pathPrefix: normalizePathPrefix(location.path),
|
|
804
889
|
rawBaseUrl,
|
|
805
890
|
apiBaseUrl,
|
|
806
|
-
|
|
891
|
+
schemaFileNames,
|
|
892
|
+
legacyPluginFileExtensions,
|
|
893
|
+
includeLegacyPluginFiles,
|
|
807
894
|
avatarSize: options.avatarSize ?? 128,
|
|
808
895
|
headers,
|
|
809
896
|
},
|
|
@@ -878,7 +965,7 @@ function extractMarketplacePlugins(payload) {
|
|
|
878
965
|
? [payload.plugin, ...(payload.plugins ?? payload.marketplace?.plugins ?? [])]
|
|
879
966
|
: payload.plugins ?? payload.marketplace?.plugins ?? [];
|
|
880
967
|
}
|
|
881
|
-
async function
|
|
968
|
+
async function readGitHubPluginSchemaCandidates(response, candidate) {
|
|
882
969
|
if (!candidate.github) {
|
|
883
970
|
return [];
|
|
884
971
|
}
|
|
@@ -886,20 +973,25 @@ async function readGitHubPluginFileCandidates(response, candidate) {
|
|
|
886
973
|
const payload = (await response.json());
|
|
887
974
|
return (payload.tree ?? [])
|
|
888
975
|
.filter((item) => Boolean(item.path && item.type === "blob"))
|
|
889
|
-
.filter((item) =>
|
|
976
|
+
.filter((item) => isPluginSchemaPath(item.path, discovery))
|
|
890
977
|
.flatMap((item) => {
|
|
891
978
|
const encodedPath = encodeUrlPath(item.path);
|
|
979
|
+
const sourceKind = githubSourceKindForPath(item.path, discovery.schemaFileNames, discovery.legacyPluginFileExtensions, discovery.includeLegacyPluginFiles);
|
|
892
980
|
return [
|
|
893
981
|
{
|
|
894
982
|
url: `${discovery.rawBaseUrl}/${discovery.owner}/${discovery.repo}/${discovery.branch}/${encodedPath}`,
|
|
895
983
|
headers: discovery.headers,
|
|
896
984
|
parser: "json",
|
|
985
|
+
sourcePath: item.path,
|
|
986
|
+
sourceKind,
|
|
897
987
|
github: discovery,
|
|
898
988
|
},
|
|
899
989
|
{
|
|
900
990
|
url: `${discovery.apiBaseUrl}/repos/${discovery.owner}/${discovery.repo}/contents/${encodedPath}?ref=${encodeURIComponent(discovery.branch)}`,
|
|
901
991
|
headers: discovery.headers,
|
|
902
992
|
parser: "github-content",
|
|
993
|
+
sourcePath: item.path,
|
|
994
|
+
sourceKind,
|
|
903
995
|
github: discovery,
|
|
904
996
|
},
|
|
905
997
|
];
|
|
@@ -921,23 +1013,26 @@ function applyMarketplaceDefaults(plugin, candidate) {
|
|
|
921
1013
|
}
|
|
922
1014
|
return plugin;
|
|
923
1015
|
}
|
|
1016
|
+
const normalizedPlugin = normalizeGitHubPluginReferences(plugin, candidate);
|
|
924
1017
|
return {
|
|
925
|
-
...
|
|
1018
|
+
...normalizedPlugin,
|
|
926
1019
|
provider: github.owner,
|
|
927
1020
|
iconUrl: githubAvatarUrl(github.owner, github.avatarSize),
|
|
928
|
-
repositoryUrl:
|
|
1021
|
+
repositoryUrl: normalizedPlugin.repositoryUrl ??
|
|
929
1022
|
`https://github.com/${github.owner}/${github.repo}`,
|
|
930
1023
|
meta: {
|
|
931
|
-
...
|
|
1024
|
+
...normalizedPlugin.meta,
|
|
932
1025
|
github: {
|
|
933
1026
|
owner: github.owner,
|
|
934
1027
|
repo: github.repo,
|
|
935
1028
|
branch: github.branch,
|
|
1029
|
+
rootPath: pluginRootPath(candidate),
|
|
1030
|
+
schemaPath: candidate.sourceKind === "schema" ? candidate.sourcePath : undefined,
|
|
936
1031
|
},
|
|
937
1032
|
},
|
|
938
1033
|
};
|
|
939
1034
|
}
|
|
940
|
-
function
|
|
1035
|
+
function isPluginSchemaPath(path, discovery) {
|
|
941
1036
|
const normalizedPath = path.replace(/^\/+/, "");
|
|
942
1037
|
const prefix = discovery.pathPrefix;
|
|
943
1038
|
if (prefix &&
|
|
@@ -945,7 +1040,205 @@ function isPluginFilePath(path, discovery) {
|
|
|
945
1040
|
!normalizedPath.startsWith(`${prefix}/`)) {
|
|
946
1041
|
return false;
|
|
947
1042
|
}
|
|
948
|
-
|
|
1043
|
+
const lowerPath = normalizedPath.toLocaleLowerCase();
|
|
1044
|
+
const fileName = lowerPath.split("/").pop() ?? lowerPath;
|
|
1045
|
+
if (discovery.schemaFileNames.includes(fileName)) {
|
|
1046
|
+
return true;
|
|
1047
|
+
}
|
|
1048
|
+
return (discovery.includeLegacyPluginFiles &&
|
|
1049
|
+
discovery.legacyPluginFileExtensions.some((extension) => lowerPath.endsWith(extension)));
|
|
1050
|
+
}
|
|
1051
|
+
function normalizeGitHubPluginReferences(plugin, candidate) {
|
|
1052
|
+
const github = candidate.github;
|
|
1053
|
+
if (!github) {
|
|
1054
|
+
return plugin;
|
|
1055
|
+
}
|
|
1056
|
+
const rootPath = pluginRootPath(candidate);
|
|
1057
|
+
const resolve = (value) => value ? resolveGitHubReferenceUrl(github, rootPath, value) : value;
|
|
1058
|
+
return {
|
|
1059
|
+
...plugin,
|
|
1060
|
+
docsUrl: resolve(plugin.docsUrl),
|
|
1061
|
+
websiteUrl: resolve(plugin.websiteUrl),
|
|
1062
|
+
frontend: normalizeFrontendManifest(plugin.frontend, github, rootPath),
|
|
1063
|
+
injections: plugin.injections?.map((injection) => ({
|
|
1064
|
+
...injection,
|
|
1065
|
+
src: resolve(injection.src),
|
|
1066
|
+
attributes: normalizeUrlAttributes(injection.attributes, resolve),
|
|
1067
|
+
})),
|
|
1068
|
+
assets: plugin.assets?.map((asset) => normalizePluginAsset(asset, resolve)),
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
function normalizeFrontendManifest(frontend, github, rootPath) {
|
|
1072
|
+
if (!frontend) {
|
|
1073
|
+
return frontend;
|
|
1074
|
+
}
|
|
1075
|
+
return {
|
|
1076
|
+
...frontend,
|
|
1077
|
+
registry: frontend.registry
|
|
1078
|
+
? Object.fromEntries(Object.entries(frontend.registry).map(([key, component]) => [
|
|
1079
|
+
key,
|
|
1080
|
+
normalizeComponentReference(component, github, rootPath),
|
|
1081
|
+
]))
|
|
1082
|
+
: frontend.registry,
|
|
1083
|
+
components: frontend.components?.map((component) => ({
|
|
1084
|
+
...component,
|
|
1085
|
+
component: normalizeComponentReference(component.component, github, rootPath),
|
|
1086
|
+
})),
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
function normalizeComponentReference(reference, github, rootPath) {
|
|
1090
|
+
if (reference.type !== "remote") {
|
|
1091
|
+
return reference;
|
|
1092
|
+
}
|
|
1093
|
+
const referencePath = reference.path ??
|
|
1094
|
+
(reference.url && isRelativeReference(reference.url)
|
|
1095
|
+
? reference.url
|
|
1096
|
+
: undefined);
|
|
1097
|
+
const urlSource = reference.url ?? referencePath;
|
|
1098
|
+
return {
|
|
1099
|
+
...reference,
|
|
1100
|
+
path: referencePath ? normalizeClientAssetPath(referencePath) : reference.path,
|
|
1101
|
+
url: urlSource
|
|
1102
|
+
? resolveGitHubReferenceUrl(github, rootPath, urlSource)
|
|
1103
|
+
: reference.url,
|
|
1104
|
+
};
|
|
1105
|
+
}
|
|
1106
|
+
function normalizePluginAsset(asset, resolve) {
|
|
1107
|
+
return {
|
|
1108
|
+
...asset,
|
|
1109
|
+
url: resolve(asset.url) ?? asset.url,
|
|
1110
|
+
};
|
|
1111
|
+
}
|
|
1112
|
+
function normalizeUrlAttributes(attributes, resolve) {
|
|
1113
|
+
if (!attributes) {
|
|
1114
|
+
return attributes;
|
|
1115
|
+
}
|
|
1116
|
+
const urlAttributes = new Set(["href", "src", "poster"]);
|
|
1117
|
+
return Object.fromEntries(Object.entries(attributes).map(([key, value]) => [
|
|
1118
|
+
key,
|
|
1119
|
+
typeof value === "string" && urlAttributes.has(key.toLocaleLowerCase())
|
|
1120
|
+
? resolve(value)
|
|
1121
|
+
: value,
|
|
1122
|
+
]));
|
|
1123
|
+
}
|
|
1124
|
+
function pluginRootPath(candidate) {
|
|
1125
|
+
if (!candidate.sourcePath) {
|
|
1126
|
+
return candidate.github?.pathPrefix;
|
|
1127
|
+
}
|
|
1128
|
+
return dirname(candidate.sourcePath) ?? candidate.github?.pathPrefix;
|
|
1129
|
+
}
|
|
1130
|
+
function githubSourceKindForPath(path, schemaFileNames, legacyPluginFileExtensions, includeLegacyPluginFiles) {
|
|
1131
|
+
const lowerPath = path.replace(/^\/+/, "").toLocaleLowerCase();
|
|
1132
|
+
const fileName = lowerPath.split("/").pop() ?? lowerPath;
|
|
1133
|
+
if (schemaFileNames.includes(fileName)) {
|
|
1134
|
+
return "schema";
|
|
1135
|
+
}
|
|
1136
|
+
if (includeLegacyPluginFiles &&
|
|
1137
|
+
legacyPluginFileExtensions.some((extension) => lowerPath.endsWith(extension))) {
|
|
1138
|
+
return "legacy-plugin";
|
|
1139
|
+
}
|
|
1140
|
+
return "marketplace";
|
|
1141
|
+
}
|
|
1142
|
+
function resolveGitHubReferenceUrl(github, rootPath, value) {
|
|
1143
|
+
if (!isRelativeReference(value)) {
|
|
1144
|
+
return value;
|
|
1145
|
+
}
|
|
1146
|
+
const { path, suffix } = splitReferenceSuffix(value);
|
|
1147
|
+
const basePath = path.startsWith("/") ? undefined : rootPath;
|
|
1148
|
+
const githubPath = joinGitHubPath(basePath, path);
|
|
1149
|
+
return `${githubRawFileUrl(github, githubPath)}${suffix}`;
|
|
1150
|
+
}
|
|
1151
|
+
function githubRawFileUrl(github, path) {
|
|
1152
|
+
return `${github.rawBaseUrl}/${github.owner}/${github.repo}/${github.branch}/${encodeUrlPath(path)}`;
|
|
1153
|
+
}
|
|
1154
|
+
function splitReferenceSuffix(value) {
|
|
1155
|
+
const index = value.search(/[?#]/);
|
|
1156
|
+
if (index === -1) {
|
|
1157
|
+
return { path: value, suffix: "" };
|
|
1158
|
+
}
|
|
1159
|
+
return {
|
|
1160
|
+
path: value.slice(0, index),
|
|
1161
|
+
suffix: value.slice(index),
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
function isRelativeReference(value) {
|
|
1165
|
+
return (!/^[a-z][a-z0-9+.-]*:/i.test(value) &&
|
|
1166
|
+
!value.startsWith("//") &&
|
|
1167
|
+
!value.startsWith("#") &&
|
|
1168
|
+
!value.startsWith("{{"));
|
|
1169
|
+
}
|
|
1170
|
+
function normalizeClientAssetPath(path) {
|
|
1171
|
+
if (!isRelativeReference(path) || path.startsWith("/")) {
|
|
1172
|
+
return undefined;
|
|
1173
|
+
}
|
|
1174
|
+
const { path: withoutSuffix } = splitReferenceSuffix(path);
|
|
1175
|
+
if (withoutSuffix.split("/").some((part) => part === "..")) {
|
|
1176
|
+
return undefined;
|
|
1177
|
+
}
|
|
1178
|
+
return normalizeGitHubPath(withoutSuffix);
|
|
1179
|
+
}
|
|
1180
|
+
function normalizeSafeAssetPath(path) {
|
|
1181
|
+
const decoded = safeDecodeURIComponent(path).replace(/^\/+/, "");
|
|
1182
|
+
const parts = decoded.split("/").filter(Boolean);
|
|
1183
|
+
if (!parts.length) {
|
|
1184
|
+
throw new PluginManagerError("Plugin asset path is required.");
|
|
1185
|
+
}
|
|
1186
|
+
if (parts.some((part) => part === "." || part === "..")) {
|
|
1187
|
+
throw new PluginManagerError("Plugin asset path cannot leave the plugin folder.");
|
|
1188
|
+
}
|
|
1189
|
+
return parts.join("/");
|
|
1190
|
+
}
|
|
1191
|
+
function joinGitHubPath(basePath, path) {
|
|
1192
|
+
return normalizeGitHubPath([basePath, path.replace(/^\/+/, "")].filter(Boolean).join("/"));
|
|
1193
|
+
}
|
|
1194
|
+
function normalizeGitHubPath(path) {
|
|
1195
|
+
const parts = [];
|
|
1196
|
+
for (const part of path.replace(/^\/+/, "").split("/")) {
|
|
1197
|
+
if (!part || part === ".") {
|
|
1198
|
+
continue;
|
|
1199
|
+
}
|
|
1200
|
+
if (part === "..") {
|
|
1201
|
+
parts.pop();
|
|
1202
|
+
continue;
|
|
1203
|
+
}
|
|
1204
|
+
parts.push(part);
|
|
1205
|
+
}
|
|
1206
|
+
return parts.join("/");
|
|
1207
|
+
}
|
|
1208
|
+
function dirname(path) {
|
|
1209
|
+
const parts = normalizeGitHubPath(path).split("/");
|
|
1210
|
+
parts.pop();
|
|
1211
|
+
return parts.length ? parts.join("/") : undefined;
|
|
1212
|
+
}
|
|
1213
|
+
function safeDecodeURIComponent(value) {
|
|
1214
|
+
try {
|
|
1215
|
+
return decodeURIComponent(value);
|
|
1216
|
+
}
|
|
1217
|
+
catch {
|
|
1218
|
+
return value;
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
function guessContentType(path, upstream) {
|
|
1222
|
+
const extension = path.split(".").pop()?.toLocaleLowerCase();
|
|
1223
|
+
const knownTypes = {
|
|
1224
|
+
css: "text/css; charset=utf-8",
|
|
1225
|
+
gif: "image/gif",
|
|
1226
|
+
html: "text/html; charset=utf-8",
|
|
1227
|
+
ico: "image/x-icon",
|
|
1228
|
+
jpeg: "image/jpeg",
|
|
1229
|
+
jpg: "image/jpeg",
|
|
1230
|
+
js: "application/javascript; charset=utf-8",
|
|
1231
|
+
json: "application/json; charset=utf-8",
|
|
1232
|
+
jsx: "application/javascript; charset=utf-8",
|
|
1233
|
+
mjs: "application/javascript; charset=utf-8",
|
|
1234
|
+
png: "image/png",
|
|
1235
|
+
svg: "image/svg+xml",
|
|
1236
|
+
txt: "text/plain; charset=utf-8",
|
|
1237
|
+
webp: "image/webp",
|
|
1238
|
+
woff: "font/woff",
|
|
1239
|
+
woff2: "font/woff2",
|
|
1240
|
+
};
|
|
1241
|
+
return (extension && knownTypes[extension]) || upstream;
|
|
949
1242
|
}
|
|
950
1243
|
function marketplaceLabel(source) {
|
|
951
1244
|
if (typeof source === "string") {
|
|
@@ -992,6 +1285,9 @@ function normalizePathPrefix(path) {
|
|
|
992
1285
|
const normalized = path?.replace(/^\/+|\/+$/g, "");
|
|
993
1286
|
return normalized || undefined;
|
|
994
1287
|
}
|
|
1288
|
+
function normalizeSchemaFileNames(fileNames) {
|
|
1289
|
+
return (fileNames?.length ? fileNames : ["schema.json"]).map((fileName) => fileName.replace(/^\/+/, "").toLocaleLowerCase());
|
|
1290
|
+
}
|
|
995
1291
|
function normalizePluginFileExtensions(extensions) {
|
|
996
1292
|
return (extensions?.length ? extensions : [".plugin"]).map((extension) => extension.startsWith(".")
|
|
997
1293
|
? extension.toLocaleLowerCase()
|