capns 0.76.17552 → 0.77.17595
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/capns.js +442 -1
- package/capns.test.js +375 -0
- package/package.json +1 -1
package/capns.js
CHANGED
|
@@ -3657,6 +3657,440 @@ class StdinSource {
|
|
|
3657
3657
|
}
|
|
3658
3658
|
}
|
|
3659
3659
|
|
|
3660
|
+
// =============================================================================
|
|
3661
|
+
// Plugin Repository System
|
|
3662
|
+
// =============================================================================
|
|
3663
|
+
|
|
3664
|
+
/**
|
|
3665
|
+
* Plugin capability summary from registry
|
|
3666
|
+
*/
|
|
3667
|
+
class PluginCapSummary {
|
|
3668
|
+
constructor(urn, title, description = '') {
|
|
3669
|
+
this.urn = urn;
|
|
3670
|
+
this.title = title;
|
|
3671
|
+
this.description = description;
|
|
3672
|
+
}
|
|
3673
|
+
}
|
|
3674
|
+
|
|
3675
|
+
/**
|
|
3676
|
+
* Plugin information from registry
|
|
3677
|
+
*/
|
|
3678
|
+
class PluginInfo {
|
|
3679
|
+
constructor(data) {
|
|
3680
|
+
this.id = data.id;
|
|
3681
|
+
this.name = data.name;
|
|
3682
|
+
this.version = data.version || '';
|
|
3683
|
+
this.description = data.description || '';
|
|
3684
|
+
this.author = data.author || '';
|
|
3685
|
+
this.pageUrl = data.pageUrl || '';
|
|
3686
|
+
this.teamId = data.teamId || '';
|
|
3687
|
+
this.signedAt = data.signedAt || '';
|
|
3688
|
+
this.minAppVersion = data.minAppVersion || '';
|
|
3689
|
+
this.caps = (data.caps || []).map(c => new PluginCapSummary(c.urn, c.title, c.description || ''));
|
|
3690
|
+
this.categories = data.categories || [];
|
|
3691
|
+
this.tags = data.tags || [];
|
|
3692
|
+
this.changelog = data.changelog || {};
|
|
3693
|
+
// Distribution fields
|
|
3694
|
+
this.platform = data.platform || '';
|
|
3695
|
+
this.packageName = data.packageName || '';
|
|
3696
|
+
this.packageSha256 = data.packageSha256 || '';
|
|
3697
|
+
this.packageSize = data.packageSize || 0;
|
|
3698
|
+
this.binaryName = data.binaryName || '';
|
|
3699
|
+
this.binarySha256 = data.binarySha256 || '';
|
|
3700
|
+
this.binarySize = data.binarySize || 0;
|
|
3701
|
+
this.availableVersions = data.availableVersions || [];
|
|
3702
|
+
}
|
|
3703
|
+
|
|
3704
|
+
/**
|
|
3705
|
+
* Check if plugin is signed (has team_id and signed_at)
|
|
3706
|
+
*/
|
|
3707
|
+
isSigned() {
|
|
3708
|
+
return this.teamId.length > 0 && this.signedAt.length > 0;
|
|
3709
|
+
}
|
|
3710
|
+
|
|
3711
|
+
/**
|
|
3712
|
+
* Check if binary download info is available
|
|
3713
|
+
*/
|
|
3714
|
+
hasBinary() {
|
|
3715
|
+
return this.binaryName.length > 0 && this.binarySha256.length > 0;
|
|
3716
|
+
}
|
|
3717
|
+
}
|
|
3718
|
+
|
|
3719
|
+
/**
|
|
3720
|
+
* Plugin suggestion for a missing cap
|
|
3721
|
+
*/
|
|
3722
|
+
class PluginSuggestion {
|
|
3723
|
+
constructor(data) {
|
|
3724
|
+
this.pluginId = data.pluginId;
|
|
3725
|
+
this.pluginName = data.pluginName;
|
|
3726
|
+
this.pluginDescription = data.pluginDescription;
|
|
3727
|
+
this.capUrn = data.capUrn;
|
|
3728
|
+
this.capTitle = data.capTitle;
|
|
3729
|
+
this.latestVersion = data.latestVersion;
|
|
3730
|
+
this.repoUrl = data.repoUrl;
|
|
3731
|
+
this.pageUrl = data.pageUrl;
|
|
3732
|
+
}
|
|
3733
|
+
}
|
|
3734
|
+
|
|
3735
|
+
/**
|
|
3736
|
+
* Plugin registry cache entry
|
|
3737
|
+
*/
|
|
3738
|
+
class PluginRepoCache {
|
|
3739
|
+
constructor(repoUrl) {
|
|
3740
|
+
this.plugins = new Map(); // plugin_id -> PluginInfo
|
|
3741
|
+
this.capToPlugins = new Map(); // cap_urn -> [plugin_ids]
|
|
3742
|
+
this.lastUpdated = Date.now();
|
|
3743
|
+
this.repoUrl = repoUrl;
|
|
3744
|
+
}
|
|
3745
|
+
}
|
|
3746
|
+
|
|
3747
|
+
/**
|
|
3748
|
+
* Plugin repository client - fetches and caches plugin registry
|
|
3749
|
+
*/
|
|
3750
|
+
class PluginRepoClient {
|
|
3751
|
+
constructor(cacheTtlSeconds = 3600) {
|
|
3752
|
+
this.caches = new Map(); // repo_url -> PluginRepoCache
|
|
3753
|
+
this.cacheTtl = cacheTtlSeconds * 1000; // Convert to milliseconds
|
|
3754
|
+
}
|
|
3755
|
+
|
|
3756
|
+
/**
|
|
3757
|
+
* Fetch registry from a URL
|
|
3758
|
+
*/
|
|
3759
|
+
async fetchRegistry(repoUrl) {
|
|
3760
|
+
const response = await fetch(repoUrl);
|
|
3761
|
+
|
|
3762
|
+
if (!response.ok) {
|
|
3763
|
+
throw new Error(`Plugin registry request failed: HTTP ${response.status} from ${repoUrl}`);
|
|
3764
|
+
}
|
|
3765
|
+
|
|
3766
|
+
const data = await response.json();
|
|
3767
|
+
|
|
3768
|
+
if (!data.plugins || !Array.isArray(data.plugins)) {
|
|
3769
|
+
throw new Error(`Invalid plugin registry response from ${repoUrl}: missing plugins array`);
|
|
3770
|
+
}
|
|
3771
|
+
|
|
3772
|
+
return data.plugins.map(p => new PluginInfo(p));
|
|
3773
|
+
}
|
|
3774
|
+
|
|
3775
|
+
/**
|
|
3776
|
+
* Update cache from registry data
|
|
3777
|
+
*/
|
|
3778
|
+
updateCache(repoUrl, plugins) {
|
|
3779
|
+
const cache = new PluginRepoCache(repoUrl);
|
|
3780
|
+
|
|
3781
|
+
for (const plugin of plugins) {
|
|
3782
|
+
cache.plugins.set(plugin.id, plugin);
|
|
3783
|
+
|
|
3784
|
+
for (const cap of plugin.caps) {
|
|
3785
|
+
if (!cache.capToPlugins.has(cap.urn)) {
|
|
3786
|
+
cache.capToPlugins.set(cap.urn, []);
|
|
3787
|
+
}
|
|
3788
|
+
cache.capToPlugins.get(cap.urn).push(plugin.id);
|
|
3789
|
+
}
|
|
3790
|
+
}
|
|
3791
|
+
|
|
3792
|
+
this.caches.set(repoUrl, cache);
|
|
3793
|
+
}
|
|
3794
|
+
|
|
3795
|
+
/**
|
|
3796
|
+
* Check if cache is stale
|
|
3797
|
+
*/
|
|
3798
|
+
isCacheStale(cache) {
|
|
3799
|
+
return (Date.now() - cache.lastUpdated) > this.cacheTtl;
|
|
3800
|
+
}
|
|
3801
|
+
|
|
3802
|
+
/**
|
|
3803
|
+
* Sync plugin data from repository URLs
|
|
3804
|
+
*/
|
|
3805
|
+
async syncRepos(repoUrls) {
|
|
3806
|
+
for (const repoUrl of repoUrls) {
|
|
3807
|
+
try {
|
|
3808
|
+
const plugins = await this.fetchRegistry(repoUrl);
|
|
3809
|
+
this.updateCache(repoUrl, plugins);
|
|
3810
|
+
} catch (e) {
|
|
3811
|
+
console.warn(`Failed to sync plugin repo ${repoUrl}: ${e.message}`);
|
|
3812
|
+
// Continue with other repos
|
|
3813
|
+
}
|
|
3814
|
+
}
|
|
3815
|
+
}
|
|
3816
|
+
|
|
3817
|
+
/**
|
|
3818
|
+
* Check if any repo needs syncing
|
|
3819
|
+
*/
|
|
3820
|
+
needsSync(repoUrls) {
|
|
3821
|
+
for (const repoUrl of repoUrls) {
|
|
3822
|
+
const cache = this.caches.get(repoUrl);
|
|
3823
|
+
if (!cache || this.isCacheStale(cache)) {
|
|
3824
|
+
return true;
|
|
3825
|
+
}
|
|
3826
|
+
}
|
|
3827
|
+
return false;
|
|
3828
|
+
}
|
|
3829
|
+
|
|
3830
|
+
/**
|
|
3831
|
+
* Get plugin suggestions for a cap URN
|
|
3832
|
+
*/
|
|
3833
|
+
getSuggestionsForCap(capUrn) {
|
|
3834
|
+
const suggestions = [];
|
|
3835
|
+
|
|
3836
|
+
for (const cache of this.caches.values()) {
|
|
3837
|
+
const pluginIds = cache.capToPlugins.get(capUrn);
|
|
3838
|
+
if (!pluginIds) continue;
|
|
3839
|
+
|
|
3840
|
+
for (const pluginId of pluginIds) {
|
|
3841
|
+
const plugin = cache.plugins.get(pluginId);
|
|
3842
|
+
if (!plugin) continue;
|
|
3843
|
+
|
|
3844
|
+
const capInfo = plugin.caps.find(c => c.urn === capUrn);
|
|
3845
|
+
if (!capInfo) continue;
|
|
3846
|
+
|
|
3847
|
+
const pageUrl = plugin.pageUrl || cache.repoUrl;
|
|
3848
|
+
|
|
3849
|
+
suggestions.push(new PluginSuggestion({
|
|
3850
|
+
pluginId: plugin.id,
|
|
3851
|
+
pluginName: plugin.name,
|
|
3852
|
+
pluginDescription: plugin.description,
|
|
3853
|
+
capUrn: capUrn,
|
|
3854
|
+
capTitle: capInfo.title,
|
|
3855
|
+
latestVersion: plugin.version,
|
|
3856
|
+
repoUrl: cache.repoUrl,
|
|
3857
|
+
pageUrl: pageUrl
|
|
3858
|
+
}));
|
|
3859
|
+
}
|
|
3860
|
+
}
|
|
3861
|
+
|
|
3862
|
+
return suggestions;
|
|
3863
|
+
}
|
|
3864
|
+
|
|
3865
|
+
/**
|
|
3866
|
+
* Get all available plugins from all repos
|
|
3867
|
+
*/
|
|
3868
|
+
getAllPlugins() {
|
|
3869
|
+
const plugins = [];
|
|
3870
|
+
for (const cache of this.caches.values()) {
|
|
3871
|
+
for (const [pluginId, pluginInfo] of cache.plugins) {
|
|
3872
|
+
plugins.push([pluginId, pluginInfo]);
|
|
3873
|
+
}
|
|
3874
|
+
}
|
|
3875
|
+
return plugins;
|
|
3876
|
+
}
|
|
3877
|
+
|
|
3878
|
+
/**
|
|
3879
|
+
* Get all available cap URNs from plugins
|
|
3880
|
+
*/
|
|
3881
|
+
getAllAvailableCaps() {
|
|
3882
|
+
const caps = new Set();
|
|
3883
|
+
for (const cache of this.caches.values()) {
|
|
3884
|
+
for (const capUrn of cache.capToPlugins.keys()) {
|
|
3885
|
+
caps.add(capUrn);
|
|
3886
|
+
}
|
|
3887
|
+
}
|
|
3888
|
+
return Array.from(caps).sort();
|
|
3889
|
+
}
|
|
3890
|
+
|
|
3891
|
+
/**
|
|
3892
|
+
* Get plugin info by ID
|
|
3893
|
+
*/
|
|
3894
|
+
getPlugin(pluginId) {
|
|
3895
|
+
for (const cache of this.caches.values()) {
|
|
3896
|
+
const plugin = cache.plugins.get(pluginId);
|
|
3897
|
+
if (plugin) {
|
|
3898
|
+
return plugin;
|
|
3899
|
+
}
|
|
3900
|
+
}
|
|
3901
|
+
return null;
|
|
3902
|
+
}
|
|
3903
|
+
|
|
3904
|
+
/**
|
|
3905
|
+
* Get suggestions for missing caps
|
|
3906
|
+
*/
|
|
3907
|
+
getSuggestionsForMissingCaps(availableCaps, requestedCaps) {
|
|
3908
|
+
const availableSet = new Set(availableCaps);
|
|
3909
|
+
const suggestions = [];
|
|
3910
|
+
|
|
3911
|
+
for (const capUrn of requestedCaps) {
|
|
3912
|
+
if (!availableSet.has(capUrn)) {
|
|
3913
|
+
suggestions.push(...this.getSuggestionsForCap(capUrn));
|
|
3914
|
+
}
|
|
3915
|
+
}
|
|
3916
|
+
|
|
3917
|
+
return suggestions;
|
|
3918
|
+
}
|
|
3919
|
+
}
|
|
3920
|
+
|
|
3921
|
+
/**
|
|
3922
|
+
* Plugin repository server - serves registry data with queries
|
|
3923
|
+
*/
|
|
3924
|
+
class PluginRepoServer {
|
|
3925
|
+
constructor(registry) {
|
|
3926
|
+
this.registry = registry;
|
|
3927
|
+
this.validateRegistry();
|
|
3928
|
+
}
|
|
3929
|
+
|
|
3930
|
+
/**
|
|
3931
|
+
* Validate registry schema
|
|
3932
|
+
*/
|
|
3933
|
+
validateRegistry() {
|
|
3934
|
+
if (!this.registry) {
|
|
3935
|
+
throw new Error('Registry is required');
|
|
3936
|
+
}
|
|
3937
|
+
if (this.registry.schemaVersion !== '3.0') {
|
|
3938
|
+
throw new Error(`Unsupported registry schema version: ${this.registry.schemaVersion}. Required: 3.0`);
|
|
3939
|
+
}
|
|
3940
|
+
if (!this.registry.plugins || typeof this.registry.plugins !== 'object') {
|
|
3941
|
+
throw new Error('Registry must have plugins object');
|
|
3942
|
+
}
|
|
3943
|
+
}
|
|
3944
|
+
|
|
3945
|
+
/**
|
|
3946
|
+
* Validate version data has all required fields
|
|
3947
|
+
*/
|
|
3948
|
+
validateVersionData(id, version, versionData) {
|
|
3949
|
+
if (!versionData.platform) {
|
|
3950
|
+
throw new Error(`Plugin ${id} v${version}: missing required field 'platform'`);
|
|
3951
|
+
}
|
|
3952
|
+
if (!versionData.package || !versionData.package.name) {
|
|
3953
|
+
throw new Error(`Plugin ${id} v${version}: missing required field 'package'`);
|
|
3954
|
+
}
|
|
3955
|
+
if (!versionData.binary || !versionData.binary.name) {
|
|
3956
|
+
throw new Error(`Plugin ${id} v${version}: missing required field 'binary'`);
|
|
3957
|
+
}
|
|
3958
|
+
}
|
|
3959
|
+
|
|
3960
|
+
/**
|
|
3961
|
+
* Compare version strings
|
|
3962
|
+
*/
|
|
3963
|
+
compareVersions(a, b) {
|
|
3964
|
+
const partsA = a.split('.').map(x => parseInt(x) || 0);
|
|
3965
|
+
const partsB = b.split('.').map(x => parseInt(x) || 0);
|
|
3966
|
+
|
|
3967
|
+
for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
|
|
3968
|
+
const numA = partsA[i] || 0;
|
|
3969
|
+
const numB = partsB[i] || 0;
|
|
3970
|
+
if (numA !== numB) {
|
|
3971
|
+
return numA - numB;
|
|
3972
|
+
}
|
|
3973
|
+
}
|
|
3974
|
+
return 0;
|
|
3975
|
+
}
|
|
3976
|
+
|
|
3977
|
+
/**
|
|
3978
|
+
* Build changelog map from versions
|
|
3979
|
+
*/
|
|
3980
|
+
buildChangelogMap(versions) {
|
|
3981
|
+
const changelog = {};
|
|
3982
|
+
for (const [version, versionData] of Object.entries(versions)) {
|
|
3983
|
+
if (versionData.changelog && Array.isArray(versionData.changelog)) {
|
|
3984
|
+
changelog[version] = versionData.changelog;
|
|
3985
|
+
}
|
|
3986
|
+
}
|
|
3987
|
+
return changelog;
|
|
3988
|
+
}
|
|
3989
|
+
|
|
3990
|
+
/**
|
|
3991
|
+
* Transform registry to flat plugin array
|
|
3992
|
+
*/
|
|
3993
|
+
transformToPluginArray() {
|
|
3994
|
+
const pluginsObject = this.registry.plugins || {};
|
|
3995
|
+
const plugins = [];
|
|
3996
|
+
|
|
3997
|
+
for (const [id, plugin] of Object.entries(pluginsObject)) {
|
|
3998
|
+
const latestVersion = plugin.latestVersion;
|
|
3999
|
+
const versionData = plugin.versions[latestVersion];
|
|
4000
|
+
|
|
4001
|
+
if (!versionData) {
|
|
4002
|
+
throw new Error(`Plugin ${id}: latest version ${latestVersion} not found in versions`);
|
|
4003
|
+
}
|
|
4004
|
+
|
|
4005
|
+
// Validate required fields - fail hard
|
|
4006
|
+
this.validateVersionData(id, latestVersion, versionData);
|
|
4007
|
+
|
|
4008
|
+
// Get all version numbers sorted descending
|
|
4009
|
+
const availableVersions = Object.keys(plugin.versions).sort((a, b) => {
|
|
4010
|
+
return this.compareVersions(b, a);
|
|
4011
|
+
});
|
|
4012
|
+
|
|
4013
|
+
// Build flat plugin object with latest version data
|
|
4014
|
+
const packageUrl = `https://filegrind.com/plugins/packages/${versionData.package.name}`;
|
|
4015
|
+
plugins.push({
|
|
4016
|
+
id,
|
|
4017
|
+
name: plugin.name,
|
|
4018
|
+
version: latestVersion,
|
|
4019
|
+
description: plugin.description,
|
|
4020
|
+
author: plugin.author,
|
|
4021
|
+
pageUrl: plugin.pageUrl || packageUrl,
|
|
4022
|
+
teamId: plugin.teamId,
|
|
4023
|
+
signedAt: versionData.releaseDate,
|
|
4024
|
+
minAppVersion: versionData.minAppVersion || plugin.minAppVersion,
|
|
4025
|
+
caps: plugin.caps || [],
|
|
4026
|
+
categories: plugin.categories,
|
|
4027
|
+
tags: plugin.tags,
|
|
4028
|
+
changelog: this.buildChangelogMap(plugin.versions),
|
|
4029
|
+
// Distribution fields - ALL REQUIRED
|
|
4030
|
+
platform: versionData.platform,
|
|
4031
|
+
packageName: versionData.package.name,
|
|
4032
|
+
packageSha256: versionData.package.sha256,
|
|
4033
|
+
packageSize: versionData.package.size,
|
|
4034
|
+
binaryName: versionData.binary.name,
|
|
4035
|
+
binarySha256: versionData.binary.sha256,
|
|
4036
|
+
binarySize: versionData.binary.size,
|
|
4037
|
+
// All available versions
|
|
4038
|
+
availableVersions
|
|
4039
|
+
});
|
|
4040
|
+
}
|
|
4041
|
+
|
|
4042
|
+
return plugins;
|
|
4043
|
+
}
|
|
4044
|
+
|
|
4045
|
+
/**
|
|
4046
|
+
* Get all plugins (API response format)
|
|
4047
|
+
*/
|
|
4048
|
+
getPlugins() {
|
|
4049
|
+
return {
|
|
4050
|
+
plugins: this.transformToPluginArray()
|
|
4051
|
+
};
|
|
4052
|
+
}
|
|
4053
|
+
|
|
4054
|
+
/**
|
|
4055
|
+
* Get plugin by ID
|
|
4056
|
+
*/
|
|
4057
|
+
getPluginById(id) {
|
|
4058
|
+
const plugins = this.transformToPluginArray();
|
|
4059
|
+
return plugins.find(p => p.id === id);
|
|
4060
|
+
}
|
|
4061
|
+
|
|
4062
|
+
/**
|
|
4063
|
+
* Search plugins by query
|
|
4064
|
+
*/
|
|
4065
|
+
searchPlugins(query) {
|
|
4066
|
+
const plugins = this.transformToPluginArray();
|
|
4067
|
+
const lowerQuery = query.toLowerCase();
|
|
4068
|
+
|
|
4069
|
+
return plugins.filter(p =>
|
|
4070
|
+
p.name.toLowerCase().includes(lowerQuery) ||
|
|
4071
|
+
p.description.toLowerCase().includes(lowerQuery) ||
|
|
4072
|
+
p.tags.some(t => t.toLowerCase().includes(lowerQuery)) ||
|
|
4073
|
+
p.caps.some(c => c.urn.toLowerCase().includes(lowerQuery) || c.title.toLowerCase().includes(lowerQuery))
|
|
4074
|
+
);
|
|
4075
|
+
}
|
|
4076
|
+
|
|
4077
|
+
/**
|
|
4078
|
+
* Get plugins by category
|
|
4079
|
+
*/
|
|
4080
|
+
getPluginsByCategory(category) {
|
|
4081
|
+
const plugins = this.transformToPluginArray();
|
|
4082
|
+
return plugins.filter(p => p.categories.includes(category));
|
|
4083
|
+
}
|
|
4084
|
+
|
|
4085
|
+
/**
|
|
4086
|
+
* Get plugins that provide a specific cap
|
|
4087
|
+
*/
|
|
4088
|
+
getPluginsByCap(capUrn) {
|
|
4089
|
+
const plugins = this.transformToPluginArray();
|
|
4090
|
+
return plugins.filter(p => p.caps.some(c => c.urn === capUrn));
|
|
4091
|
+
}
|
|
4092
|
+
}
|
|
4093
|
+
|
|
3660
4094
|
// Export for CommonJS
|
|
3661
4095
|
module.exports = {
|
|
3662
4096
|
CapUrn,
|
|
@@ -3756,5 +4190,12 @@ module.exports = {
|
|
|
3756
4190
|
CapGraphStats,
|
|
3757
4191
|
CapGraph,
|
|
3758
4192
|
StdinSource,
|
|
3759
|
-
StdinSourceKind
|
|
4193
|
+
StdinSourceKind,
|
|
4194
|
+
// Plugin Repository
|
|
4195
|
+
PluginCapSummary,
|
|
4196
|
+
PluginInfo,
|
|
4197
|
+
PluginSuggestion,
|
|
4198
|
+
PluginRepoCache,
|
|
4199
|
+
PluginRepoClient,
|
|
4200
|
+
PluginRepoServer
|
|
3760
4201
|
};
|
package/capns.test.js
CHANGED
|
@@ -8,6 +8,7 @@ const {
|
|
|
8
8
|
Cap, MediaSpec, MediaSpecError, MediaSpecErrorCodes,
|
|
9
9
|
resolveMediaUrn, buildExtensionIndex, mediaUrnsForExtension, getExtensionMappings,
|
|
10
10
|
CapMatrixError, CapMatrix, BestCapSetMatch, CompositeCapSet, CapBlock,
|
|
11
|
+
PluginInfo, PluginCapSummary, PluginSuggestion, PluginRepoClient, PluginRepoServer,
|
|
11
12
|
CapGraphEdge, CapGraphStats, CapGraph,
|
|
12
13
|
StdinSource, StdinSourceKind,
|
|
13
14
|
validateNoMediaSpecRedefinitionSync,
|
|
@@ -1903,6 +1904,361 @@ function testJS_mediaSpecConstruction() {
|
|
|
1903
1904
|
assertEqual(spec2.profile, null, 'Should have null profile');
|
|
1904
1905
|
}
|
|
1905
1906
|
|
|
1907
|
+
// =============================================================================
|
|
1908
|
+
// Plugin Repository Tests (TEST320-TEST335)
|
|
1909
|
+
// =============================================================================
|
|
1910
|
+
|
|
1911
|
+
// Sample registry for testing
|
|
1912
|
+
const sampleRegistry = {
|
|
1913
|
+
schemaVersion: '3.0',
|
|
1914
|
+
lastUpdated: '2026-02-07T16:48:28Z',
|
|
1915
|
+
plugins: {
|
|
1916
|
+
pdfcartridge: {
|
|
1917
|
+
name: 'pdfcartridge',
|
|
1918
|
+
description: 'PDF document processor',
|
|
1919
|
+
author: 'test-author',
|
|
1920
|
+
pageUrl: 'https://example.com/pdf',
|
|
1921
|
+
teamId: 'P336JK947M',
|
|
1922
|
+
minAppVersion: '1.0.0',
|
|
1923
|
+
categories: ['document'],
|
|
1924
|
+
tags: ['pdf', 'extractor'],
|
|
1925
|
+
caps: [
|
|
1926
|
+
{
|
|
1927
|
+
urn: 'cap:in="media:pdf;bytes";op=disbind;out="media:disbound-page;textable;form=list"',
|
|
1928
|
+
title: 'Disbind PDF',
|
|
1929
|
+
description: 'Extract pages'
|
|
1930
|
+
},
|
|
1931
|
+
{
|
|
1932
|
+
urn: 'cap:in="media:pdf;bytes";op=extract_metadata;out="media:file-metadata;textable;form=map"',
|
|
1933
|
+
title: 'Extract Metadata',
|
|
1934
|
+
description: 'Get PDF metadata'
|
|
1935
|
+
}
|
|
1936
|
+
],
|
|
1937
|
+
latestVersion: '0.81.5325',
|
|
1938
|
+
versions: {
|
|
1939
|
+
'0.81.5325': {
|
|
1940
|
+
releaseDate: '2026-02-07T16:40:28Z',
|
|
1941
|
+
changelog: ['Initial release'],
|
|
1942
|
+
minAppVersion: '1.0.0',
|
|
1943
|
+
platform: 'darwin-arm64',
|
|
1944
|
+
package: {
|
|
1945
|
+
name: 'pdfcartridge-0.81.5325.pkg',
|
|
1946
|
+
sha256: '9b68724eb9220ecf01e8ed4f5f80c594fbac2239bc5bf675005ec882ecc5eba0',
|
|
1947
|
+
size: 5187485
|
|
1948
|
+
},
|
|
1949
|
+
binary: {
|
|
1950
|
+
name: 'pdfcartridge-0.81.5325-darwin-arm64',
|
|
1951
|
+
sha256: '908187ec35632758f1a00452ff4755ba01020ea288619098b6998d5d33851d19',
|
|
1952
|
+
size: 12980288
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
},
|
|
1957
|
+
txtcartridge: {
|
|
1958
|
+
name: 'txtcartridge',
|
|
1959
|
+
description: 'Text file processor',
|
|
1960
|
+
author: 'test-author',
|
|
1961
|
+
pageUrl: 'https://example.com/txt',
|
|
1962
|
+
teamId: 'P336JK947M',
|
|
1963
|
+
minAppVersion: '1.0.0',
|
|
1964
|
+
categories: ['text'],
|
|
1965
|
+
tags: ['txt', 'text'],
|
|
1966
|
+
caps: [
|
|
1967
|
+
{
|
|
1968
|
+
urn: 'cap:in="media:txt;textable";op=disbind;out="media:disbound-page;textable;form=list"',
|
|
1969
|
+
title: 'Disbind Text',
|
|
1970
|
+
description: 'Extract text pages'
|
|
1971
|
+
}
|
|
1972
|
+
],
|
|
1973
|
+
latestVersion: '0.54.6408',
|
|
1974
|
+
versions: {
|
|
1975
|
+
'0.54.6408': {
|
|
1976
|
+
releaseDate: '2026-02-07T17:44:00Z',
|
|
1977
|
+
changelog: ['First version'],
|
|
1978
|
+
minAppVersion: '1.0.0',
|
|
1979
|
+
platform: 'darwin-arm64',
|
|
1980
|
+
package: {
|
|
1981
|
+
name: 'txtcartridge-0.54.6408.pkg',
|
|
1982
|
+
sha256: 'abc123',
|
|
1983
|
+
size: 821000
|
|
1984
|
+
},
|
|
1985
|
+
binary: {
|
|
1986
|
+
name: 'txtcartridge-0.54.6408-darwin-arm64',
|
|
1987
|
+
sha256: 'def456',
|
|
1988
|
+
size: 1700000
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
};
|
|
1995
|
+
|
|
1996
|
+
// TEST320: Plugin info construction
|
|
1997
|
+
function test320_pluginInfoConstruction() {
|
|
1998
|
+
const data = {
|
|
1999
|
+
id: 'testplugin',
|
|
2000
|
+
name: 'Test Plugin',
|
|
2001
|
+
version: '1.0.0',
|
|
2002
|
+
description: 'A test',
|
|
2003
|
+
teamId: 'TEAM123',
|
|
2004
|
+
signedAt: '2026-01-01',
|
|
2005
|
+
binaryName: 'test-binary',
|
|
2006
|
+
binarySha256: 'abc123',
|
|
2007
|
+
caps: [{urn: 'cap:in="media:void";op=test;out="media:void"', title: 'Test', description: ''}]
|
|
2008
|
+
};
|
|
2009
|
+
const plugin = new PluginInfo(data);
|
|
2010
|
+
assert(plugin.id === 'testplugin', 'ID should match');
|
|
2011
|
+
assert(plugin.teamId === 'TEAM123', 'Team ID should match');
|
|
2012
|
+
assert(plugin.caps.length === 1, 'Should have 1 cap');
|
|
2013
|
+
assert(plugin.caps[0].urn === 'cap:in="media:void";op=test;out="media:void"', 'Cap URN should match');
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
// TEST321: Plugin info is signed check
|
|
2017
|
+
function test321_pluginInfoIsSigned() {
|
|
2018
|
+
const signed = new PluginInfo({id: 'test', teamId: 'TEAM', signedAt: '2026-01-01', caps: []});
|
|
2019
|
+
assert(signed.isSigned() === true, 'Plugin with teamId and signedAt should be signed');
|
|
2020
|
+
|
|
2021
|
+
const unsigned1 = new PluginInfo({id: 'test', teamId: '', signedAt: '2026-01-01', caps: []});
|
|
2022
|
+
assert(unsigned1.isSigned() === false, 'Plugin without teamId should not be signed');
|
|
2023
|
+
|
|
2024
|
+
const unsigned2 = new PluginInfo({id: 'test', teamId: 'TEAM', signedAt: '', caps: []});
|
|
2025
|
+
assert(unsigned2.isSigned() === false, 'Plugin without signedAt should not be signed');
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
// TEST322: Plugin info has binary check
|
|
2029
|
+
function test322_pluginInfoHasBinary() {
|
|
2030
|
+
const withBinary = new PluginInfo({id: 'test', binaryName: 'test-bin', binarySha256: 'abc', caps: []});
|
|
2031
|
+
assert(withBinary.hasBinary() === true, 'Plugin with binary info should return true');
|
|
2032
|
+
|
|
2033
|
+
const noBinary1 = new PluginInfo({id: 'test', binaryName: '', binarySha256: 'abc', caps: []});
|
|
2034
|
+
assert(noBinary1.hasBinary() === false, 'Plugin without binaryName should return false');
|
|
2035
|
+
|
|
2036
|
+
const noBinary2 = new PluginInfo({id: 'test', binaryName: 'test', binarySha256: '', caps: []});
|
|
2037
|
+
assert(noBinary2.hasBinary() === false, 'Plugin without binarySha256 should return false');
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2040
|
+
// TEST323: PluginRepoServer validate registry
|
|
2041
|
+
function test323_pluginRepoServerValidateRegistry() {
|
|
2042
|
+
// Valid registry
|
|
2043
|
+
const server = new PluginRepoServer(sampleRegistry);
|
|
2044
|
+
assert(server.registry.schemaVersion === '3.0', 'Should accept valid registry');
|
|
2045
|
+
|
|
2046
|
+
// Invalid schema version
|
|
2047
|
+
let threw = false;
|
|
2048
|
+
try {
|
|
2049
|
+
new PluginRepoServer({schemaVersion: '2.0', plugins: {}});
|
|
2050
|
+
} catch (e) {
|
|
2051
|
+
threw = true;
|
|
2052
|
+
assert(e.message.includes('schema version'), 'Should reject wrong schema version');
|
|
2053
|
+
}
|
|
2054
|
+
assert(threw, 'Should throw for invalid schema');
|
|
2055
|
+
|
|
2056
|
+
// Missing plugins
|
|
2057
|
+
threw = false;
|
|
2058
|
+
try {
|
|
2059
|
+
new PluginRepoServer({schemaVersion: '3.0'});
|
|
2060
|
+
} catch (e) {
|
|
2061
|
+
threw = true;
|
|
2062
|
+
assert(e.message.includes('plugins'), 'Should reject missing plugins');
|
|
2063
|
+
}
|
|
2064
|
+
assert(threw, 'Should throw for missing plugins');
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
// TEST324: PluginRepoServer transform to array
|
|
2068
|
+
function test324_pluginRepoServerTransformToArray() {
|
|
2069
|
+
const server = new PluginRepoServer(sampleRegistry);
|
|
2070
|
+
const plugins = server.transformToPluginArray();
|
|
2071
|
+
|
|
2072
|
+
assert(Array.isArray(plugins), 'Should return array');
|
|
2073
|
+
assert(plugins.length === 2, 'Should have 2 plugins');
|
|
2074
|
+
|
|
2075
|
+
const pdf = plugins.find(p => p.id === 'pdfcartridge');
|
|
2076
|
+
assert(pdf !== undefined, 'Should include pdfcartridge');
|
|
2077
|
+
assert(pdf.version === '0.81.5325', 'Should have latest version');
|
|
2078
|
+
assert(pdf.teamId === 'P336JK947M', 'Should have teamId');
|
|
2079
|
+
assert(pdf.signedAt === '2026-02-07T16:40:28Z', 'Should have signedAt from releaseDate');
|
|
2080
|
+
assert(pdf.binaryName === 'pdfcartridge-0.81.5325-darwin-arm64', 'Should have binary name');
|
|
2081
|
+
assert(pdf.binarySha256 === '908187ec35632758f1a00452ff4755ba01020ea288619098b6998d5d33851d19', 'Should have SHA256');
|
|
2082
|
+
assert(Array.isArray(pdf.caps), 'Should have caps array');
|
|
2083
|
+
assert(pdf.caps.length === 2, 'Should have 2 caps');
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
// TEST325: PluginRepoServer get plugins
|
|
2087
|
+
function test325_pluginRepoServerGetPlugins() {
|
|
2088
|
+
const server = new PluginRepoServer(sampleRegistry);
|
|
2089
|
+
const response = server.getPlugins();
|
|
2090
|
+
|
|
2091
|
+
assert(response.plugins !== undefined, 'Should have plugins field');
|
|
2092
|
+
assert(Array.isArray(response.plugins), 'Plugins should be array');
|
|
2093
|
+
assert(response.plugins.length === 2, 'Should have 2 plugins');
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
// TEST326: PluginRepoServer get plugin by ID
|
|
2097
|
+
function test326_pluginRepoServerGetPluginById() {
|
|
2098
|
+
const server = new PluginRepoServer(sampleRegistry);
|
|
2099
|
+
|
|
2100
|
+
const pdf = server.getPluginById('pdfcartridge');
|
|
2101
|
+
assert(pdf !== undefined, 'Should find pdfcartridge');
|
|
2102
|
+
assert(pdf.id === 'pdfcartridge', 'Should have correct ID');
|
|
2103
|
+
|
|
2104
|
+
const notFound = server.getPluginById('nonexistent');
|
|
2105
|
+
assert(notFound === undefined, 'Should return undefined for missing plugin');
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
// TEST327: PluginRepoServer search plugins
|
|
2109
|
+
function test327_pluginRepoServerSearchPlugins() {
|
|
2110
|
+
const server = new PluginRepoServer(sampleRegistry);
|
|
2111
|
+
|
|
2112
|
+
const pdfResults = server.searchPlugins('pdf');
|
|
2113
|
+
assert(pdfResults.length === 1, 'Should find 1 PDF plugin');
|
|
2114
|
+
assert(pdfResults[0].id === 'pdfcartridge', 'Should find pdfcartridge');
|
|
2115
|
+
|
|
2116
|
+
const metadataResults = server.searchPlugins('metadata');
|
|
2117
|
+
assert(metadataResults.length === 1, 'Should find plugin by cap title');
|
|
2118
|
+
|
|
2119
|
+
const noResults = server.searchPlugins('nonexistent');
|
|
2120
|
+
assert(noResults.length === 0, 'Should return empty for no matches');
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
// TEST328: PluginRepoServer get by category
|
|
2124
|
+
function test328_pluginRepoServerGetByCategory() {
|
|
2125
|
+
const server = new PluginRepoServer(sampleRegistry);
|
|
2126
|
+
|
|
2127
|
+
const docPlugins = server.getPluginsByCategory('document');
|
|
2128
|
+
assert(docPlugins.length === 1, 'Should find 1 document plugin');
|
|
2129
|
+
assert(docPlugins[0].id === 'pdfcartridge', 'Should be pdfcartridge');
|
|
2130
|
+
|
|
2131
|
+
const textPlugins = server.getPluginsByCategory('text');
|
|
2132
|
+
assert(textPlugins.length === 1, 'Should find 1 text plugin');
|
|
2133
|
+
assert(textPlugins[0].id === 'txtcartridge', 'Should be txtcartridge');
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
// TEST329: PluginRepoServer get by cap
|
|
2137
|
+
function test329_pluginRepoServerGetByCap() {
|
|
2138
|
+
const server = new PluginRepoServer(sampleRegistry);
|
|
2139
|
+
|
|
2140
|
+
const disbindCap = 'cap:in="media:pdf;bytes";op=disbind;out="media:disbound-page;textable;form=list"';
|
|
2141
|
+
const plugins = server.getPluginsByCap(disbindCap);
|
|
2142
|
+
|
|
2143
|
+
assert(plugins.length === 1, 'Should find 1 plugin with this cap');
|
|
2144
|
+
assert(plugins[0].id === 'pdfcartridge', 'Should be pdfcartridge');
|
|
2145
|
+
|
|
2146
|
+
const metadataCap = 'cap:in="media:pdf;bytes";op=extract_metadata;out="media:file-metadata;textable;form=map"';
|
|
2147
|
+
const metadataPlugins = server.getPluginsByCap(metadataCap);
|
|
2148
|
+
assert(metadataPlugins.length === 1, 'Should find metadata cap');
|
|
2149
|
+
}
|
|
2150
|
+
|
|
2151
|
+
// TEST330: PluginRepoClient update cache
|
|
2152
|
+
function test330_pluginRepoClientUpdateCache() {
|
|
2153
|
+
const client = new PluginRepoClient(3600);
|
|
2154
|
+
const server = new PluginRepoServer(sampleRegistry);
|
|
2155
|
+
const plugins = server.transformToPluginArray().map(p => new PluginInfo(p));
|
|
2156
|
+
|
|
2157
|
+
client.updateCache('https://example.com/api/plugins', plugins);
|
|
2158
|
+
|
|
2159
|
+
const cache = client.caches.get('https://example.com/api/plugins');
|
|
2160
|
+
assert(cache !== undefined, 'Cache should exist');
|
|
2161
|
+
assert(cache.plugins.size === 2, 'Should have 2 plugins in cache');
|
|
2162
|
+
assert(cache.capToPlugins.size > 0, 'Should have cap mappings');
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2165
|
+
// TEST331: PluginRepoClient get suggestions
|
|
2166
|
+
function test331_pluginRepoClientGetSuggestions() {
|
|
2167
|
+
const client = new PluginRepoClient(3600);
|
|
2168
|
+
const server = new PluginRepoServer(sampleRegistry);
|
|
2169
|
+
const plugins = server.transformToPluginArray().map(p => new PluginInfo(p));
|
|
2170
|
+
|
|
2171
|
+
client.updateCache('https://example.com/api/plugins', plugins);
|
|
2172
|
+
|
|
2173
|
+
const disbindCap = 'cap:in="media:pdf;bytes";op=disbind;out="media:disbound-page;textable;form=list"';
|
|
2174
|
+
const suggestions = client.getSuggestionsForCap(disbindCap);
|
|
2175
|
+
|
|
2176
|
+
assert(suggestions.length === 1, 'Should find 1 suggestion');
|
|
2177
|
+
assert(suggestions[0].pluginId === 'pdfcartridge', 'Should suggest pdfcartridge');
|
|
2178
|
+
assert(suggestions[0].capUrn === disbindCap, 'Should have correct cap URN');
|
|
2179
|
+
assert(suggestions[0].capTitle === 'Disbind PDF', 'Should have cap title');
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
// TEST332: PluginRepoClient get plugin
|
|
2183
|
+
function test332_pluginRepoClientGetPlugin() {
|
|
2184
|
+
const client = new PluginRepoClient(3600);
|
|
2185
|
+
const server = new PluginRepoServer(sampleRegistry);
|
|
2186
|
+
const plugins = server.transformToPluginArray().map(p => new PluginInfo(p));
|
|
2187
|
+
|
|
2188
|
+
client.updateCache('https://example.com/api/plugins', plugins);
|
|
2189
|
+
|
|
2190
|
+
const plugin = client.getPlugin('pdfcartridge');
|
|
2191
|
+
assert(plugin !== null, 'Should find plugin');
|
|
2192
|
+
assert(plugin.id === 'pdfcartridge', 'Should have correct ID');
|
|
2193
|
+
|
|
2194
|
+
const notFound = client.getPlugin('nonexistent');
|
|
2195
|
+
assert(notFound === null, 'Should return null for missing plugin');
|
|
2196
|
+
}
|
|
2197
|
+
|
|
2198
|
+
// TEST333: PluginRepoClient get all caps
|
|
2199
|
+
function test333_pluginRepoClientGetAllCaps() {
|
|
2200
|
+
const client = new PluginRepoClient(3600);
|
|
2201
|
+
const server = new PluginRepoServer(sampleRegistry);
|
|
2202
|
+
const plugins = server.transformToPluginArray().map(p => new PluginInfo(p));
|
|
2203
|
+
|
|
2204
|
+
client.updateCache('https://example.com/api/plugins', plugins);
|
|
2205
|
+
|
|
2206
|
+
const caps = client.getAllAvailableCaps();
|
|
2207
|
+
assert(Array.isArray(caps), 'Should return array');
|
|
2208
|
+
assert(caps.length === 3, 'Should have 3 unique caps');
|
|
2209
|
+
assert(caps.every(c => typeof c === 'string'), 'All caps should be strings');
|
|
2210
|
+
}
|
|
2211
|
+
|
|
2212
|
+
// TEST334: PluginRepoClient needs sync
|
|
2213
|
+
function test334_pluginRepoClientNeedsSync() {
|
|
2214
|
+
const client = new PluginRepoClient(1); // 1 second TTL
|
|
2215
|
+
const server = new PluginRepoServer(sampleRegistry);
|
|
2216
|
+
const plugins = server.transformToPluginArray().map(p => new PluginInfo(p));
|
|
2217
|
+
|
|
2218
|
+
const urls = ['https://example.com/api/plugins'];
|
|
2219
|
+
|
|
2220
|
+
// Should need sync initially
|
|
2221
|
+
assert(client.needsSync(urls) === true, 'Should need sync with empty cache');
|
|
2222
|
+
|
|
2223
|
+
// Update cache
|
|
2224
|
+
client.updateCache(urls[0], plugins);
|
|
2225
|
+
|
|
2226
|
+
// Should not need sync immediately
|
|
2227
|
+
assert(client.needsSync(urls) === false, 'Should not need sync right after update');
|
|
2228
|
+
|
|
2229
|
+
// Wait for cache to expire (1 second)
|
|
2230
|
+
// Note: Can't test this synchronously, would need async test
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2233
|
+
// TEST335: PluginRepoServer and Client integration
|
|
2234
|
+
function test335_pluginRepoServerClientIntegration() {
|
|
2235
|
+
// Server creates API response
|
|
2236
|
+
const server = new PluginRepoServer(sampleRegistry);
|
|
2237
|
+
const apiResponse = server.getPlugins();
|
|
2238
|
+
|
|
2239
|
+
// Client consumes API response
|
|
2240
|
+
const client = new PluginRepoClient(3600);
|
|
2241
|
+
const plugins = apiResponse.plugins.map(p => new PluginInfo(p));
|
|
2242
|
+
client.updateCache('https://example.com/api/plugins', plugins);
|
|
2243
|
+
|
|
2244
|
+
// Client can find plugin
|
|
2245
|
+
const plugin = client.getPlugin('pdfcartridge');
|
|
2246
|
+
assert(plugin !== null, 'Client should find plugin from server data');
|
|
2247
|
+
assert(plugin.isSigned(), 'Plugin should be signed');
|
|
2248
|
+
assert(plugin.hasBinary(), 'Plugin should have binary');
|
|
2249
|
+
|
|
2250
|
+
// Client can get suggestions
|
|
2251
|
+
const capUrn = 'cap:in="media:pdf;bytes";op=disbind;out="media:disbound-page;textable;form=list"';
|
|
2252
|
+
const suggestions = client.getSuggestionsForCap(capUrn);
|
|
2253
|
+
assert(suggestions.length === 1, 'Should get suggestions');
|
|
2254
|
+
assert(suggestions[0].pluginId === 'pdfcartridge', 'Should suggest correct plugin');
|
|
2255
|
+
|
|
2256
|
+
// Server can search
|
|
2257
|
+
const searchResults = server.searchPlugins('pdf');
|
|
2258
|
+
assert(searchResults.length === 1, 'Server search should work');
|
|
2259
|
+
assert(searchResults[0].id === plugin.id, 'Search and client should agree');
|
|
2260
|
+
}
|
|
2261
|
+
|
|
1906
2262
|
// ============================================================================
|
|
1907
2263
|
// Test runner
|
|
1908
2264
|
// ============================================================================
|
|
@@ -2078,6 +2434,25 @@ async function runTests() {
|
|
|
2078
2434
|
if (p2) await p2;
|
|
2079
2435
|
runTest('JS: media_spec_construction', testJS_mediaSpecConstruction);
|
|
2080
2436
|
|
|
2437
|
+
// plugin_repo: PluginRepoServer and PluginRepoClient tests
|
|
2438
|
+
console.log('\n--- plugin_repo ---');
|
|
2439
|
+
runTest('TEST320: plugin_info_construction', test320_pluginInfoConstruction);
|
|
2440
|
+
runTest('TEST321: plugin_info_is_signed', test321_pluginInfoIsSigned);
|
|
2441
|
+
runTest('TEST322: plugin_info_has_binary', test322_pluginInfoHasBinary);
|
|
2442
|
+
runTest('TEST323: plugin_repo_server_validate_registry', test323_pluginRepoServerValidateRegistry);
|
|
2443
|
+
runTest('TEST324: plugin_repo_server_transform_to_array', test324_pluginRepoServerTransformToArray);
|
|
2444
|
+
runTest('TEST325: plugin_repo_server_get_plugins', test325_pluginRepoServerGetPlugins);
|
|
2445
|
+
runTest('TEST326: plugin_repo_server_get_plugin_by_id', test326_pluginRepoServerGetPluginById);
|
|
2446
|
+
runTest('TEST327: plugin_repo_server_search_plugins', test327_pluginRepoServerSearchPlugins);
|
|
2447
|
+
runTest('TEST328: plugin_repo_server_get_by_category', test328_pluginRepoServerGetByCategory);
|
|
2448
|
+
runTest('TEST329: plugin_repo_server_get_by_cap', test329_pluginRepoServerGetByCap);
|
|
2449
|
+
runTest('TEST330: plugin_repo_client_update_cache', test330_pluginRepoClientUpdateCache);
|
|
2450
|
+
runTest('TEST331: plugin_repo_client_get_suggestions', test331_pluginRepoClientGetSuggestions);
|
|
2451
|
+
runTest('TEST332: plugin_repo_client_get_plugin', test332_pluginRepoClientGetPlugin);
|
|
2452
|
+
runTest('TEST333: plugin_repo_client_get_all_caps', test333_pluginRepoClientGetAllCaps);
|
|
2453
|
+
runTest('TEST334: plugin_repo_client_needs_sync', test334_pluginRepoClientNeedsSync);
|
|
2454
|
+
runTest('TEST335: plugin_repo_server_client_integration', test335_pluginRepoServerClientIntegration);
|
|
2455
|
+
|
|
2081
2456
|
// Summary
|
|
2082
2457
|
console.log(`\n${passCount + failCount} tests: ${passCount} passed, ${failCount} failed`);
|
|
2083
2458
|
if (failCount > 0) {
|
package/package.json
CHANGED