Feature/fe new registry (#2647)

* bugfixes

* update fe types

* implement new registry types in marketplace and ui

* fix marketplace types to have default params

* add alt implementation toggle

* merge cleanup

* more cleanup and notes

* fix build

* cleanup sync with next/minor

* add exver JS parser

* parse ValidExVer to string

* update types to interface

* add VersionRange and comparative functions

* Parse ExtendedVersion from string

* add conjunction, disjunction, and inversion logic

* consider flavor in satisfiedBy fn

* consider prerelease for ordering

* add compare fn for sorting

* rename fns for consistency

* refactoring

* update compare fn to return null if flavors don't match

* begin simplifying dependencies

* under construction

* wip

* add dependency metadata to CurrentDependencyInfo

* ditch inheritance for recursive VersionRange constructor. Recursive 'satisfiedBy' fn wip

* preprocess manifest

* misc fixes

* use sdk version as osVersion in manifest

* chore: Change the type to just validate and not generate all solutions.

* add publishedAt

* fix pegjs exports

* integrate exver into sdk

* misc fixes

* complete satisfiedBy fn

* refactor - use greaterThanOrEqual and lessThanOrEqual fns

* fix tests

* update dependency details

* update types

* remove interim types

* rename alt implementation to flavor

* cleanup os update

* format exver.ts

* add s9pk parsing endpoints

* fix build

* update to exver

* exver and bug fixes

* update static endpoints + cleanup

* cleanup

* update static proxy verification

* make mocks more robust; fix dep icon fallback; cleanup

* refactor alert versions and update fixtures

* registry bugfixes

* misc fixes

* cleanup unused

* convert patchdb ui seed to camelCase

* update otherVersions type

* change otherVersions: null to 'none'

* refactor and complete feature

* improve static endpoints

* fix install params

* mask systemd-networkd-wait-online

* fix static file fetching

* include non-matching versions in otherVersions

* convert release notes to modal and clean up displayExver

* alert for no other versions

* Fix ack-instructions casing

* fix indeterminate loader on service install

---------

Co-authored-by: Aiden McClelland <me@drbonez.dev>
Co-authored-by: Shadowy Super Coder <musashidisciple@proton.me>
Co-authored-by: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com>
Co-authored-by: J H <dragondef@gmail.com>
Co-authored-by: Matt Hill <mattnine@protonmail.com>
This commit is contained in:
Lucy
2024-07-22 20:48:12 -04:00
committed by GitHub
parent 0fbb18b315
commit a535fc17c3
196 changed files with 7002 additions and 2162 deletions

File diff suppressed because one or more lines are too long

View File

@@ -3,11 +3,26 @@ import {
PackageDataEntry,
} from 'src/app/services/patch-db/data-model'
import { Metric, NotificationLevel, RR, ServerNotifications } from './api.types'
import { BTC_ICON, LND_ICON, PROXY_ICON } from './api-icons'
import { DependencyMetadata, MarketplacePkg } from '@start9labs/marketplace'
import { BTC_ICON, LND_ICON, PROXY_ICON, REGISTRY_ICON } from './api-icons'
import { Log } from '@start9labs/shared'
import { configBuilderToSpec } from 'src/app/util/configBuilderToSpec'
import { T, CB } from '@start9labs/start-sdk'
import { GetPackagesRes } from '@start9labs/marketplace'
const mockBlake3Commitment: T.Blake3Commitment = {
hash: 'fakehash',
size: 0,
}
const mockMerkleArchiveCommitment: T.MerkleArchiveCommitment = {
rootSighash: 'fakehash',
rootMaxsize: 0,
}
const mockDescription = {
short: 'Lorem ipsum dolor sit amet',
long: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
}
export module Mock {
export const ServerUpdated: T.ServerStatus = {
@@ -37,17 +52,42 @@ export module Mock {
},
}
export const ReleaseNotes: RR.GetReleaseNotesRes = {
'0.19.2':
'Contrary to popular belief, Lorem Ipsum is not simply random text.',
'0.19.1': 'release notes for Bitcoin 0.19.1',
'0.19.0': 'release notes for Bitcoin 0.19.0',
export const RegistryInfo: T.RegistryInfo = {
name: 'Start9 Registry',
icon: REGISTRY_ICON,
categories: {
bitcoin: {
name: 'Bitcoin',
description: mockDescription,
},
featured: {
name: 'Featured',
description: mockDescription,
},
lightning: {
name: 'Lightning',
description: mockDescription,
},
communications: {
name: 'Communications',
description: mockDescription,
},
data: {
name: 'Data',
description: mockDescription,
},
ai: {
name: 'AI',
description: mockDescription,
},
},
}
export const MockManifestBitcoind: T.Manifest = {
id: 'bitcoind',
title: 'Bitcoin Core',
version: '0.21.0',
version: '0.21.0:0',
satisfies: [],
gitHash: 'abcdefgh',
description: {
short: 'A Bitcoin full node by Bitcoin Core.',
@@ -90,7 +130,8 @@ export module Mock {
export const MockManifestLnd: T.Manifest = {
id: 'lnd',
title: 'Lightning Network Daemon',
version: '0.11.1',
version: '0.11.1:0',
satisfies: [],
gitHash: 'abcdefgh',
description: {
short: 'A bolt spec compliant client.',
@@ -116,11 +157,13 @@ export module Mock {
bitcoind: {
description: 'LND needs bitcoin to live.',
optional: true,
s9pk: '',
},
'btc-rpc-proxy': {
description:
'As long as Bitcoin is pruned, LND needs Bitcoin Proxy to fetch block over the P2P network.',
optional: true,
s9pk: '',
},
},
hasConfig: true,
@@ -143,7 +186,8 @@ export module Mock {
export const MockManifestBitcoinProxy: T.Manifest = {
id: 'btc-rpc-proxy',
title: 'Bitcoin Proxy',
version: '0.2.2',
version: '0.2.2:0',
satisfies: [],
gitHash: 'lmnopqrx',
description: {
short: 'A super charger for your Bitcoin node.',
@@ -168,6 +212,7 @@ export module Mock {
bitcoind: {
description: 'Bitcoin Proxy requires a Bitcoin node.',
optional: false,
s9pk: '',
},
},
hasConfig: false,
@@ -187,149 +232,509 @@ export module Mock {
},
}
export const BitcoinDep: DependencyMetadata = {
export const BitcoinDep: T.DependencyMetadata = {
title: 'Bitcoin Core',
icon: BTC_ICON,
optional: false,
hidden: true,
description: 'Needed to run',
}
export const ProxyDep: DependencyMetadata = {
export const ProxyDep: T.DependencyMetadata = {
title: 'Bitcoin Proxy',
icon: PROXY_ICON,
optional: true,
hidden: false,
description: 'Needed to run',
}
export const MarketplacePkgs: {
[id: string]: {
[version: string]: MarketplacePkg
}
export const OtherPackageVersions: {
[id: T.PackageId]: GetPackagesRes
} = {
bitcoind: {
'0.19.0': {
icon: BTC_ICON,
license: 'licenseUrl',
instructions: 'instructionsUrl',
manifest: {
...Mock.MockManifestBitcoind,
version: '0.19.0',
'=26.1.0:0.1.0': {
best: {
'26.1.0:0.1.0': {
title: 'Bitcoin Core',
description: mockDescription,
hardwareRequirements: { arch: null, device: {}, ram: null },
license: 'mit',
wrapperRepo: 'https://github.com/start9labs/bitcoind-startos',
upstreamRepo: 'https://github.com/bitcoin/bitcoin',
supportSite: 'https://bitcoin.org',
marketingSite: 'https://bitcoin.org',
releaseNotes: 'Even better support for Bitcoin and wallets!',
osVersion: '0.3.6',
gitHash: 'fakehash',
icon: BTC_ICON,
sourceVersion: null,
dependencyMetadata: {},
donationUrl: null,
alerts: {
install: 'test',
uninstall: 'test',
start: 'test',
stop: 'test',
restore: 'test',
},
s9pk: {
url: 'https://github.com/Start9Labs/bitcoind-startos/releases/download/v26.1.0/bitcoind.s9pk',
commitment: mockMerkleArchiveCommitment,
signatures: {},
publishedAt: Date.now().toString(),
},
},
'#knots:26.1.20240325:0': {
title: 'Bitcoin Knots',
description: {
short: 'An alternate fully verifying implementation of Bitcoin',
long: 'Bitcoin Knots is a combined Bitcoin node and wallet. Not only is it easy to use, but it also ensures bitcoins you receive are both real bitcoins and really yours.',
},
hardwareRequirements: { arch: null, device: {}, ram: null },
license: 'mit',
wrapperRepo: 'https://github.com/start9labs/bitcoinknots-startos',
upstreamRepo: 'https://github.com/bitcoinknots/bitcoin',
supportSite: 'https://bitcoinknots.org',
marketingSite: 'https://bitcoinknots.org',
releaseNotes: 'Even better support for Bitcoin and wallets!',
osVersion: '0.3.6',
gitHash: 'fakehash',
icon: BTC_ICON,
sourceVersion: null,
dependencyMetadata: {},
donationUrl: null,
alerts: {
install: 'test',
uninstall: 'test',
start: 'test',
stop: 'test',
restore: 'test',
},
s9pk: {
url: 'https://github.com/Start9Labs/bitcoinknots-startos/releases/download/v26.1.20240513/bitcoind.s9pk',
commitment: mockMerkleArchiveCommitment,
signatures: {},
publishedAt: Date.now().toString(),
},
},
},
categories: ['bitcoin', 'featured'],
otherVersions: {
'27.0.0:1.0.0': {
releaseNotes: 'Even better support for Bitcoin and wallets!',
},
'#knots:27.1.0:0': {
releaseNotes: 'Even better support for Bitcoin and wallets!',
},
},
categories: ['bitcoin', 'cryptocurrency'],
versions: ['0.19.0', '0.20.0', '0.21.0'],
dependencyMetadata: {},
publishedAt: new Date().toISOString(),
},
'0.20.0': {
icon: BTC_ICON,
license: 'licenseUrl',
instructions: 'instructionsUrl',
manifest: {
...Mock.MockManifestBitcoind,
version: '0.20.0',
'=#knots:26.1.20240325:0': {
best: {
'26.1.0:0.1.0': {
title: 'Bitcoin Core',
description: mockDescription,
hardwareRequirements: { arch: null, device: {}, ram: null },
license: 'mit',
wrapperRepo: 'https://github.com/start9labs/bitcoind-startos',
upstreamRepo: 'https://github.com/bitcoin/bitcoin',
supportSite: 'https://bitcoin.org',
marketingSite: 'https://bitcoin.org',
releaseNotes: 'Even better support for Bitcoin and wallets!',
osVersion: '0.3.6',
gitHash: 'fakehash',
icon: BTC_ICON,
sourceVersion: null,
dependencyMetadata: {},
donationUrl: null,
alerts: {
install: 'test',
uninstall: 'test',
start: 'test',
stop: 'test',
restore: 'test',
},
s9pk: {
url: 'https://github.com/Start9Labs/bitcoind-startos/releases/download/v26.1.0/bitcoind.s9pk',
commitment: mockMerkleArchiveCommitment,
signatures: {},
publishedAt: Date.now().toString(),
},
},
'#knots:26.1.20240325:0': {
title: 'Bitcoin Knots',
description: {
short: 'An alternate fully verifying implementation of Bitcoin',
long: 'Bitcoin Knots is a combined Bitcoin node and wallet. Not only is it easy to use, but it also ensures bitcoins you receive are both real bitcoins and really yours.',
},
hardwareRequirements: { arch: null, device: {}, ram: null },
license: 'mit',
wrapperRepo: 'https://github.com/start9labs/bitcoinknots-startos',
upstreamRepo: 'https://github.com/bitcoinknots/bitcoin',
supportSite: 'https://bitcoinknots.org',
marketingSite: 'https://bitcoinknots.org',
releaseNotes: 'Even better support for Bitcoin and wallets!',
osVersion: '0.3.6',
gitHash: 'fakehash',
icon: BTC_ICON,
sourceVersion: null,
dependencyMetadata: {},
donationUrl: null,
alerts: {
install: 'test',
uninstall: 'test',
start: 'test',
stop: 'test',
restore: 'test',
},
s9pk: {
url: 'https://github.com/Start9Labs/bitcoinknots-startos/releases/download/v26.1.20240513/bitcoind.s9pk',
commitment: mockMerkleArchiveCommitment,
signatures: {},
publishedAt: Date.now().toString(),
},
},
},
categories: ['bitcoin', 'cryptocurrency'],
versions: ['0.19.0', '0.20.0', '0.21.0'],
dependencyMetadata: {},
publishedAt: new Date().toISOString(),
},
'0.21.0': {
icon: BTC_ICON,
license: 'licenseUrl',
instructions: 'instructionsUrl',
manifest: {
...Mock.MockManifestBitcoind,
version: '0.21.0',
releaseNotes:
'For a complete list of changes, please visit <a href="https://bitcoincore.org/en/releases/0.21.0/">https://bitcoincore.org/en/releases/0.21.0/</a><br /><ul><li>Taproot!</li><li>New RPCs</li><li>Experimental Descriptor Wallets</li></ul>',
categories: ['bitcoin', 'featured'],
otherVersions: {
'27.0.0:1.0.0': {
releaseNotes: 'Even better support for Bitcoin and wallets!',
},
'#knots:27.1.0:0': {
releaseNotes: 'Even better support for Bitcoin and wallets!',
},
},
categories: ['bitcoin', 'cryptocurrency'],
versions: ['0.19.0', '0.20.0', '0.21.0'],
dependencyMetadata: {},
publishedAt: new Date().toISOString(),
},
latest: {
icon: BTC_ICON,
license: 'licenseUrl',
instructions: 'instructionsUrl',
manifest: {
...Mock.MockManifestBitcoind,
releaseNotes:
'For a complete list of changes, please visit <a href="https://bitcoincore.org/en/releases/0.21.0/" target="_blank">https://bitcoincore.org/en/releases/0.21.0/</a><br />Or in [markdown](https://bitcoincore.org/en/releases/0.21.0/)<ul><li>Taproot!</li><li>New RPCs</li><li>Experimental Descriptor Wallets</li></ul>',
},
categories: ['bitcoin', 'cryptocurrency'],
versions: ['0.19.0', '0.20.0', '0.21.0'],
dependencyMetadata: {},
publishedAt: new Date().toISOString(),
},
},
lnd: {
'0.11.0': {
icon: LND_ICON,
license: 'licenseUrl',
instructions: 'instructionsUrl',
manifest: {
...Mock.MockManifestLnd,
version: '0.11.0',
releaseNotes: 'release notes for LND 0.11.0',
'=0.17.5:0': {
best: {
'0.17.5:0': {
title: 'LND',
description: mockDescription,
hardwareRequirements: { arch: null, device: {}, ram: null },
license: 'mit',
wrapperRepo: 'https://github.com/start9labs/lnd-startos',
upstreamRepo: 'https://github.com/lightningnetwork/lnd',
supportSite: 'https://lightning.engineering/slack.html',
marketingSite: 'https://lightning.engineering/',
releaseNotes: 'Upstream release to 0.17.5',
osVersion: '0.3.6',
gitHash: 'fakehash',
icon: LND_ICON,
sourceVersion: null,
dependencyMetadata: {
bitcoind: {
title: 'Bitcoin Core',
icon: BTC_ICON,
description: 'Used for RPC requests',
optional: false,
},
'btc-rpc-proxy': {
title: 'Bitcoin Proxy',
icon: PROXY_ICON,
description: 'Used for authorized proxying of RPC requests',
optional: true,
},
},
donationUrl: null,
alerts: {
install: 'test',
uninstall: 'test',
start: 'test',
stop: 'test',
restore: 'test',
},
s9pk: {
url: 'https://github.com/Start9Labs/lnd-startos/releases/download/v0.17.5/lnd.s9pk',
commitment: mockMerkleArchiveCommitment,
signatures: {},
publishedAt: Date.now().toString(),
},
},
},
categories: ['bitcoin', 'lightning', 'cryptocurrency'],
versions: ['0.11.0', '0.11.1'],
dependencyMetadata: {
bitcoind: BitcoinDep,
'btc-rpc-proxy': ProxyDep,
categories: ['lightning', 'featured'],
otherVersions: {
'0.18.0:0.0.1': {
releaseNotes: 'Upstream release and minor fixes.',
},
'0.17.4-beta:1.0-alpha': {
releaseNotes: 'Upstream release to 0.17.4',
},
},
publishedAt: new Date().toISOString(),
},
'0.11.1': {
icon: LND_ICON,
license: 'licenseUrl',
instructions: 'instructionsUrl',
manifest: {
...Mock.MockManifestLnd,
version: '0.11.1',
releaseNotes: 'release notes for LND 0.11.1',
'=0.17.4-beta:1.0-alpha': {
best: {
'0.17.4-beta:1.0-alpha': {
title: 'LND',
description: mockDescription,
hardwareRequirements: { arch: null, device: {}, ram: null },
license: 'mit',
wrapperRepo: 'https://github.com/start9labs/lnd-startos',
upstreamRepo: 'https://github.com/lightningnetwork/lnd',
supportSite: 'https://lightning.engineering/slack.html',
marketingSite: 'https://lightning.engineering/',
releaseNotes: 'Upstream release to 0.17.4',
osVersion: '0.3.6',
gitHash: 'fakehash',
icon: LND_ICON,
sourceVersion: null,
dependencyMetadata: {
bitcoind: {
title: 'Bitcoin Core',
icon: BTC_ICON,
description: 'Used for RPC requests',
optional: false,
},
'btc-rpc-proxy': {
title: 'Bitcoin Proxy',
icon: PROXY_ICON,
description: 'Used for authorized proxying of RPC requests',
optional: true,
},
},
donationUrl: null,
alerts: {
install: 'test',
uninstall: 'test',
start: 'test',
stop: 'test',
restore: 'test',
},
s9pk: {
url: 'https://github.com/Start9Labs/lnd-startos/releases/download/v0.17.4/lnd.s9pk',
commitment: mockMerkleArchiveCommitment,
signatures: {},
publishedAt: Date.now().toString(),
},
},
},
categories: ['bitcoin', 'lightning', 'cryptocurrency'],
versions: ['0.11.0', '0.11.1'],
dependencyMetadata: {
bitcoind: BitcoinDep,
'btc-rpc-proxy': ProxyDep,
categories: ['lightning', 'featured'],
otherVersions: {
'0.18.0:0.0.1': {
releaseNotes: 'Upstream release and minor fixes.',
},
'0.17.5:0': {
releaseNotes: 'Upstream release to 0.17.5',
},
},
publishedAt: new Date().toISOString(),
},
latest: {
icon: LND_ICON,
license: 'licenseUrl',
instructions: 'instructionsUrl',
manifest: Mock.MockManifestLnd,
categories: ['bitcoin', 'lightning', 'cryptocurrency'],
versions: ['0.11.0', '0.11.1'],
dependencyMetadata: {
bitcoind: BitcoinDep,
'btc-rpc-proxy': ProxyDep,
},
publishedAt: new Date(new Date().valueOf() + 10).toISOString(),
},
},
'btc-rpc-proxy': {
latest: {
icon: PROXY_ICON,
license: 'licenseUrl',
instructions: 'instructionsUrl',
manifest: Mock.MockManifestBitcoinProxy,
categories: ['bitcoin'],
versions: ['0.2.2'],
dependencyMetadata: {
bitcoind: BitcoinDep,
'=0.3.2.6:0': {
best: {
'0.3.2.6:0': {
title: 'Bitcoin Proxy',
description: mockDescription,
hardwareRequirements: { arch: null, device: {}, ram: null },
license: 'mit',
wrapperRepo: 'https://github.com/Start9Labs/btc-rpc-proxy-wrappers',
upstreamRepo: 'https://github.com/Kixunil/btc-rpc-proxy',
supportSite: 'https://github.com/Kixunil/btc-rpc-proxy/issues',
marketingSite: '',
releaseNotes: 'Upstream release and minor fixes.',
osVersion: '0.3.6',
gitHash: 'fakehash',
icon: PROXY_ICON,
sourceVersion: null,
dependencyMetadata: {},
donationUrl: null,
alerts: {
install: 'test',
uninstall: 'test',
start: 'test',
stop: 'test',
restore: 'test',
},
s9pk: {
url: 'https://github.com/Start9Labs/btc-rpc-proxy-startos/releases/download/v0.3.2.7.1/btc-rpc-proxy.s9pk',
commitment: mockMerkleArchiveCommitment,
signatures: {},
publishedAt: Date.now().toString(),
},
},
},
categories: ['bitcoin'],
otherVersions: {
'0.3.2.7:0': {
releaseNotes: 'Upstream release and minor fixes.',
},
},
publishedAt: new Date().toISOString(),
},
},
}
export const MarketplacePkgsList: RR.GetMarketplacePackagesRes =
Object.values(Mock.MarketplacePkgs).map(service => service['latest'])
export const RegistryPackages: GetPackagesRes = {
bitcoind: {
best: {
'27.0.0:1.0.0': {
title: 'Bitcoin Core',
description: mockDescription,
hardwareRequirements: { arch: null, device: {}, ram: null },
license: 'mit',
wrapperRepo: 'https://github.com/start9labs/bitcoind-startos',
upstreamRepo: 'https://github.com/bitcoin/bitcoin',
supportSite: 'https://bitcoin.org',
marketingSite: 'https://bitcoin.org',
releaseNotes: 'Even better support for Bitcoin and wallets!',
osVersion: '0.3.6',
gitHash: 'fakehash',
icon: BTC_ICON,
sourceVersion: null,
dependencyMetadata: {},
donationUrl: null,
alerts: {
install: 'test',
uninstall: 'test',
start: 'test',
stop: 'test',
restore: 'test',
},
s9pk: {
url: 'https://github.com/Start9Labs/bitcoind-startos/releases/download/v27.0.0/bitcoind.s9pk',
commitment: mockMerkleArchiveCommitment,
signatures: {},
publishedAt: Date.now().toString(),
},
},
'#knots:27.1.0:0': {
title: 'Bitcoin Knots',
description: {
short: 'An alternate fully verifying implementation of Bitcoin',
long: 'Bitcoin Knots is a combined Bitcoin node and wallet. Not only is it easy to use, but it also ensures bitcoins you receive are both real bitcoins and really yours.',
},
hardwareRequirements: { arch: null, device: {}, ram: null },
license: 'mit',
wrapperRepo: 'https://github.com/start9labs/bitcoinknots-startos',
upstreamRepo: 'https://github.com/bitcoinknots/bitcoin',
supportSite: 'https://bitcoinknots.org',
marketingSite: 'https://bitcoinknots.org',
releaseNotes: 'Even better support for Bitcoin and wallets!',
osVersion: '0.3.6',
gitHash: 'fakehash',
icon: BTC_ICON,
sourceVersion: null,
dependencyMetadata: {},
donationUrl: null,
alerts: {
install: 'test',
uninstall: 'test',
start: 'test',
stop: 'test',
restore: 'test',
},
s9pk: {
url: 'https://github.com/Start9Labs/bitcoinknots-startos/releases/download/v26.1.20240513/bitcoind.s9pk',
commitment: mockMerkleArchiveCommitment,
signatures: {},
publishedAt: Date.now().toString(),
},
},
},
categories: ['bitcoin', 'featured'],
otherVersions: {
'26.1.0:0.1.0': {
releaseNotes: 'Even better support for Bitcoin and wallets!',
},
'#knots:26.1.20240325:0': {
releaseNotes: 'Even better Knots support for Bitcoin and wallets!',
},
},
},
lnd: {
best: {
'0.18.0:0.0.1': {
title: 'LND',
description: mockDescription,
hardwareRequirements: { arch: null, device: {}, ram: null },
license: 'mit',
wrapperRepo: 'https://github.com/start9labs/lnd-startos',
upstreamRepo: 'https://github.com/lightningnetwork/lnd',
supportSite: 'https://lightning.engineering/slack.html',
marketingSite: 'https://lightning.engineering/',
releaseNotes: 'Upstream release and minor fixes.',
osVersion: '0.3.6',
gitHash: 'fakehash',
icon: LND_ICON,
sourceVersion: null,
dependencyMetadata: {
bitcoind: {
title: 'Bitcoin Core',
icon: BTC_ICON,
description: 'Used for RPC requests',
optional: false,
},
'btc-rpc-proxy': {
title: 'Bitcoin Proxy',
icon: null,
description: 'Used for authorized RPC requests',
optional: true,
},
},
donationUrl: null,
alerts: {
install: 'test',
uninstall: 'test',
start: 'test',
stop: 'test',
restore: 'test',
},
s9pk: {
url: 'https://github.com/Start9Labs/lnd-startos/releases/download/v0.18.0.1/lnd.s9pk',
commitment: mockMerkleArchiveCommitment,
signatures: {},
publishedAt: Date.now().toString(),
},
},
},
categories: ['lightning', 'featured'],
otherVersions: {
'0.17.5:0': {
releaseNotes: 'Upstream release to 0.17.5',
},
'0.17.4-beta:1.0-alpha': {
releaseNotes: 'Upstream release to 0.17.4',
},
},
},
'btc-rpc-proxy': {
best: {
'0.3.2.7:0': {
title: 'Bitcoin Proxy',
description: mockDescription,
hardwareRequirements: { arch: null, device: {}, ram: null },
license: 'mit',
wrapperRepo: 'https://github.com/Start9Labs/btc-rpc-proxy-wrappers',
upstreamRepo: 'https://github.com/Kixunil/btc-rpc-proxy',
supportSite: 'https://github.com/Kixunil/btc-rpc-proxy/issues',
marketingSite: '',
releaseNotes: 'Upstream release and minor fixes.',
osVersion: '0.3.6',
gitHash: 'fakehash',
icon: PROXY_ICON,
sourceVersion: null,
dependencyMetadata: {},
donationUrl: null,
alerts: {
install: 'test',
uninstall: 'test',
start: 'test',
stop: 'test',
restore: 'test',
},
s9pk: {
url: 'https://github.com/Start9Labs/btc-rpc-proxy-startos/releases/download/v0.3.2.7/btc-rpc-proxy.s9pk',
commitment: mockMerkleArchiveCommitment,
signatures: {},
publishedAt: Date.now().toString(),
},
},
},
categories: ['bitcoin'],
otherVersions: {
'0.3.2.6:0': {
releaseNotes: 'Upstream release and minor fixes.',
},
},
},
}
export const Notifications: ServerNotifications = [
{
@@ -658,13 +1063,13 @@ export module Mock {
packageBackups: {
bitcoind: {
title: 'Bitcoin Core',
version: '0.21.0',
version: '0.21.0:0',
osVersion: '0.3.6',
timestamp: new Date().toISOString(),
},
'btc-rpc-proxy': {
title: 'Bitcoin Proxy',
version: '0.2.2',
version: '0.2.2:0',
osVersion: '0.3.6',
timestamp: new Date().toISOString(),
},
@@ -1489,8 +1894,7 @@ export module Mock {
title: Mock.MockManifestBitcoind.title,
icon: 'assets/img/service-icons/bitcoind.svg',
kind: 'running',
registryUrl: '',
versionSpec: '>=26.0.0',
versionRange: '>=26.0.0',
healthChecks: [],
configSatisfied: true,
},
@@ -1576,8 +1980,7 @@ export module Mock {
title: Mock.MockManifestBitcoind.title,
icon: 'assets/img/service-icons/bitcoind.svg',
kind: 'running',
registryUrl: 'https://registry.start9.com',
versionSpec: '>=26.0.0',
versionRange: '>=26.0.0',
healthChecks: [],
configSatisfied: true,
},
@@ -1585,8 +1988,7 @@ export module Mock {
title: Mock.MockManifestBitcoinProxy.title,
icon: 'assets/img/service-icons/btc-rpc-proxy.png',
kind: 'exists',
registryUrl: 'https://community-registry.start9.com',
versionSpec: '>2.0.0',
versionRange: '>2.0.0',
configSatisfied: false,
},
},

View File

@@ -1,5 +1,4 @@
import { Dump } from 'patch-db-client'
import { MarketplacePkg, StoreInfo } from '@start9labs/marketplace'
import { PackagePropertiesVersioned } from 'src/app/util/properties.util'
import { DataModel } from 'src/app/services/patch-db/data-model'
import { StartOSDiskInfo, LogsRes, ServerLogsReq } from '@start9labs/shared'
@@ -223,12 +222,7 @@ export module RR {
export type GetPackageMetricsReq = { id: string } // package.metrics
export type GetPackageMetricsRes = Metric
export type InstallPackageReq = {
id: string
versionSpec?: string
versionPriority?: 'min' | 'max'
registry: string
} // package.install
export type InstallPackageReq = T.InstallParams
export type InstallPackageRes = null
export type GetPackageConfigReq = { id: string } // package.config.get
@@ -287,26 +281,13 @@ export module RR {
progress: string // guid
}
// marketplace
// registry
export type GetMarketplaceInfoReq = { serverId: string }
export type GetMarketplaceInfoRes = StoreInfo
/** these are returned in ASCENDING order. the newest available version will be the LAST in the object */
export type GetRegistryOsUpdateRes = { [version: string]: T.OsVersionInfo }
export type CheckOSUpdateReq = { serverId: string }
export type CheckOSUpdateRes = OSUpdate
export type GetMarketplacePackagesReq = {
ids?: { id: string; version: string }[]
// iff !ids
category?: string
query?: string
page?: number
perPage?: number
}
export type GetMarketplacePackagesRes = MarketplacePkg[]
export type GetReleaseNotesReq = { id: string }
export type GetReleaseNotesRes = { [version: string]: string }
}
export interface OSUpdate {

View File

@@ -1,17 +1,32 @@
import { Observable } from 'rxjs'
import { RR } from './api.types'
import { RPCOptions } from '@start9labs/shared'
import { T } from '@start9labs/start-sdk'
import {
GetPackageRes,
GetPackagesRes,
MarketplacePkg,
} from '@start9labs/marketplace'
export abstract class ApiService {
// http
// for getting static files: ex icons, instructions, licenses
abstract getStatic(url: string): Promise<string>
// for sideloading packages
abstract uploadPackage(guid: string, body: Blob): Promise<string>
abstract uploadFile(body: Blob): Promise<string>
// for getting static files: ex icons, instructions, licenses
abstract getStaticProxy(
pkg: MarketplacePkg,
path: 'LICENSE.md' | 'instructions.md',
): Promise<string>
abstract getStaticInstalled(
id: T.PackageId,
path: 'LICENSE.md' | 'instructions.md',
): Promise<string>
// websocket
abstract openWebsocket$<T>(
@@ -120,14 +135,23 @@ export abstract class ApiService {
// marketplace URLs
abstract marketplaceProxy<T>(
path: string,
params: Record<string, unknown>,
url: string,
abstract registryRequest<T>(
registryUrl: string,
options: RPCOptions,
): Promise<T>
abstract checkOSUpdate(qp: RR.CheckOSUpdateReq): Promise<RR.CheckOSUpdateRes>
abstract getRegistryInfo(registryUrl: string): Promise<T.RegistryInfo>
abstract getRegistryPackage(
url: string,
id: string,
versionRange: string | null,
): Promise<GetPackageRes>
abstract getRegistryPackages(registryUrl: string): Promise<GetPackagesRes>
// notification
abstract getNotifications(

View File

@@ -18,6 +18,15 @@ import { AuthService } from '../auth.service'
import { DOCUMENT } from '@angular/common'
import { DataModel } from '../patch-db/data-model'
import { Dump, pathFromArray } from 'patch-db-client'
import { T } from '@start9labs/start-sdk'
import {
GetPackageReq,
GetPackageRes,
GetPackagesReq,
GetPackagesRes,
MarketplacePkg,
} from '@start9labs/marketplace'
import { blake3 } from '@noble/hashes/blake3'
@Injectable()
export class LiveApiService extends ApiService {
@@ -29,17 +38,7 @@ export class LiveApiService extends ApiService {
@Inject(PATCH_CACHE) private readonly cache$: Observable<Dump<DataModel>>,
) {
super()
; (window as any).rpcClient = this
}
// for getting static files: ex icons, instructions, licenses
async getStatic(url: string): Promise<string> {
return this.httpRequest({
method: Method.GET,
url,
responseType: 'text',
})
;(window as any).rpcClient = this
}
// for sideloading packages
@@ -62,6 +61,36 @@ export class LiveApiService extends ApiService {
})
}
// for getting static files: ex. instructions, licenses
async getStaticProxy(
pkg: MarketplacePkg,
path: 'LICENSE.md' | 'instructions.md',
): Promise<string> {
const encodedUrl = encodeURIComponent(pkg.s9pk.url)
return this.httpRequest({
method: Method.GET,
url: `/s9pk/proxy/${encodedUrl}/${path}`,
params: {
rootSighash: pkg.s9pk.commitment.rootSighash,
rootMaxsize: pkg.s9pk.commitment.rootMaxsize,
},
responseType: 'text',
})
}
async getStaticInstalled(
id: T.PackageId,
path: 'LICENSE.md' | 'instructions.md',
): Promise<string> {
return this.httpRequest({
method: Method.GET,
url: `/s9pk/installed/${id}.s9pk/${path}`,
responseType: 'text',
})
}
// websocket
openWebsocket$<T>(
@@ -257,24 +286,63 @@ export class LiveApiService extends ApiService {
// marketplace URLs
async marketplaceProxy<T>(
path: string,
qp: Record<string, string>,
baseUrl: string,
async registryRequest<T>(
registryUrl: string,
options: RPCOptions,
): Promise<T> {
const fullUrl = `${baseUrl}${path}?${new URLSearchParams(qp).toString()}`
return this.rpcRequest({
method: 'marketplace.get',
params: { url: fullUrl },
...options,
method: `registry.${options.method}`,
params: { registry: registryUrl, ...options.params },
})
}
async checkOSUpdate(qp: RR.CheckOSUpdateReq): Promise<RR.CheckOSUpdateRes> {
return this.marketplaceProxy(
'/eos/v0/latest',
qp,
this.config.marketplace.start9,
)
const { serverId } = qp
return this.registryRequest(this.config.marketplace.start9, {
method: 'os.version.get',
params: { serverId },
})
}
async getRegistryInfo(registryUrl: string): Promise<T.RegistryInfo> {
return this.registryRequest(registryUrl, {
method: 'info',
params: {},
})
}
async getRegistryPackage(
registryUrl: string,
id: string,
versionRange: string | null,
): Promise<GetPackageRes> {
const params: GetPackageReq = {
id,
version: versionRange,
sourceVersion: null,
otherVersions: 'short',
}
return this.registryRequest<GetPackageRes>(registryUrl, {
method: 'package.get',
params,
})
}
async getRegistryPackages(registryUrl: string): Promise<GetPackagesRes> {
const params: GetPackagesReq = {
id: null,
version: null,
sourceVersion: null,
otherVersions: 'short',
}
return this.registryRequest<GetPackagesRes>(registryUrl, {
method: 'package.get',
params,
})
}
// notification
@@ -504,6 +572,29 @@ export class LiveApiService extends ApiService {
private async httpRequest<T>(opts: HttpOptions): Promise<T> {
const res = await this.http.httpRequest<T>(opts)
if (res.headers.get('Repr-Digest')) {
// verify
const digest = res.headers.get('Repr-Digest')!
let data: Uint8Array
if (opts.responseType === 'arrayBuffer') {
data = Buffer.from(res.body as ArrayBuffer)
} else if (opts.responseType === 'text') {
data = Buffer.from(res.body as string)
} else if ((opts.responseType as string) === 'blob') {
data = Buffer.from(await (res.body as Blob).arrayBuffer())
} else {
console.warn(
`could not verify Repr-Digest for responseType ${
opts.responseType || 'json'
}`,
)
return res.body
}
const computedDigest = Buffer.from(blake3(data)).toString('base64')
if (`blake3=:${computedDigest}:` === digest) return res.body
console.debug(computedDigest, digest)
throw new Error('File digest mismatch.')
}
return res.body
}
}

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'
import { Log, RPCErrorDetails, pauseFor } from '@start9labs/shared'
import { Log, RPCErrorDetails, RPCOptions, pauseFor } from '@start9labs/shared'
import { ApiService } from './embassy-api.service'
import {
Operation,
@@ -30,8 +30,12 @@ import {
} from 'rxjs'
import { mockPatchData } from './mock-patch'
import { AuthService } from '../auth.service'
import { StoreInfo } from '@start9labs/marketplace'
import { T } from '@start9labs/start-sdk'
import {
GetPackageRes,
GetPackagesRes,
MarketplacePkg,
} from '@start9labs/marketplace'
const PROGRESS: T.FullProgress = {
overall: {
@@ -48,10 +52,7 @@ const PROGRESS: T.FullProgress = {
},
{
name: 'Validating',
progress: {
done: 0,
total: 40,
},
progress: null,
},
{
name: 'Installing',
@@ -80,14 +81,25 @@ export class MockApiService extends ApiService {
.subscribe()
}
async getStatic(url: string): Promise<string> {
async uploadPackage(guid: string, body: Blob): Promise<string> {
await pauseFor(2000)
return 'success'
}
async getStaticProxy(
pkg: MarketplacePkg,
path: 'LICENSE.md' | 'instructions.md',
): Promise<string> {
await pauseFor(2000)
return markdown
}
async uploadPackage(guid: string, body: Blob): Promise<string> {
async getStaticInstalled(
id: T.PackageId,
path: 'LICENSE.md' | 'instructions.md',
): Promise<string> {
await pauseFor(2000)
return 'success'
return markdown
}
// websocket
@@ -136,7 +148,7 @@ export class MockApiService extends ApiService {
this.stateIndex++
return this.stateIndex === 1 ? 'initializing' : 'running'
return this.stateIndex === 1 ? 'running' : 'running'
}
// db
@@ -448,34 +460,13 @@ export class MockApiService extends ApiService {
// marketplace URLs
async marketplaceProxy(
path: string,
params: Record<string, string>,
url: string,
async registryRequest(
registryUrl: string,
options: RPCOptions,
): Promise<any> {
await pauseFor(2000)
if (path === '/package/v0/info') {
const info: StoreInfo = {
name: 'Start9 Registry',
categories: [
'bitcoin',
'lightning',
'data',
'featured',
'messaging',
'social',
'alt coin',
],
}
return info
} else if (path === '/package/v0/index') {
return Mock.MarketplacePkgsList
} else if (path.startsWith('/package/v0/release-notes')) {
return Mock.ReleaseNotes
} else if (path.includes('instructions') || path.includes('license')) {
return markdown
}
return Error('do not call directly')
}
async checkOSUpdate(qp: RR.CheckOSUpdateReq): Promise<RR.CheckOSUpdateRes> {
@@ -483,6 +474,29 @@ export class MockApiService extends ApiService {
return Mock.MarketplaceEos
}
async getRegistryInfo(registryUrl: string): Promise<T.RegistryInfo> {
await pauseFor(2000)
return Mock.RegistryInfo
}
async getRegistryPackage(
url: string,
id: string,
versionRange: string,
): Promise<GetPackageRes> {
await pauseFor(2000)
if (!versionRange) {
return Mock.RegistryPackages[id]
} else {
return Mock.OtherPackageVersions[id][versionRange]
}
}
async getRegistryPackages(registryUrl: string): Promise<GetPackagesRes> {
await pauseFor(2000)
return Mock.RegistryPackages
}
// notification
async getNotifications(
@@ -742,11 +756,11 @@ export class MockApiService extends ApiService {
...Mock.LocalPkgs[params.id],
stateInfo: {
// if installing
// state: PackageState.Installing,
state: 'installing',
// if updating
state: 'updating',
manifest: mockPatchData.packageData[params.id].stateInfo.manifest!,
// state: 'updating',
// manifest: mockPatchData.packageData[params.id].stateInfo.manifest!,
// both
installingInfo: {
@@ -1129,11 +1143,7 @@ export class MockApiService extends ApiService {
const progress = JSON.parse(JSON.stringify(PROGRESS))
for (let [i, phase] of progress.phases.entries()) {
if (
!phase.progress ||
typeof phase.progress !== 'object' ||
!phase.progress.total
) {
if (!phase.progress || phase.progress === true || !phase.progress.total) {
await pauseFor(2000)
const patches: Operation<any>[] = [

View File

@@ -88,7 +88,7 @@ export const mockPatchData: DataModel = {
state: 'installed',
manifest: {
...Mock.MockManifestBitcoind,
version: '0.20.0',
version: '0.20.0:0',
},
},
icon: '/assets/img/service-icons/bitcoind.svg',
@@ -295,7 +295,7 @@ export const mockPatchData: DataModel = {
state: 'installed',
manifest: {
...Mock.MockManifestLnd,
version: '0.11.0',
version: '0.11.0:0.0.1',
},
},
icon: '/assets/img/service-icons/lnd.png',
@@ -368,8 +368,7 @@ export const mockPatchData: DataModel = {
title: 'Bitcoin Core',
icon: 'assets/img/service-icons/bitcoind.svg',
kind: 'running',
registryUrl: 'https://registry.start9.com',
versionSpec: '>=26.0.0',
versionRange: '>=26.0.0',
healthChecks: [],
configSatisfied: true,
},
@@ -377,8 +376,7 @@ export const mockPatchData: DataModel = {
title: 'Bitcoin Proxy',
icon: 'assets/img/service-icons/btc-rpc-proxy.png',
kind: 'running',
registryUrl: 'https://community-registry.start9.com',
versionSpec: '>2.0.0',
versionRange: '>2.0.0',
healthChecks: [],
configSatisfied: false,
},

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'
import { Emver } from '@start9labs/shared'
import { Exver } from '@start9labs/shared'
import { distinctUntilChanged, map, shareReplay } from 'rxjs/operators'
import { PatchDB } from 'patch-db-client'
import {
@@ -39,7 +39,7 @@ export class DepErrorService {
)
constructor(
private readonly emver: Emver,
private readonly exver: Exver,
private readonly patch: PatchDB<DataModel>,
) {}
@@ -87,11 +87,17 @@ export class DepErrorService {
const depManifest = dep.stateInfo.manifest
// incorrect version
if (!this.emver.satisfies(depManifest.version, currentDep.versionSpec)) {
return {
type: 'incorrectVersion',
expected: currentDep.versionSpec,
received: depManifest.version,
if (!this.exver.satisfies(depManifest.version, currentDep.versionRange)) {
if (
depManifest.satisfies.some(
v => !this.exver.satisfies(v, currentDep.versionRange),
)
) {
return {
type: 'incorrectVersion',
expected: currentDep.versionRange,
received: depManifest.version,
}
}
}

View File

@@ -1,5 +1,4 @@
import { Injectable } from '@angular/core'
import { Emver } from '@start9labs/shared'
import { BehaviorSubject, combineLatest } from 'rxjs'
import { distinctUntilChanged, map } from 'rxjs/operators'
import { OSUpdate } from 'src/app/services/api/api.types'
@@ -7,6 +6,7 @@ import { ApiService } from 'src/app/services/api/embassy-api.service'
import { PatchDB } from 'patch-db-client'
import { getServerInfo } from 'src/app/util/get-server-info'
import { DataModel } from './patch-db/data-model'
import { Exver } from '@start9labs/shared'
@Injectable({
providedIn: 'root',
@@ -47,15 +47,15 @@ export class EOSService {
constructor(
private readonly api: ApiService,
private readonly emver: Emver,
private readonly patch: PatchDB<DataModel>,
private readonly exver: Exver,
) {}
async loadEos(): Promise<void> {
const { version, id } = await getServerInfo(this.patch)
this.osUpdate = await this.api.checkOSUpdate({ serverId: id })
const updateAvailable =
this.emver.compare(this.osUpdate.version, version) === 1
this.exver.compareOsVersion(this.osUpdate.version, version) === 'greater'
this.updateAvailable$.next(updateAvailable)
}
}

View File

@@ -1,11 +1,11 @@
import { Injectable } from '@angular/core'
import { Inject, Injectable } from '@angular/core'
import {
MarketplacePkg,
AbstractMarketplaceService,
StoreData,
Marketplace,
StoreInfo,
StoreIdentity,
MarketplacePkg,
GetPackageRes,
} from '@start9labs/marketplace'
import {
BehaviorSubject,
@@ -37,8 +37,9 @@ import {
tap,
} from 'rxjs/operators'
import { ConfigService } from './config.service'
import { sameUrl } from '@start9labs/shared'
import { Exver, sameUrl } from '@start9labs/shared'
import { ClientStorageService } from './client-storage.service'
import { ExtendedVersion, T } from '@start9labs/start-sdk'
@Injectable()
export class MarketplaceService implements AbstractMarketplaceService {
@@ -93,11 +94,9 @@ export class MarketplaceService implements AbstractMarketplaceService {
mergeMap(({ url, name }) =>
this.fetchStore$(url).pipe(
tap(data => {
if (data?.info) this.updateStoreName(url, name, data.info.name)
}),
map<StoreData | null, [string, StoreData | null]>(data => {
return [url, data]
if (data?.info.name) this.updateStoreName(url, name, data.info.name)
}),
map<StoreData | null, [string, StoreData | null]>(data => [url, data]),
startWith<[string, StoreData | null]>([url, null]),
),
),
@@ -148,6 +147,7 @@ export class MarketplaceService implements AbstractMarketplaceService {
private readonly patch: PatchDB<DataModel>,
private readonly config: ConfigService,
private readonly clientStorageService: ClientStorageService,
private readonly exver: Exver,
) {}
getKnownHosts$(filtered = false): Observable<StoreIdentity[]> {
@@ -170,28 +170,29 @@ export class MarketplaceService implements AbstractMarketplaceService {
getPackage$(
id: string,
version: string,
optionalUrl?: string,
version: string | null,
flavor: string | null,
registryUrl?: string,
): Observable<MarketplacePkg> {
return this.patch.watch$('ui', 'marketplace').pipe(
switchMap(uiMarketplace => {
const url = optionalUrl || uiMarketplace.selectedUrl
return this.selectedHost$.pipe(
switchMap(selected =>
this.marketplace$.pipe(
switchMap(m => {
const url = registryUrl || selected.url
if (version !== '*' || !uiMarketplace.knownHosts[url]) {
return this.fetchPackage$(id, version, url)
}
const pkg = m[url]?.packages.find(
p =>
p.id === id &&
p.flavor === flavor &&
(!version || this.exver.compareExver(p.version, version) === 0),
)
return this.marketplace$.pipe(
map(m => m[url]),
filter(Boolean),
take(1),
map(
store =>
store.packages.find(p => p.manifest.id === id) ||
({} as MarketplacePkg),
),
)
}),
return !!pkg
? of(pkg)
: this.fetchPackage$(url, id, version, flavor)
}),
),
),
)
}
@@ -210,56 +211,22 @@ export class MarketplaceService implements AbstractMarketplaceService {
): Promise<void> {
const params: RR.InstallPackageReq = {
id,
versionSpec: `=${version}`,
version,
registry: url,
}
await this.api.installPackage(params)
}
fetchInfo$(url: string): Observable<StoreInfo> {
return this.patch.watch$('serverInfo').pipe(
take(1),
switchMap(serverInfo => {
const qp: RR.GetMarketplaceInfoReq = { serverId: serverInfo.id }
return this.api.marketplaceProxy<RR.GetMarketplaceInfoRes>(
'/package/v0/info',
qp,
url,
)
}),
)
fetchInfo$(url: string): Observable<T.RegistryInfo> {
return from(this.api.getRegistryInfo(url))
}
fetchReleaseNotes$(
id: string,
url?: string,
): Observable<Record<string, string>> {
return this.selectedHost$.pipe(
switchMap(m => {
return from(
this.api.marketplaceProxy<Record<string, string>>(
`/package/v0/release-notes/${id}`,
{},
url || m.url,
),
)
}),
)
}
fetchStatic$(id: string, type: string, url?: string): Observable<string> {
return this.selectedHost$.pipe(
switchMap(m => {
return from(
this.api.marketplaceProxy<string>(
`/package/v0/${type}/${id}`,
{},
url || m.url,
),
)
}),
)
fetchStatic$(
pkg: MarketplacePkg,
type: 'LICENSE.md' | 'instructions.md',
): Observable<string> {
return from(this.api.getStaticProxy(pkg, type))
}
private fetchStore$(url: string): Observable<StoreData | null> {
@@ -273,33 +240,57 @@ export class MarketplaceService implements AbstractMarketplaceService {
)
}
private fetchPackages$(
url: string,
params: Omit<RR.GetMarketplacePackagesReq, 'page' | 'per-page'> = {},
): Observable<MarketplacePkg[]> {
const qp: RR.GetMarketplacePackagesReq = {
...params,
page: 1,
perPage: 100,
}
if (qp.ids) qp.ids = JSON.stringify(qp.ids)
return from(
this.api.marketplaceProxy<RR.GetMarketplacePackagesRes>(
'/package/v0/index',
qp,
url,
),
private fetchPackages$(url: string): Observable<MarketplacePkg[]> {
return from(this.api.getRegistryPackages(url)).pipe(
map(packages => {
return Object.entries(packages).flatMap(([id, pkgInfo]) =>
Object.keys(pkgInfo.best).map(version =>
this.convertToMarketplacePkg(
id,
version,
this.exver.getFlavor(version),
pkgInfo,
),
),
)
}),
)
}
private fetchPackage$(
convertToMarketplacePkg(
id: string,
version: string,
version: string | null,
flavor: string | null,
pkgInfo: GetPackageRes,
): MarketplacePkg {
version =
version ||
Object.keys(pkgInfo.best).find(v => this.exver.getFlavor(v) === flavor) ||
null
return !version || !pkgInfo.best[version]
? ({} as MarketplacePkg)
: {
id,
version,
flavor,
...pkgInfo,
...pkgInfo.best[version],
}
}
private fetchPackage$(
url: string,
id: string,
version: string | null,
flavor: string | null,
): Observable<MarketplacePkg> {
return this.fetchPackages$(url, { ids: [{ id, version }] }).pipe(
map(pkgs => pkgs[0] || {}),
return from(
this.api.getRegistryPackage(url, id, version ? `=${version}` : null),
).pipe(
map(pkgInfo =>
this.convertToMarketplacePkg(id, version, flavor, pkgInfo),
),
)
}

View File

@@ -50,6 +50,10 @@ export type PackageDataEntry<T extends StateInfo = StateInfo> =
stateInfo: T
}
export type AllPackageData = NonNullable<
T.AllPackageData & Record<string, PackageDataEntry<StateInfo>>
>
export type StateInfo = InstalledState | InstallingState | UpdatingState
export type InstalledState = {