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

View File

@@ -1,6 +1,17 @@
<ion-content class="with-widgets">
<ion-header>
<ion-toolbar>
<ion-title>Past Release Notes</ion-title>
<ion-buttons slot="end">
<ion-button (click)="dismiss()" class="enter-click">
<ion-icon name="close"></ion-icon>
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding with-widgets">
<ng-container *ngIf="notes$ | async as notes; else loading">
<div *ngFor="let note of notes | keyvalue: asIsOrder">
<div *ngFor="let note of notes | keyvalue : asIsOrder">
<ion-button
expand="full"
color="light"
@@ -8,7 +19,7 @@
[class.ion-activated]="isSelected(note.key)"
(click)="setSelected(note.key)"
>
<p class="version">{{ note.key | displayEmver }}</p>
<p class="version">{{ note.key }}</p>
</ion-button>
<ion-card
tuiElement

View File

@@ -0,0 +1,69 @@
import {
ChangeDetectionStrategy,
Component,
ElementRef,
Input,
} from '@angular/core'
import { AbstractMarketplaceService } from '../../services/marketplace.service'
import { MarketplacePkg } from '../../types'
import { map } from 'rxjs'
import { Exver } from '@start9labs/shared'
import { ModalController } from '@ionic/angular'
@Component({
selector: 'release-notes',
templateUrl: './release-notes.component.html',
styleUrls: ['./release-notes.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ReleaseNotesComponent {
@Input() pkg!: MarketplacePkg
private selected: string | null = null
readonly notes$ = this.marketplaceService.getSelectedStore$().pipe(
map(s => {
return Object.entries(this.pkg.otherVersions)
.filter(
([v, _]) =>
this.exver.getFlavor(v) === this.pkg.flavor &&
this.exver.compareExver(this.pkg.version, v) === 1,
)
.reduce(
(obj, [version, info]) => ({
...obj,
[version]: info.releaseNotes,
}),
{
[`${this.pkg.version} (current)`]: this.pkg.releaseNotes,
},
)
}),
)
constructor(
private readonly marketplaceService: AbstractMarketplaceService,
private readonly exver: Exver,
private readonly modalCtrl: ModalController,
) {}
async dismiss() {
return this.modalCtrl.dismiss()
}
isSelected(key: string): boolean {
return this.selected === key
}
setSelected(selected: string) {
this.selected = this.isSelected(selected) ? null : selected
}
getDocSize(key: string, { nativeElement }: ElementRef<HTMLElement>) {
return this.isSelected(key) ? nativeElement.scrollHeight : 0
}
asIsOrder(a: any, b: any) {
return 0
}
}

View File

@@ -2,12 +2,11 @@ import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import { IonicModule } from '@ionic/angular'
import {
EmverPipesModule,
ExverPipesModule,
MarkdownPipeModule,
TextSpinnerComponentModule,
} from '@start9labs/shared'
import { TuiElementModule } from '@taiga-ui/cdk'
import { ReleaseNotesComponent } from './release-notes.component'
@NgModule({
@@ -15,11 +14,11 @@ import { ReleaseNotesComponent } from './release-notes.component'
CommonModule,
IonicModule,
TextSpinnerComponentModule,
EmverPipesModule,
ExverPipesModule,
MarkdownPipeModule,
TuiElementModule,
],
declarations: [ReleaseNotesComponent],
exports: [ReleaseNotesComponent],
})
export class ReleaseNotesModule {}
export class ReleaseNotesComponentModule {}

View File

@@ -1,9 +1,9 @@
<ion-button
*ngFor="let cat of categories"
*ngFor="let cat of categories | keyvalue : originalOrder"
fill="clear"
class="category"
[class.category_selected]="cat === category"
(click)="switchCategory(cat)"
[class.category_selected]="cat.key === category"
(click)="switchCategory(cat.key)"
>
{{ cat }}
{{ cat.value.name }}
</ion-button>

View File

@@ -5,6 +5,7 @@ import {
Input,
Output,
} from '@angular/core'
import { T } from '@start9labs/start-sdk'
@Component({
selector: 'marketplace-categories',
@@ -17,7 +18,7 @@ import {
})
export class CategoriesComponent {
@Input()
categories: readonly string[] = []
categories!: Map<string, T.Category>
@Input()
category = ''
@@ -29,4 +30,8 @@ export class CategoriesComponent {
this.category = category
this.categoryChange.emit(category)
}
originalOrder() {
return 0
}
}

View File

@@ -1,12 +1,16 @@
<ion-item class="service-card" [routerLink]="['/marketplace', pkg.manifest.id]">
<ion-item
class="service-card"
[routerLink]="['/marketplace', pkg.id]"
[queryParams]="{ flavor: pkg.flavor, version: pkg.version }"
>
<ion-thumbnail slot="start">
<img alt="" [src]="pkg.icon | trustUrl" />
</ion-thumbnail>
<ion-label>
<h2 class="montserrat">
<strong>{{ pkg.manifest.title }}</strong>
<strong>{{ pkg.title }}</strong>
</h2>
<h3>{{ pkg.manifest.description.short }}</h3>
<h3>{{ pkg.description.short }}</h3>
<ng-content></ng-content>
</ion-label>
</ion-item>

View File

@@ -1,39 +0,0 @@
import { ChangeDetectionStrategy, Component, ElementRef } from '@angular/core'
import { ActivatedRoute } from '@angular/router'
import { getPkgId } from '@start9labs/shared'
import { AbstractMarketplaceService } from '../../services/marketplace.service'
@Component({
selector: 'release-notes',
templateUrl: './release-notes.component.html',
styleUrls: ['./release-notes.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ReleaseNotesComponent {
private readonly pkgId = getPkgId(this.route)
private selected: string | null = null
readonly notes$ = this.marketplaceService.fetchReleaseNotes$(this.pkgId)
constructor(
private readonly route: ActivatedRoute,
private readonly marketplaceService: AbstractMarketplaceService,
) {}
isSelected(key: string): boolean {
return this.selected === key
}
setSelected(selected: string) {
this.selected = this.isSelected(selected) ? null : selected
}
getDocSize(key: string, { nativeElement }: ElementRef<HTMLElement>) {
return this.isSelected(key) ? nativeElement.scrollHeight : 0
}
asIsOrder(a: any, b: any) {
return 0
}
}

View File

@@ -1,13 +1,11 @@
<!-- release notes -->
<ion-item-divider>
New in {{ pkg.manifest.version | displayEmver }}
</ion-item-divider>
<ion-item-divider>New in {{ pkg.version }}</ion-item-divider>
<ion-item lines="none" color="transparent">
<ion-label>
<div [innerHTML]="pkg.manifest.releaseNotes | markdown"></div>
<div [innerHTML]="pkg.releaseNotes | markdown"></div>
</ion-label>
</ion-item>
<ion-button routerLink="notes" fill="clear" strong>
<ion-button fill="clear" strong (click)="presentModalNotes()">
Past Release Notes
<ion-icon slot="end" name="arrow-forward"></ion-icon>
</ion-button>
@@ -15,10 +13,10 @@
<ion-item-divider>Description</ion-item-divider>
<ion-item lines="none" color="transparent">
<ion-label>
<h2>{{ pkg.manifest.description.long }}</h2>
<h2>{{ pkg.description.long }}</h2>
</ion-label>
</ion-item>
<div *ngIf="pkg.manifest.marketingSite as url" style="padding: 4px 0 10px 14px">
<div *ngIf="pkg.marketingSite as url" style="padding: 4px 0 10px 14px">
<ion-button [href]="url" target="_blank" rel="noreferrer" color="tertiary">
View website
<ion-icon slot="end" name="open-outline"></ion-icon>

View File

@@ -1,5 +1,7 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
import { MarketplacePkg } from '../../../types'
import { ModalController } from '@ionic/angular'
import { ReleaseNotesComponent } from '../../../modals/release-notes/release-notes.component'
@Component({
selector: 'marketplace-about',
@@ -10,4 +12,15 @@ import { MarketplacePkg } from '../../../types'
export class AboutComponent {
@Input()
pkg!: MarketplacePkg
constructor(private readonly modalCtrl: ModalController) {}
async presentModalNotes() {
const modal = await this.modalCtrl.create({
componentProps: { pkg: this.pkg },
component: ReleaseNotesComponent,
})
await modal.present()
}
}

View File

@@ -2,9 +2,9 @@ import { CommonModule } from '@angular/common'
import { NgModule } from '@angular/core'
import { RouterModule } from '@angular/router'
import { IonicModule } from '@ionic/angular'
import { EmverPipesModule, MarkdownPipeModule } from '@start9labs/shared'
import { ExverPipesModule, MarkdownPipeModule } from '@start9labs/shared'
import { AboutComponent } from './about.component'
import { ReleaseNotesComponentModule } from '../../../modals/release-notes/release-notes.module'
@NgModule({
imports: [
@@ -12,7 +12,8 @@ import { AboutComponent } from './about.component'
RouterModule,
IonicModule,
MarkdownPipeModule,
EmverPipesModule,
ExverPipesModule,
ReleaseNotesComponentModule,
],
declarations: [AboutComponent],
exports: [AboutComponent],

View File

@@ -1,10 +1,10 @@
<ion-item-divider>Additional Info</ion-item-divider>
<ion-grid *ngIf="pkg.manifest as manifest">
<ion-grid *ngIf="pkg">
<ion-row>
<ion-col responsiveCol sizeXs="12" sizeMd="6">
<ion-item-group>
<ion-item
*ngIf="manifest.gitHash as gitHash; else noHash"
*ngIf="pkg.gitHash as gitHash; else noHash"
button
detail="false"
(click)="copy(gitHash)"
@@ -33,7 +33,7 @@
<ion-item button detail="false" (click)="presentModalMd('license')">
<ion-label>
<h2>License</h2>
<p>{{ manifest.license }}</p>
<p>{{ pkg.license }}</p>
</ion-label>
<ion-icon slot="end" name="chevron-forward"></ion-icon>
</ion-item>
@@ -53,39 +53,39 @@
<ion-col responsiveCol sizeXs="12" sizeMd="6">
<ion-item-group>
<ion-item
[href]="manifest.upstreamRepo"
[href]="pkg.upstreamRepo"
target="_blank"
rel="noreferrer"
detail="false"
>
<ion-label>
<h2>Source Repository</h2>
<p>{{ manifest.upstreamRepo }}</p>
<p>{{ pkg.upstreamRepo }}</p>
</ion-label>
<ion-icon slot="end" name="open-outline"></ion-icon>
</ion-item>
<ion-item
[href]="manifest.wrapperRepo"
[href]="pkg.wrapperRepo"
target="_blank"
rel="noreferrer"
detail="false"
>
<ion-label>
<h2>Wrapper Repository</h2>
<p>{{ manifest.wrapperRepo }}</p>
<p>{{ pkg.wrapperRepo }}</p>
</ion-label>
<ion-icon slot="end" name="open-outline"></ion-icon>
</ion-item>
<ion-item
[href]="manifest.supportSite"
[disabled]="!manifest.supportSite"
[href]="pkg.supportSite"
[disabled]="!pkg.supportSite"
target="_blank"
rel="noreferrer"
detail="false"
>
<ion-label>
<h2>Support Site</h2>
<p>{{ manifest.supportSite || 'Not provided' }}</p>
<p>{{ pkg.supportSite || 'Not provided' }}</p>
</ion-label>
<ion-icon slot="end" name="open-outline"></ion-icon>
</ion-item>

View File

@@ -10,15 +10,9 @@ import {
ModalController,
ToastController,
} from '@ionic/angular'
import {
copyToClipboard,
displayEmver,
Emver,
MarkdownComponent,
} from '@start9labs/shared'
import { copyToClipboard, Exver, MarkdownComponent } from '@start9labs/shared'
import { MarketplacePkg } from '../../../types'
import { AbstractMarketplaceService } from '../../../services/marketplace.service'
import { ActivatedRoute } from '@angular/router'
@Component({
selector: 'marketplace-additional',
@@ -32,15 +26,12 @@ export class AdditionalComponent {
@Output()
version = new EventEmitter<string>()
readonly url = this.route.snapshot.queryParamMap.get('url') || undefined
constructor(
private readonly alertCtrl: AlertController,
private readonly modalCtrl: ModalController,
private readonly emver: Emver,
private readonly exver: Exver,
private readonly marketplaceService: AbstractMarketplaceService,
private readonly toastCtrl: ToastController,
private readonly route: ActivatedRoute,
) {}
async copy(address: string): Promise<void> {
@@ -58,41 +49,53 @@ export class AdditionalComponent {
}
async presentAlertVersions() {
const alert = await this.alertCtrl.create({
header: 'Versions',
inputs: this.pkg.versions
.sort((a, b) => -1 * (this.emver.compare(a, b) || 0))
.map(v => ({
name: v, // for CSS
type: 'radio',
label: displayEmver(v), // appearance on screen
value: v, // literal SEM version value
checked: this.pkg.manifest.version === v,
})),
buttons: [
{
text: 'Cancel',
role: 'cancel',
},
{
text: 'Ok',
handler: (version: string) => this.version.emit(version),
},
],
})
const versions = Object.keys(this.pkg.otherVersions).filter(
v => this.exver.getFlavor(v) === this.pkg.flavor,
)
await alert.present()
if (!versions.length) {
const alert = await this.alertCtrl.create({
header: 'Versions',
message: 'No other versions',
})
await alert.present()
} else {
const alert = await this.alertCtrl.create({
header: 'Versions',
inputs: versions
.sort((a, b) => -1 * (this.exver.compareExver(a, b) || 0))
.map(v => ({
name: v, // for CSS
type: 'radio',
label: v, // appearance on screen
value: v, // literal SEM version value
checked: this.pkg.version === v,
})),
buttons: [
{
text: 'Cancel',
role: 'cancel',
},
{
text: 'Ok',
handler: (version: string) => this.version.emit(version),
},
],
})
await alert.present()
}
}
async presentModalMd(title: string) {
async presentModalMd(asset: 'license' | 'instructions') {
const content = this.marketplaceService.fetchStatic$(
this.pkg.manifest.id,
title,
this.url,
this.pkg,
asset === 'license' ? 'LICENSE.md' : 'instructions.md',
)
const modal = await this.modalCtrl.create({
componentProps: { title, content },
componentProps: { title: asset, content },
component: MarkdownComponent,
})

View File

@@ -2,7 +2,7 @@
<ion-grid>
<ion-row>
<ion-col
*ngFor="let dep of pkg.manifest.dependencies | keyvalue"
*ngFor="let dep of pkg.dependencyMetadata | keyvalue"
responsiveCol
sizeSm="12"
sizeMd="6"
@@ -17,7 +17,11 @@
</ion-thumbnail>
<ion-label>
<h2>
{{ pkg.dependencyMetadata[dep.key].title }}
{{
pkg.dependencyMetadata[dep.key].title
? pkg.dependencyMetadata[dep.key].title
: dep.key
}}
<span *ngIf="dep.value.optional; else required">(optional)</span>
<ng-template #required>
<span>(Required)</span>

View File

@@ -11,6 +11,7 @@ export class DependenciesComponent {
pkg!: MarketplacePkg
getImg(key: string): string {
return this.pkg.dependencyMetadata[key].icon
const icon = this.pkg.dependencyMetadata[key]?.icon
return icon ? icon : 'assets/img/service-icons/fallback.png'
}
}

View File

@@ -2,11 +2,7 @@ import { CommonModule } from '@angular/common'
import { NgModule } from '@angular/core'
import { RouterModule } from '@angular/router'
import { IonicModule } from '@ionic/angular'
import {
EmverPipesModule,
ResponsiveColModule,
SharedPipesModule,
} from '@start9labs/shared'
import { ResponsiveColModule, SharedPipesModule } from '@start9labs/shared'
import { DependenciesComponent } from './dependencies.component'
@@ -16,7 +12,6 @@ import { DependenciesComponent } from './dependencies.component'
RouterModule,
IonicModule,
SharedPipesModule,
EmverPipesModule,
ResponsiveColModule,
],
declarations: [DependenciesComponent],

View File

@@ -0,0 +1,21 @@
<ion-item-divider>Alternative Implementations</ion-item-divider>
<ion-grid>
<ion-row>
<ion-col *ngFor="let pkg of pkgs" responsiveCol sizeSm="12" sizeMd="6">
<ion-item
[routerLink]="['/marketplace', pkg.id]"
[queryParams]="{ flavor: pkg.flavor }"
>
<ion-thumbnail slot="start">
<img alt="" style="border-radius: 100%" [src]="pkg.icon | trustUrl" />
</ion-thumbnail>
<ion-label>
<h2>
{{ pkg.title }}
</h2>
<p>{{ pkg.version }}</p>
</ion-label>
</ion-item>
</ion-col>
</ion-row>
</ion-grid>

View File

@@ -0,0 +1,12 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
import { MarketplacePkg } from '../../../types'
@Component({
selector: 'marketplace-flavors',
templateUrl: 'flavors.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FlavorsComponent {
@Input()
pkgs!: MarketplacePkg[]
}

View File

@@ -0,0 +1,19 @@
import { CommonModule } from '@angular/common'
import { NgModule } from '@angular/core'
import { RouterModule } from '@angular/router'
import { IonicModule } from '@ionic/angular'
import { ResponsiveColModule, SharedPipesModule } from '@start9labs/shared'
import { FlavorsComponent } from './flavors.component'
@NgModule({
imports: [
CommonModule,
RouterModule,
IonicModule,
SharedPipesModule,
ResponsiveColModule,
],
declarations: [FlavorsComponent],
exports: [FlavorsComponent],
})
export class FlavorsModule {}

View File

@@ -1,9 +1,11 @@
<div class="header montserrat">
<img class="logo" alt="" [src]="pkg.icon | trustUrl" />
<div class="text">
<h1 ticker class="title">{{ pkg.manifest.title }}</h1>
<p class="version">{{ pkg.manifest.version | displayEmver }}</p>
<p class="published">Released: {{ pkg.publishedAt | date: 'medium' }}</p>
<h1 ticker class="title">{{ pkg.title }}</h1>
<p class="version">{{ pkg.version }}</p>
<p class="published">
Released: {{ pkg.s9pk.publishedAt | date : 'medium' }}
</p>
<ng-content></ng-content>
</div>
</div>

View File

@@ -2,7 +2,7 @@ import { CommonModule } from '@angular/common'
import { NgModule } from '@angular/core'
import { IonicModule } from '@ionic/angular'
import {
EmverPipesModule,
ExverPipesModule,
SharedPipesModule,
TickerModule,
} from '@start9labs/shared'
@@ -16,7 +16,7 @@ import { PackageComponent } from './package.component'
CommonModule,
IonicModule,
SharedPipesModule,
EmverPipesModule,
ExverPipesModule,
TickerModule,
],
})

View File

@@ -26,11 +26,11 @@ export class FilterPackagesPipe implements PipeTransform {
distance: 16,
keys: [
{
name: 'manifest.title',
name: 'title',
weight: 1,
},
{
name: 'manifest.id',
name: 'id',
weight: 0.5,
},
],
@@ -42,19 +42,19 @@ export class FilterPackagesPipe implements PipeTransform {
useExtendedSearch: true,
keys: [
{
name: 'manifest.title',
name: 'title',
weight: 1,
},
{
name: 'manifest.id',
name: 'id',
weight: 0.5,
},
{
name: 'manifest.description.short',
name: 'description.short',
weight: 0.4,
},
{
name: 'manifest.description.long',
name: 'description.long',
weight: 0.1,
},
],
@@ -71,7 +71,8 @@ export class FilterPackagesPipe implements PipeTransform {
.filter(p => category === 'all' || p.categories.includes(category))
.sort((a, b) => {
return (
new Date(b.publishedAt).valueOf() - new Date(a.publishedAt).valueOf()
new Date(b.s9pk.publishedAt).valueOf() -
new Date(a.s9pk.publishedAt).valueOf()
)
})
}

View File

@@ -10,8 +10,6 @@ export * from './pages/list/search/search.component'
export * from './pages/list/search/search.module'
export * from './pages/list/skeleton/skeleton.component'
export * from './pages/list/skeleton/skeleton.module'
export * from './pages/release-notes/release-notes.component'
export * from './pages/release-notes/release-notes.module'
export * from './pages/show/about/about.component'
export * from './pages/show/about/about.module'
export * from './pages/show/additional/additional.component'
@@ -20,6 +18,8 @@ export * from './pages/show/dependencies/dependencies.component'
export * from './pages/show/dependencies/dependencies.module'
export * from './pages/show/package/package.component'
export * from './pages/show/package/package.module'
export * from './pages/show/flavors/flavors.component'
export * from './pages/show/flavors/flavors.module'
export * from './pipes/filter-packages.pipe'

View File

@@ -12,18 +12,13 @@ export abstract class AbstractMarketplaceService {
abstract getPackage$(
id: string,
version: string,
version: string | null,
flavor: string | null,
url?: string,
): Observable<MarketplacePkg> // could be {} so need to check in show page
abstract fetchReleaseNotes$(
id: string,
url?: string,
): Observable<Record<string, string>>
): Observable<MarketplacePkg>
abstract fetchStatic$(
id: string,
type: string,
url?: string,
pkg: MarketplacePkg,
type: 'LICENSE.md' | 'instructions.md',
): Observable<string>
}

View File

@@ -1,48 +1,40 @@
import { Url } from '@start9labs/shared'
import { T } from '@start9labs/start-sdk'
export type StoreURL = string
export type StoreName = string
export interface StoreIdentity {
url: StoreURL
name?: StoreName
export type GetPackageReq = {
id: string
version: string | null
sourceVersion: null // @TODO what is this?
otherVersions: 'short'
}
export type GetPackageRes = T.GetPackageResponse & {
otherVersions: { [version: string]: T.PackageInfoShort }
}
export type Marketplace = Record<StoreURL, StoreData | null>
export interface StoreData {
info: StoreInfo
export type GetPackagesReq = {
id: null
version: null
sourceVersion: null
otherVersions: 'short'
}
export type GetPackagesRes = {
[id: T.PackageId]: GetPackageRes
}
export type StoreIdentity = {
url: string
name?: string
}
export type Marketplace = Record<string, StoreData | null>
export type StoreData = {
info: T.RegistryInfo
packages: MarketplacePkg[]
}
export interface StoreInfo {
name: StoreName
categories: string[]
}
export type StoreIdentityWithData = StoreData & StoreIdentity
export interface MarketplacePkg {
icon: Url
license: Url
instructions: Url
manifest: T.Manifest
categories: string[]
versions: string[]
dependencyMetadata: {
[id: string]: DependencyMetadata
export type MarketplacePkg = T.PackageVersionInfo &
Omit<GetPackageRes, 'best'> & {
id: T.PackageId
version: string
flavor: string | null
}
publishedAt: string
}
export interface DependencyMetadata {
title: string
icon: Url
optional: boolean
hidden: boolean
}
export interface Dependency {
description: string | null
optional: boolean
}