@stolyarovmn/dsh-ui-registry-aggregator 0.5.0 → 0.5.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 +7 -7
- package/client.js +11 -2
- package/package.json +1 -1
- package/source-core.js +86 -10
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
This branch is the clean DSH `0.2.0` implementation line. It does **not** carry compatibility code or UI workarounds from DSH `0.1.x`.
|
|
4
4
|
|
|
5
|
-
Current stable version: `0.5.
|
|
5
|
+
Current stable version: `0.5.1`
|
|
6
6
|
Current validation baseline: DSH `v0.2.0-rc.2`. Compatibility checks use the actual running DSH version from the active installation manifest rather than a hardcoded RC.
|
|
7
7
|
|
|
8
8
|
## Install from npm
|
|
@@ -12,7 +12,7 @@ $DSH_VERSION = "0.2.0-rc.2"
|
|
|
12
12
|
$PROFILE = "dsh-020-test"
|
|
13
13
|
$env:DSH_HOME = "$env:USERPROFILE\.dsh-020-test"
|
|
14
14
|
|
|
15
|
-
pnpm dlx "@deepseek-ai/dsh@$DSH_VERSION" plugin --profile $PROFILE add "@stolyarovmn/dsh-ui-registry-aggregator@0.5.
|
|
15
|
+
pnpm dlx "@deepseek-ai/dsh@$DSH_VERSION" plugin --profile $PROFILE add "@stolyarovmn/dsh-ui-registry-aggregator@0.5.1"
|
|
16
16
|
pnpm dlx "@deepseek-ai/dsh@$DSH_VERSION" --profile $PROFILE
|
|
17
17
|
```
|
|
18
18
|
|
|
@@ -40,7 +40,7 @@ Implemented in this milestone:
|
|
|
40
40
|
- combinable multi-sort criteria for relevance, stars, downloads, freshness, and name with per-criterion direction; when several are active they contribute equally through percentile-normalized composite ranking, so every selected criterion can affect the order;
|
|
41
41
|
- npm 30-day download enrichment plus lazy paired `30d | total` stats for visible npm rows; lifetime totals are summed from the package creation date (or npm's 2015-01-10 data floor) in bounded windows, and the UI never presents a partial lifetime sum as a real total;
|
|
42
42
|
- package freshness distinguishes npm release time from GitHub repository push time, so GitHub metadata churn does not make every item look newly released;
|
|
43
|
-
- best-effort package artwork discovery
|
|
43
|
+
- best-effort package artwork discovery prefers the official DSH top-level manifest `icon`; when it is absent, Browse can recover the GitHub repository from npm manifest metadata and lazily use a safe repository-local README logo/icon image. Installed Updates reuse the native Plugin Manager `BundleInfo.meta.icon`. All fetched artwork remains limited to SVG/PNG/JPEG/WebP and 256 KiB;
|
|
44
44
|
- popular/default Browse results when the query is empty;
|
|
45
45
|
- real npm/GitHub source marks and a package icon exposed through the DSH 0.2 manifest contract;
|
|
46
46
|
- horizontal layout containment for narrow Plugin Manager detail panes;
|
|
@@ -48,11 +48,11 @@ Implemented in this milestone:
|
|
|
48
48
|
- icon-only Browse install states using the same local DSH-style glyph language as the rest of the page;
|
|
49
49
|
- explicit `v<version>` package versions plus lazy manifest evidence for GitHub/npm rows;
|
|
50
50
|
- compatibility evidence against the actual running DSH version, following DSH peer semantics for `@deepseek-ai/dsh` and `@deepseek-ai/dsh-*`; Browse shows `Compatible`, `Incompatible`, or neutral `Not verified` as plain colored text, and can filter by compatibility;
|
|
51
|
-
- live Updates discovery from native installed bundles plus latest npm manifests, with per-package update through `remote.pluginManager.installBundle`;
|
|
51
|
+
- live Updates discovery from native installed bundles plus latest npm manifests, with per-package update through `remote.pluginManager.installBundle`; package update actions use a distinct two-arrow Update glyph while content/list refresh keeps the single-arrow Refresh glyph;
|
|
52
52
|
- no runtime import of Harness Client implementation packages;
|
|
53
53
|
- Harness-provided React and DSH theme tokens.
|
|
54
54
|
|
|
55
|
-
Known limitations in `0.5.
|
|
55
|
+
Known limitations in `0.5.1`:
|
|
56
56
|
|
|
57
57
|
- build-script approval UI remains in the native **Add plugin** dialog;
|
|
58
58
|
- bulk **Update all** / cancel orchestration is not part of the DSH 0.2 line yet;
|
|
@@ -81,7 +81,7 @@ Sources UI
|
|
|
81
81
|
├─ custom-json
|
|
82
82
|
└─ corporate
|
|
83
83
|
|
|
84
|
-
Browse Install
|
|
84
|
+
Browse Install
|
|
85
85
|
└─ native DSH remote.pluginManager
|
|
86
86
|
├─ inspect(spec)
|
|
87
87
|
├─ installBundle(spec)
|
|
@@ -105,7 +105,7 @@ pnpm dlx "@deepseek-ai/dsh@$DSH_VERSION" plugin --profile $PROFILE add "github:S
|
|
|
105
105
|
pnpm dlx "@deepseek-ai/dsh@$DSH_VERSION" --profile $PROFILE
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
-
Then open **Plugins → Installed → Registry Aggregator
|
|
108
|
+
Then open **Plugins → Installed → Registry Aggregator → Sources** and verify source state in the real Harness UI.
|
|
109
109
|
|
|
110
110
|
## Historical lines
|
|
111
111
|
|
package/client.js
CHANGED
|
@@ -615,6 +615,15 @@ window.__ModuleLoader__.load({
|
|
|
615
615
|
] })
|
|
616
616
|
}
|
|
617
617
|
|
|
618
|
+
function IconUpdate({ size = 16 }) {
|
|
619
|
+
return h(SvgIcon, { size, children: [
|
|
620
|
+
h('path', { d: 'M13.5 5.5C12.6 3.2 10.5 1.75 8 1.75C5.9 1.75 4.05 2.75 2.9 4.3L1.75 5.75', stroke: 'currentColor' }),
|
|
621
|
+
h('path', { d: 'M1.75 2.75V5.75H4.75', stroke: 'currentColor' }),
|
|
622
|
+
h('path', { d: 'M2.5 10.5C3.4 12.8 5.5 14.25 8 14.25C10.1 14.25 11.95 13.25 13.1 11.7L14.25 10.25', stroke: 'currentColor' }),
|
|
623
|
+
h('path', { d: 'M14.25 13.25V10.25H11.25', stroke: 'currentColor' }),
|
|
624
|
+
] })
|
|
625
|
+
}
|
|
626
|
+
|
|
618
627
|
function IconPlus({ size = 16 }) {
|
|
619
628
|
return h(SvgIcon, { size, children: [
|
|
620
629
|
h('path', { d: 'M8 2V14', stroke: 'currentColor' }),
|
|
@@ -1775,7 +1784,7 @@ window.__ModuleLoader__.load({
|
|
|
1775
1784
|
? t('updateRetry')
|
|
1776
1785
|
: format(t, 'updateAction', { version: item.availableVersion })
|
|
1777
1786
|
return h('li', { key, className: 'ra-update-row' },
|
|
1778
|
-
h('span', { className: 'ra-plugin-icon', 'aria-hidden': true }, h(
|
|
1787
|
+
h('span', { className: 'ra-plugin-icon', 'aria-hidden': true }, h(PluginArtwork, { src: item.bundle?.meta?.icon })),
|
|
1779
1788
|
h('div', { className: 'ra-update-main' },
|
|
1780
1789
|
h('div', { className: 'ra-update-title' },
|
|
1781
1790
|
h('span', null, key),
|
|
@@ -1796,7 +1805,7 @@ window.__ModuleLoader__.load({
|
|
|
1796
1805
|
? h(IconCheck, { size: 16 })
|
|
1797
1806
|
: busy
|
|
1798
1807
|
? h(IconRefresh, { size: 16, className: 'ra-spin' })
|
|
1799
|
-
: h(
|
|
1808
|
+
: h(IconUpdate, { size: 16 }),
|
|
1800
1809
|
}),
|
|
1801
1810
|
)
|
|
1802
1811
|
}),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stolyarovmn/dsh-ui-registry-aggregator",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Federated plugin discovery and update UI for DeepSeek Harness 0.2.x, integrated with the native Plugin Manager.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Stolyarovmn",
|
package/source-core.js
CHANGED
|
@@ -505,6 +505,13 @@ function cleanUrl(value) {
|
|
|
505
505
|
}
|
|
506
506
|
}
|
|
507
507
|
|
|
508
|
+
function manifestRepositoryUrl(manifest) {
|
|
509
|
+
const repository = manifest?.repository
|
|
510
|
+
if (typeof repository === 'string') return cleanUrl(repository)
|
|
511
|
+
if (repository && typeof repository === 'object' && !Array.isArray(repository)) return cleanUrl(repository.url)
|
|
512
|
+
return undefined
|
|
513
|
+
}
|
|
514
|
+
|
|
508
515
|
function npmPlugin(entry, source) {
|
|
509
516
|
const pkg = entry?.package
|
|
510
517
|
const name = text(pkg?.name)
|
|
@@ -722,6 +729,41 @@ function safeIconPath(value) {
|
|
|
722
729
|
return { path: clean, mediaType }
|
|
723
730
|
}
|
|
724
731
|
|
|
732
|
+
function readmeIconPath(markdown) {
|
|
733
|
+
const source = String(markdown ?? '')
|
|
734
|
+
const candidates = []
|
|
735
|
+
const add = (raw, label = '') => {
|
|
736
|
+
const candidate = text(raw)?.replace(/^<|>$/g, '').split(/[?#]/u)[0]
|
|
737
|
+
const icon = safeIconPath(candidate)
|
|
738
|
+
if (!icon) return
|
|
739
|
+
const signal = (String(label) + ' ' + icon.path).toLowerCase()
|
|
740
|
+
if (!/(^|[\/_.\s-])(icon|logo|avatar|brand|mark)([\/_.\s-]|$)/u.test(signal)) return
|
|
741
|
+
const lowerPath = icon.path.toLowerCase()
|
|
742
|
+
const basename = lowerPath.split('/').pop() ?? ''
|
|
743
|
+
const score = /^(icon|logo)([._-]|$)/u.test(basename)
|
|
744
|
+
? 3
|
|
745
|
+
: /(^|\/)(assets?|images?|img|media)\//u.test(lowerPath)
|
|
746
|
+
? 2
|
|
747
|
+
: 1
|
|
748
|
+
candidates.push({ ...icon, score })
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
const htmlImage = /<img\b([^>]*?)>/giu
|
|
752
|
+
let match
|
|
753
|
+
while ((match = htmlImage.exec(source))) {
|
|
754
|
+
const attrs = match[1]
|
|
755
|
+
const src = /\bsrc\s*=\s*["']([^"']+)["']/iu.exec(attrs)?.[1]
|
|
756
|
+
const alt = /\balt\s*=\s*["']([^"']*)["']/iu.exec(attrs)?.[1] ?? ''
|
|
757
|
+
add(src, alt)
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
const markdownImage = /!\[([^\]]*)\]\(([^)\s]+)(?:\s+["'][^"']*["'])?\)/gu
|
|
761
|
+
while ((match = markdownImage.exec(source))) add(match[2], match[1])
|
|
762
|
+
|
|
763
|
+
candidates.sort((left, right) => right.score - left.score)
|
|
764
|
+
return candidates[0]
|
|
765
|
+
}
|
|
766
|
+
|
|
725
767
|
function encodedPath(path) {
|
|
726
768
|
return path.split('/').map(part => encodeURIComponent(part)).join('/')
|
|
727
769
|
}
|
|
@@ -900,8 +942,10 @@ export async function resolvePluginMetadata(items, options = {}) {
|
|
|
900
942
|
}
|
|
901
943
|
|
|
902
944
|
const compatibility = dshCompatibility(manifest, options.runtimeVersion)
|
|
945
|
+
const repository = manifestRepositoryUrl(manifest) ?? cleanUrl(item?.repository)
|
|
903
946
|
const value = {
|
|
904
947
|
...(text(manifest?.version) ? { version: text(manifest.version) } : {}),
|
|
948
|
+
...(repository ? { repository } : {}),
|
|
905
949
|
compatibility: compatibility.status,
|
|
906
950
|
...(compatibility.peers.length ? { dshPeers: compatibility.peers } : {}),
|
|
907
951
|
runtimeVersion: text(options.runtimeVersion),
|
|
@@ -917,22 +961,54 @@ async function resolveNpmIcon(item, options) {
|
|
|
917
961
|
if (!packageName || !version) return undefined
|
|
918
962
|
const manifest = await npmManifest(item, options)
|
|
919
963
|
const icon = safeIconPath(manifest?.icon)
|
|
920
|
-
if (
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
964
|
+
if (icon) {
|
|
965
|
+
const packagePath = packageName.startsWith('@')
|
|
966
|
+
? packageName.split('/').map(part => encodeURIComponent(part)).join('/')
|
|
967
|
+
: encodeURIComponent(packageName)
|
|
968
|
+
const iconUrl = 'https://unpkg.com/' + packagePath + '@' + encodeURIComponent(version) + '/' + encodedPath(icon.path)
|
|
969
|
+
return iconDataFromUrl(iconUrl, icon.mediaType, options)
|
|
970
|
+
}
|
|
971
|
+
const parts = githubRepositoryParts(manifestRepositoryUrl(manifest))
|
|
972
|
+
return parts ? githubReadmeIcon(parts, options) : undefined
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
async function githubReadmeIcon(parts, options) {
|
|
976
|
+
const source = { id: 'plugin-readme-icon', name: 'plugin README icon', type: 'github', enabled: true }
|
|
977
|
+
for (const filename of ['README.md', 'readme.md']) {
|
|
978
|
+
try {
|
|
979
|
+
const readmeUrl = 'https://raw.githubusercontent.com/' + encodeURIComponent(parts.owner) + '/' + encodeURIComponent(parts.repo) + '/HEAD/' + filename
|
|
980
|
+
const bytes = await fetchBinary(readmeUrl, source, {
|
|
981
|
+
...options,
|
|
982
|
+
authToken: undefined,
|
|
983
|
+
allowPrivateNetwork: false,
|
|
984
|
+
maxBytes: Math.min(options.maxResponseBytes ?? DEFAULT_MAX_RESPONSE_BYTES, 512 * 1024),
|
|
985
|
+
})
|
|
986
|
+
const icon = readmeIconPath(new TextDecoder().decode(bytes))
|
|
987
|
+
if (!icon) continue
|
|
988
|
+
const iconUrl = 'https://raw.githubusercontent.com/' + encodeURIComponent(parts.owner) + '/' + encodeURIComponent(parts.repo) + '/HEAD/' + encodedPath(icon.path)
|
|
989
|
+
return await iconDataFromUrl(iconUrl, icon.mediaType, options)
|
|
990
|
+
} catch {
|
|
991
|
+
// README artwork is optional evidence; try the next conventional filename.
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
return undefined
|
|
926
995
|
}
|
|
927
996
|
|
|
928
997
|
async function resolveGithubIcon(item, options) {
|
|
929
998
|
const parts = githubRepositoryParts(item?.repository)
|
|
930
999
|
if (!parts) return undefined
|
|
931
|
-
|
|
1000
|
+
let manifest
|
|
1001
|
+
try {
|
|
1002
|
+
manifest = await githubManifest(item, options)
|
|
1003
|
+
} catch {
|
|
1004
|
+
manifest = undefined
|
|
1005
|
+
}
|
|
932
1006
|
const icon = safeIconPath(manifest?.icon)
|
|
933
|
-
if (
|
|
934
|
-
|
|
935
|
-
|
|
1007
|
+
if (icon) {
|
|
1008
|
+
const iconUrl = 'https://raw.githubusercontent.com/' + encodeURIComponent(parts.owner) + '/' + encodeURIComponent(parts.repo) + '/HEAD/' + encodedPath(icon.path)
|
|
1009
|
+
return iconDataFromUrl(iconUrl, icon.mediaType, options)
|
|
1010
|
+
}
|
|
1011
|
+
return githubReadmeIcon(parts, options)
|
|
936
1012
|
}
|
|
937
1013
|
|
|
938
1014
|
export async function resolvePluginIcons(items, options = {}) {
|