mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 12:11:56 +00:00
fix: correct false breakage detection for flavored packages and confi… (#3149)
fix: correct false breakage detection for flavored packages and config changes Two bugs caused the UI to incorrectly warn about dependency breakages: 1. dryUpdate (version path): Flavored package versions (e.g. #knots:27.0.0:0) failed exver.satisfies() against flavorless ranges (e.g. >=26.0.0) due to flavor mismatch. Now checks the manifest's `satisfies` declarations, matching the pattern already used in DepErrorService. Added `satisfies` field to PackageVersionInfo so it's available from registry data. 2. checkConflicts (config path): fast-json-patch's compare() treated missing keys as conflicts (add ops) and used positional array comparison, diverging from the backend's conflicts() semantics. Replaced with a conflicts() function that mirrors core/src/service/action.rs — missing keys are not conflicts, and arrays use set-based comparison. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -31,7 +31,7 @@ import { hasCurrentDeps } from 'src/app/utils/has-deps'
|
||||
|
||||
import { MarketplaceAlertsService } from '../services/alerts.service'
|
||||
|
||||
type KEYS = 'id' | 'version' | 'alerts' | 'flavor'
|
||||
type KEYS = 'id' | 'version' | 'alerts' | 'flavor' | 'satisfies'
|
||||
|
||||
@Component({
|
||||
selector: 'marketplace-controls',
|
||||
@@ -185,9 +185,13 @@ export class MarketplaceControlsComponent {
|
||||
}
|
||||
|
||||
private async dryInstall(url: string | null) {
|
||||
const { id, version } = this.pkg()
|
||||
const { id, version, satisfies } = this.pkg()
|
||||
const packages = await getAllPackages(this.patch)
|
||||
const breakages = dryUpdate({ id, version }, packages, this.exver)
|
||||
const breakages = dryUpdate(
|
||||
{ id, version, satisfies: satisfies || [] },
|
||||
packages,
|
||||
this.exver,
|
||||
)
|
||||
|
||||
if (!breakages.length || (await this.alerts.alertBreakages(breakages))) {
|
||||
this.installOrUpload(url)
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
TuiNotification,
|
||||
} from '@taiga-ui/core'
|
||||
import { injectContext } from '@taiga-ui/polymorpheus'
|
||||
import * as json from 'fast-json-patch'
|
||||
import { compare } from 'fast-json-patch'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { catchError, EMPTY, endWith, firstValueFrom, from, map } from 'rxjs'
|
||||
@@ -191,9 +190,7 @@ export class ActionInputModal {
|
||||
task.actionId === this.actionId &&
|
||||
task.when?.condition === 'input-not-matches' &&
|
||||
task.input &&
|
||||
json
|
||||
.compare(input, task.input.value)
|
||||
.some(op => op.op === 'add' || op.op === 'replace'),
|
||||
conflicts(task.input.value, input),
|
||||
),
|
||||
)
|
||||
.map(id => id)
|
||||
@@ -214,3 +211,26 @@ export class ActionInputModal {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Mirrors the Rust backend's `conflicts()` function in core/src/service/action.rs.
|
||||
// A key in the partial that is missing from the full input is NOT a conflict.
|
||||
function conflicts(left: unknown, right: unknown): boolean {
|
||||
if (
|
||||
typeof left === 'object' &&
|
||||
left !== null &&
|
||||
!Array.isArray(left) &&
|
||||
typeof right === 'object' &&
|
||||
right !== null &&
|
||||
!Array.isArray(right)
|
||||
) {
|
||||
const l = left as Record<string, unknown>
|
||||
const r = right as Record<string, unknown>
|
||||
return Object.keys(l).some(k => (k in r ? conflicts(l[k], r[k]) : false))
|
||||
}
|
||||
|
||||
if (Array.isArray(left) && Array.isArray(right)) {
|
||||
return left.some(v => right.every(vr => conflicts(v, vr)))
|
||||
}
|
||||
|
||||
return left !== right
|
||||
}
|
||||
|
||||
@@ -459,6 +459,7 @@ export namespace Mock {
|
||||
gitHash: 'fakehash',
|
||||
icon: BTC_ICON,
|
||||
sourceVersion: null,
|
||||
satisfies: [],
|
||||
dependencyMetadata: {},
|
||||
donationUrl: null,
|
||||
alerts: {
|
||||
@@ -501,6 +502,7 @@ export namespace Mock {
|
||||
gitHash: 'fakehash',
|
||||
icon: BTC_ICON,
|
||||
sourceVersion: null,
|
||||
satisfies: [],
|
||||
dependencyMetadata: {},
|
||||
donationUrl: null,
|
||||
alerts: {
|
||||
@@ -553,6 +555,7 @@ export namespace Mock {
|
||||
gitHash: 'fakehash',
|
||||
icon: BTC_ICON,
|
||||
sourceVersion: null,
|
||||
satisfies: [],
|
||||
dependencyMetadata: {},
|
||||
donationUrl: null,
|
||||
alerts: {
|
||||
@@ -595,6 +598,7 @@ export namespace Mock {
|
||||
gitHash: 'fakehash',
|
||||
icon: BTC_ICON,
|
||||
sourceVersion: null,
|
||||
satisfies: [],
|
||||
dependencyMetadata: {},
|
||||
donationUrl: null,
|
||||
alerts: {
|
||||
@@ -649,6 +653,7 @@ export namespace Mock {
|
||||
gitHash: 'fakehash',
|
||||
icon: LND_ICON,
|
||||
sourceVersion: null,
|
||||
satisfies: [],
|
||||
dependencyMetadata: {
|
||||
bitcoind: BitcoinDep,
|
||||
'btc-rpc-proxy': ProxyDep,
|
||||
@@ -704,6 +709,7 @@ export namespace Mock {
|
||||
gitHash: 'fakehash',
|
||||
icon: LND_ICON,
|
||||
sourceVersion: null,
|
||||
satisfies: [],
|
||||
dependencyMetadata: {
|
||||
bitcoind: BitcoinDep,
|
||||
'btc-rpc-proxy': ProxyDep,
|
||||
@@ -763,6 +769,7 @@ export namespace Mock {
|
||||
gitHash: 'fakehash',
|
||||
icon: BTC_ICON,
|
||||
sourceVersion: null,
|
||||
satisfies: [],
|
||||
dependencyMetadata: {},
|
||||
donationUrl: null,
|
||||
alerts: {
|
||||
@@ -805,6 +812,7 @@ export namespace Mock {
|
||||
gitHash: 'fakehash',
|
||||
icon: BTC_ICON,
|
||||
sourceVersion: null,
|
||||
satisfies: [],
|
||||
dependencyMetadata: {},
|
||||
donationUrl: null,
|
||||
alerts: {
|
||||
@@ -857,6 +865,7 @@ export namespace Mock {
|
||||
gitHash: 'fakehash',
|
||||
icon: LND_ICON,
|
||||
sourceVersion: null,
|
||||
satisfies: [],
|
||||
dependencyMetadata: {
|
||||
bitcoind: BitcoinDep,
|
||||
'btc-rpc-proxy': ProxyDep,
|
||||
@@ -912,6 +921,7 @@ export namespace Mock {
|
||||
gitHash: 'fakehash',
|
||||
icon: PROXY_ICON,
|
||||
sourceVersion: null,
|
||||
satisfies: [],
|
||||
dependencyMetadata: {
|
||||
bitcoind: BitcoinDep,
|
||||
},
|
||||
|
||||
@@ -3,7 +3,11 @@ import { DataModel } from '../services/patch-db/data-model'
|
||||
import { getManifest } from './get-package-data'
|
||||
|
||||
export function dryUpdate(
|
||||
{ id, version }: { id: string; version: string },
|
||||
{
|
||||
id,
|
||||
version,
|
||||
satisfies,
|
||||
}: { id: string; version: string; satisfies: string[] },
|
||||
pkgs: DataModel['packageData'],
|
||||
exver: Exver,
|
||||
): string[] {
|
||||
@@ -13,10 +17,24 @@ export function dryUpdate(
|
||||
Object.keys(pkg.currentDependencies || {}).some(
|
||||
pkgId => pkgId === id,
|
||||
) &&
|
||||
!exver.satisfies(
|
||||
!versionSatisfies(
|
||||
version,
|
||||
satisfies,
|
||||
pkg.currentDependencies[id]?.versionRange || '',
|
||||
exver,
|
||||
),
|
||||
)
|
||||
.map(pkg => getManifest(pkg).title)
|
||||
}
|
||||
|
||||
function versionSatisfies(
|
||||
version: string,
|
||||
satisfies: string[],
|
||||
range: string,
|
||||
exver: Exver,
|
||||
): boolean {
|
||||
return (
|
||||
exver.satisfies(version, range) ||
|
||||
satisfies.some(v => exver.satisfies(v, range))
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user