@camstack/server 1.2.575 → 1.2.576
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.
|
@@ -1532,7 +1532,7 @@ function buildAddonsProvider(ar, ps, ls, moleculer, configService, ctx, agentReg
|
|
|
1532
1532
|
listInstallations: async (input) => (0, addon_uninstall_js_1.listInstallations)(uninstallSeam, input.packageName),
|
|
1533
1533
|
reloadPackages: async () => ps.reloadPackages(),
|
|
1534
1534
|
searchAvailable: async (input) => {
|
|
1535
|
-
const results = await ps.searchNpm(input?.query);
|
|
1535
|
+
const results = await ps.searchNpm(input?.query, input?.capability);
|
|
1536
1536
|
const installedIds = new Set(ps.listInstalled().map((p) => p.name));
|
|
1537
1537
|
return results.map((r) => ({
|
|
1538
1538
|
...r,
|
|
@@ -192,6 +192,7 @@ class AddonPackageService {
|
|
|
192
192
|
cachedUpdates = null;
|
|
193
193
|
searchCache = null;
|
|
194
194
|
versionCache = new Map();
|
|
195
|
+
capabilityCache = new Map();
|
|
195
196
|
updateAvailability;
|
|
196
197
|
// -- Auto-update state ----------------------------------------------------
|
|
197
198
|
autoUpdateConfig = {
|
|
@@ -217,6 +218,8 @@ class AddonPackageService {
|
|
|
217
218
|
static UPDATE_CACHE_TTL_MS = 10 * 60 * 1000; // 10 minutes
|
|
218
219
|
static SEARCH_CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
219
220
|
static VERSION_CACHE_TTL_MS = 10 * 60 * 1000; // 10 minutes
|
|
221
|
+
static CAPABILITY_CACHE_TTL_MS = 10 * 60 * 1000; // 10 minutes
|
|
222
|
+
static CAPABILITY_LOOKUP_CONCURRENCY = 6;
|
|
220
223
|
static NPM_REGISTRY = 'https://registry.npmjs.org';
|
|
221
224
|
static REGISTRY_TIMEOUT_MS = 10_000;
|
|
222
225
|
constructor(loggingService, eventBusService, configService, addonRegistry, notificationService, toastService,
|
|
@@ -777,8 +780,15 @@ class AddonPackageService {
|
|
|
777
780
|
// =========================================================================
|
|
778
781
|
// npm search
|
|
779
782
|
// =========================================================================
|
|
780
|
-
/**
|
|
781
|
-
|
|
783
|
+
/**
|
|
784
|
+
* Search npm for camstack addon packages, optionally filtered by query and
|
|
785
|
+
* by a capability the package must declare. The capabilities come from each
|
|
786
|
+
* package's own `/latest` manifest (`camstack.addons[].capabilities`) — the
|
|
787
|
+
* single source of truth (D4), no hand-kept keywords. A package whose
|
|
788
|
+
* manifest could not be read carries `capabilities: null` and is excluded
|
|
789
|
+
* from a capability-filtered result (unknown is not a match).
|
|
790
|
+
*/
|
|
791
|
+
async searchNpm(query, capability) {
|
|
782
792
|
const npmResults = await this.fetchSearchFromNpm();
|
|
783
793
|
let filtered = npmResults;
|
|
784
794
|
if (query) {
|
|
@@ -787,7 +797,14 @@ class AddonPackageService {
|
|
|
787
797
|
r.description?.toLowerCase().includes(q) ||
|
|
788
798
|
r.keywords?.some((k) => k.toLowerCase().includes(q)));
|
|
789
799
|
}
|
|
790
|
-
|
|
800
|
+
const capabilitiesByName = await this.lookupCapabilities(filtered.map((r) => r.name));
|
|
801
|
+
const failed = filtered.filter((r) => capabilitiesByName.get(r.name) === null).length;
|
|
802
|
+
if (failed > 0) {
|
|
803
|
+
this.logger.warn('npm capability lookup failed for some packages', {
|
|
804
|
+
meta: { failed, total: filtered.length, capability: capability ?? null },
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
const withCapabilities = filtered.map((r) => ({
|
|
791
808
|
name: r.name,
|
|
792
809
|
version: r.version,
|
|
793
810
|
description: r.description ?? '',
|
|
@@ -796,7 +813,73 @@ class AddonPackageService {
|
|
|
796
813
|
author: r.publisher?.username ?? '',
|
|
797
814
|
installed: false, // caller will enrich
|
|
798
815
|
installedVersion: undefined,
|
|
816
|
+
capabilities: capabilitiesByName.get(r.name) ?? null,
|
|
799
817
|
}));
|
|
818
|
+
if (capability === undefined)
|
|
819
|
+
return withCapabilities;
|
|
820
|
+
return withCapabilities.filter((r) => r.capabilities?.includes(capability) === true);
|
|
821
|
+
}
|
|
822
|
+
/**
|
|
823
|
+
* Declared capabilities per package, through a bounded worker pool and the
|
|
824
|
+
* 10-minute cache. `null` = the lookup failed (not cached, so the next
|
|
825
|
+
* search retries).
|
|
826
|
+
*/
|
|
827
|
+
async lookupCapabilities(names) {
|
|
828
|
+
const result = new Map();
|
|
829
|
+
const now = Date.now();
|
|
830
|
+
const pending = [];
|
|
831
|
+
for (const name of names) {
|
|
832
|
+
const cached = this.capabilityCache.get(name);
|
|
833
|
+
if (cached !== undefined && cached.expiresAt > now)
|
|
834
|
+
result.set(name, cached.capabilities);
|
|
835
|
+
else
|
|
836
|
+
pending.push(name);
|
|
837
|
+
}
|
|
838
|
+
let next = 0;
|
|
839
|
+
const worker = async () => {
|
|
840
|
+
while (next < pending.length) {
|
|
841
|
+
const name = pending[next++];
|
|
842
|
+
if (name === undefined)
|
|
843
|
+
return;
|
|
844
|
+
const capabilities = await this.fetchPackageCapabilities(name);
|
|
845
|
+
result.set(name, capabilities);
|
|
846
|
+
if (capabilities !== null) {
|
|
847
|
+
this.capabilityCache.set(name, {
|
|
848
|
+
capabilities,
|
|
849
|
+
expiresAt: Date.now() + AddonPackageService.CAPABILITY_CACHE_TTL_MS,
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
};
|
|
854
|
+
const poolSize = Math.min(AddonPackageService.CAPABILITY_LOOKUP_CONCURRENCY, pending.length);
|
|
855
|
+
await Promise.all(Array.from({ length: poolSize }, worker));
|
|
856
|
+
return result;
|
|
857
|
+
}
|
|
858
|
+
/** Read `camstack.addons[].capabilities` from a package's `/latest`; null on any failure. */
|
|
859
|
+
async fetchPackageCapabilities(packageName) {
|
|
860
|
+
try {
|
|
861
|
+
const url = `${this.resolveRegistryBase()}/${packageName.replace('/', '%2F')}/latest`;
|
|
862
|
+
const response = await fetch(url, {
|
|
863
|
+
signal: AbortSignal.timeout(AddonPackageService.REGISTRY_TIMEOUT_MS),
|
|
864
|
+
});
|
|
865
|
+
if (!response.ok)
|
|
866
|
+
return null;
|
|
867
|
+
const data = await fetchJsonObject(response);
|
|
868
|
+
const addons = asRecord(data['camstack'])['addons'];
|
|
869
|
+
const names = new Set();
|
|
870
|
+
for (const addon of Array.isArray(addons) ? addons : []) {
|
|
871
|
+
const caps = asRecord(addon)['capabilities'];
|
|
872
|
+
for (const cap of Array.isArray(caps) ? caps : []) {
|
|
873
|
+
const name = typeof cap === 'string' ? cap : asString(asRecord(cap)['name']);
|
|
874
|
+
if (name)
|
|
875
|
+
names.add(name);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
return [...names];
|
|
879
|
+
}
|
|
880
|
+
catch {
|
|
881
|
+
return null;
|
|
882
|
+
}
|
|
800
883
|
}
|
|
801
884
|
// =========================================================================
|
|
802
885
|
// Package versions
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/server",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.576",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"private": false,
|
|
6
6
|
"files": [
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
]
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@camstack/addon-admin-ui": "1.2.
|
|
41
|
+
"@camstack/addon-admin-ui": "1.2.426",
|
|
42
42
|
"@camstack/addon-agent-ui": "1.2.233",
|
|
43
43
|
"@camstack/addon-auth": "1.2.145",
|
|
44
44
|
"@camstack/addon-decoder-nodeav": "1.2.123",
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
"@camstack/addon-post-analysis": "1.2.369",
|
|
49
49
|
"@camstack/develop": "0.1.10",
|
|
50
50
|
"@camstack/sdk": "1.2.131",
|
|
51
|
-
"@camstack/system": "1.2.
|
|
52
|
-
"@camstack/types": "1.2.
|
|
51
|
+
"@camstack/system": "1.2.435",
|
|
52
|
+
"@camstack/types": "1.2.371",
|
|
53
53
|
"@camstack/ui-library": "1.2.289",
|
|
54
54
|
"@fastify/compress": "^9.0.0",
|
|
55
55
|
"@fastify/cookie": "^11.0.2",
|