upstream-radar 0.34.0 → 0.36.0
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 +87 -24
- package/dist/src/cli.js +12 -4
- package/dist/src/cli.js.map +1 -1
- package/dist/src/dependency-index.d.ts +32 -0
- package/dist/src/dependency-index.d.ts.map +1 -1
- package/dist/src/dependency-index.js +230 -0
- package/dist/src/dependency-index.js.map +1 -1
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/upstream-alignment.d.ts +67 -0
- package/dist/src/upstream-alignment.d.ts.map +1 -0
- package/dist/src/upstream-alignment.js +263 -0
- package/dist/src/upstream-alignment.js.map +1 -0
- package/dist/src/upstream-observer.d.ts +20 -0
- package/dist/src/upstream-observer.d.ts.map +1 -1
- package/dist/src/upstream-observer.js +156 -4
- package/dist/src/upstream-observer.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/docs/README.zh-CN.md +54 -18
- package/package.json +1 -1
- package/schemas/reverse-dependency-index.schema.json +132 -0
- package/schemas/upstream-downstream-ir.schema.json +87 -0
|
@@ -2,8 +2,10 @@ import { createHash, randomUUID } from 'node:crypto';
|
|
|
2
2
|
import { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { spawn } from 'node:child_process';
|
|
4
4
|
import { dirname, resolve } from 'node:path';
|
|
5
|
+
import { findReverseDependencyImpacts } from './dependency-index.js';
|
|
5
6
|
import { parseNpmLockGraph, parsePnpmLockGraph } from './graph.js';
|
|
6
7
|
import { parsePackageManifestSnapshot } from './inventory.js';
|
|
8
|
+
import { buildUpstreamDownstreamIR, parseUpstreamDownstreamIR } from './upstream-alignment.js';
|
|
7
9
|
export const OBSERVER_TARGETS_SCHEMA = 'upstream-radar.observer-targets/v1alpha1';
|
|
8
10
|
export const OBSERVATION_STATE_SCHEMA = 'upstream-radar.observation-state/v1alpha1';
|
|
9
11
|
export const UPSTREAM_CHANGE_TASK_SCHEMA = 'upstream-radar.upstream-change-task/v1alpha1';
|
|
@@ -421,6 +423,7 @@ function parseSnapshot(value, label) {
|
|
|
421
423
|
: Array.isArray(source.warnings) && source.warnings.length <= 100
|
|
422
424
|
? source.warnings.map((item, index) => boundedString(item, `${label}.warnings[${index}]`, 2_048))
|
|
423
425
|
: (() => { throw new Error(`${label}.warnings must be a bounded string array`); })();
|
|
426
|
+
const alignment = source.alignment === undefined ? undefined : parseUpstreamDownstreamIR(source.alignment, `${label}.alignment`);
|
|
424
427
|
return {
|
|
425
428
|
targetId,
|
|
426
429
|
ecosystem,
|
|
@@ -431,6 +434,7 @@ function parseSnapshot(value, label) {
|
|
|
431
434
|
...(source.graph === undefined ? {} : { graph: source.graph }),
|
|
432
435
|
...(graphError === undefined ? {} : { graphError }),
|
|
433
436
|
...(warnings === undefined ? {} : { warnings }),
|
|
437
|
+
...(alignment === undefined ? {} : { alignment }),
|
|
434
438
|
};
|
|
435
439
|
}
|
|
436
440
|
function parseTask(value, label) {
|
|
@@ -921,6 +925,24 @@ export class UpstreamObserverClient {
|
|
|
921
925
|
graphError = safeError(error);
|
|
922
926
|
}
|
|
923
927
|
}
|
|
928
|
+
const alignment = buildUpstreamDownstreamIR({
|
|
929
|
+
targetId: targetValue.id,
|
|
930
|
+
ecosystem: targetValue.ecosystem,
|
|
931
|
+
source: {
|
|
932
|
+
repository: targetRepository.fullName,
|
|
933
|
+
commit,
|
|
934
|
+
packagePath,
|
|
935
|
+
},
|
|
936
|
+
manifest,
|
|
937
|
+
...(packageObservation === undefined ? {} : {
|
|
938
|
+
package: {
|
|
939
|
+
name: packageObservation.name,
|
|
940
|
+
version: packageObservation.version,
|
|
941
|
+
},
|
|
942
|
+
}),
|
|
943
|
+
...(graph === undefined ? {} : { graph }),
|
|
944
|
+
...(graphError === undefined ? {} : { graphError }),
|
|
945
|
+
});
|
|
924
946
|
return {
|
|
925
947
|
targetId: targetValue.id,
|
|
926
948
|
ecosystem: targetValue.ecosystem,
|
|
@@ -940,6 +962,7 @@ export class UpstreamObserverClient {
|
|
|
940
962
|
...(graph === undefined ? {} : { graph }),
|
|
941
963
|
...(graphError === undefined ? {} : { graphError }),
|
|
942
964
|
...(warnings.length === 0 ? {} : { warnings }),
|
|
965
|
+
alignment,
|
|
943
966
|
};
|
|
944
967
|
}
|
|
945
968
|
async compare(repositoryValue, beforeCommit, afterCommit) {
|
|
@@ -1028,8 +1051,22 @@ function summary(snapshot) {
|
|
|
1028
1051
|
}),
|
|
1029
1052
|
...(snapshot.graphError === undefined ? {} : { graphError: snapshot.graphError }),
|
|
1030
1053
|
...(snapshot.warnings === undefined ? {} : { warnings: [...snapshot.warnings] }),
|
|
1054
|
+
...(snapshot.alignment === undefined ? {} : { alignment: structuredClone(snapshot.alignment) }),
|
|
1031
1055
|
};
|
|
1032
1056
|
}
|
|
1057
|
+
function alignmentSignature(value) {
|
|
1058
|
+
if (value === undefined)
|
|
1059
|
+
return '';
|
|
1060
|
+
return JSON.stringify({
|
|
1061
|
+
status: value.status,
|
|
1062
|
+
checks: value.checks.map(check => ({
|
|
1063
|
+
code: check.code,
|
|
1064
|
+
status: check.status,
|
|
1065
|
+
upstream: check.upstream,
|
|
1066
|
+
downstream: check.downstream,
|
|
1067
|
+
})),
|
|
1068
|
+
});
|
|
1069
|
+
}
|
|
1033
1070
|
function manifestChanges(before, after) {
|
|
1034
1071
|
const fields = ['type', 'main', 'exports', 'engines', 'dependencies', 'optionalDependencies', 'peerDependencies', 'peerDependenciesMeta', 'dsh']
|
|
1035
1072
|
.filter(field => JSON.stringify(before[field]) !== JSON.stringify(after[field]));
|
|
@@ -1076,6 +1113,38 @@ function graphChanges(before, after) {
|
|
|
1076
1113
|
removedUnresolved: [...beforeUnresolved].filter(edge => !afterUnresolved.has(edge)).sort(),
|
|
1077
1114
|
};
|
|
1078
1115
|
}
|
|
1116
|
+
function addObservedPackageVersion(versions, side, name, version) {
|
|
1117
|
+
if (name === undefined || version === undefined || name === '' || version === '')
|
|
1118
|
+
return;
|
|
1119
|
+
const entry = versions.get(name) ?? { before: new Set(), after: new Set() };
|
|
1120
|
+
entry[side].add(version);
|
|
1121
|
+
versions.set(name, entry);
|
|
1122
|
+
}
|
|
1123
|
+
function addSnapshotPackages(versions, side, snapshot) {
|
|
1124
|
+
addObservedPackageVersion(versions, side, snapshot.manifest.name, snapshot.manifest.version);
|
|
1125
|
+
addObservedPackageVersion(versions, side, snapshot.package?.name, snapshot.package?.version);
|
|
1126
|
+
for (const node of snapshot.graph?.nodes ?? [])
|
|
1127
|
+
addObservedPackageVersion(versions, side, node.name, node.version);
|
|
1128
|
+
}
|
|
1129
|
+
/** Convert two complete snapshots into package-name changes for downstream lookup. */
|
|
1130
|
+
function reverseDependencyChanges(before, after) {
|
|
1131
|
+
const versions = new Map();
|
|
1132
|
+
addSnapshotPackages(versions, 'before', before);
|
|
1133
|
+
addSnapshotPackages(versions, 'after', after);
|
|
1134
|
+
return [...versions.entries()]
|
|
1135
|
+
.filter(([, value]) => {
|
|
1136
|
+
const beforeVersions = [...value.before].sort();
|
|
1137
|
+
const afterVersions = [...value.after].sort();
|
|
1138
|
+
return JSON.stringify(beforeVersions) !== JSON.stringify(afterVersions);
|
|
1139
|
+
})
|
|
1140
|
+
.map(([name, value]) => ({
|
|
1141
|
+
ecosystem: 'npm',
|
|
1142
|
+
name,
|
|
1143
|
+
beforeVersions: [...value.before].sort(),
|
|
1144
|
+
afterVersions: [...value.after].sort(),
|
|
1145
|
+
}))
|
|
1146
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
1147
|
+
}
|
|
1079
1148
|
function packageChanged(before, after) {
|
|
1080
1149
|
if (before.package?.name !== after.package?.name || before.package?.version !== after.package?.version)
|
|
1081
1150
|
return true;
|
|
@@ -1109,6 +1178,13 @@ function meaningfulChange(sourceChange, before, after) {
|
|
|
1109
1178
|
if (after.package !== undefined && after.manifest.version !== after.package.version) {
|
|
1110
1179
|
reasons.push(`source/published version drift: source ${after.manifest.name}@${after.manifest.version}, npm ${after.package.name}@${after.package.version}`);
|
|
1111
1180
|
}
|
|
1181
|
+
// Legacy observation files predate the IR. Populate the new field on the
|
|
1182
|
+
// next run without manufacturing a fake upstream change; only compare two
|
|
1183
|
+
// already-established alignment records.
|
|
1184
|
+
if (before.alignment !== undefined && after.alignment !== undefined
|
|
1185
|
+
&& alignmentSignature(before.alignment) !== alignmentSignature(after.alignment)) {
|
|
1186
|
+
reasons.push(`upstream/downstream alignment changed: ${after.alignment?.status ?? 'not observed'}`);
|
|
1187
|
+
}
|
|
1112
1188
|
if (sourceChange.beforeCommit !== sourceChange.afterCommit) {
|
|
1113
1189
|
if (sourceChange.comparison === 'unavailable')
|
|
1114
1190
|
reasons.push('source commit changed and file comparison was unavailable');
|
|
@@ -1122,10 +1198,13 @@ function meaningfulChange(sourceChange, before, after) {
|
|
|
1122
1198
|
function createTaskId(targetId, beforeCommit, afterCommit, packageVersion, graphDigest) {
|
|
1123
1199
|
return `upstream-task-${hash([targetId, beforeCommit, afterCommit, packageVersion ?? '', graphDigest ?? ''].join('\0'))}`;
|
|
1124
1200
|
}
|
|
1125
|
-
function createChange(target, before, after, sourceChange) {
|
|
1201
|
+
function createChange(target, before, after, sourceChange, reverseDependencyIndex) {
|
|
1126
1202
|
const manifest = manifestChanges(before.manifest, after.manifest);
|
|
1127
1203
|
const graph = graphChanges(before.graph, after.graph);
|
|
1128
1204
|
const decision = meaningfulChange(sourceChange, before, after);
|
|
1205
|
+
const reverseDependencyImpacts = reverseDependencyIndex === undefined
|
|
1206
|
+
? []
|
|
1207
|
+
: findReverseDependencyImpacts(reverseDependencyIndex, reverseDependencyChanges(before, after));
|
|
1129
1208
|
const taskId = decision.meaningful
|
|
1130
1209
|
? createTaskId(target.id, before.source.commit, after.source.commit, after.package?.version, after.graph?.digest)
|
|
1131
1210
|
: undefined;
|
|
@@ -1140,6 +1219,7 @@ function createChange(target, before, after, sourceChange) {
|
|
|
1140
1219
|
...(graph === undefined ? {} : { graph }),
|
|
1141
1220
|
reasons: decision.reasons,
|
|
1142
1221
|
meaningful: decision.meaningful,
|
|
1222
|
+
...(reverseDependencyImpacts.length === 0 ? {} : { reverseDependencyImpacts }),
|
|
1143
1223
|
...(taskId === undefined ? {} : { taskId }),
|
|
1144
1224
|
};
|
|
1145
1225
|
}
|
|
@@ -1184,7 +1264,8 @@ export function renderUpstreamChangeAgentPrompt(task) {
|
|
|
1184
1264
|
3. 如果需要项目代码证据,只读取当前项目已经存在的文件;不要为了验证结论安装包或执行上游代码。
|
|
1185
1265
|
4. 区分源码变化、发布包变化、依赖图变化和模型判断。只有明确指出文件、配置、符号或“不知道缺少什么证据”,结论才算完整。
|
|
1186
1266
|
5. breaking_change 只能在有明确入口、导出、DSH bundle、peer/API 或发布说明证据时判断;不因为版本号变化就直接断言。
|
|
1187
|
-
6.
|
|
1267
|
+
6. 如果 change_json.reverseDependencyImpacts 存在,它表示静态反向索引找到的下游插件和依赖路径;把它当作影响范围证据,不要把“可能受影响”写成“已经损坏”。
|
|
1268
|
+
7. 返回严格 JSON 对象,字段只能是 impact、confidence、evidence、breaking_change、dependency_risk、recommended_action、urgency、reasoning_summary。
|
|
1188
1269
|
|
|
1189
1270
|
expected_output:
|
|
1190
1271
|
${JSON.stringify(task.expectedOutput, null, 2)}
|
|
@@ -1485,6 +1566,7 @@ export async function runObserver(config, previousState, options = {}) {
|
|
|
1485
1566
|
let pendingTasks = [...previousState.pendingTasks];
|
|
1486
1567
|
const changes = [];
|
|
1487
1568
|
const baselineTargets = [];
|
|
1569
|
+
const alignmentFindings = [];
|
|
1488
1570
|
const errors = [];
|
|
1489
1571
|
const newTasks = [];
|
|
1490
1572
|
for (const configuredTarget of config.targets) {
|
|
@@ -1494,8 +1576,16 @@ export async function runObserver(config, previousState, options = {}) {
|
|
|
1494
1576
|
nextTargets[configuredTarget.id] = current;
|
|
1495
1577
|
if (before === undefined) {
|
|
1496
1578
|
baselineTargets.push(configuredTarget.id);
|
|
1579
|
+
if (current.alignment !== undefined)
|
|
1580
|
+
alignmentFindings.push({ targetId: configuredTarget.id, alignment: structuredClone(current.alignment) });
|
|
1497
1581
|
continue;
|
|
1498
1582
|
}
|
|
1583
|
+
if (before.alignment === undefined && current.alignment !== undefined) {
|
|
1584
|
+
// Upgrade legacy state into the IR and show its first finding once.
|
|
1585
|
+
// This is evidence for the author, not a fake upstream change and not
|
|
1586
|
+
// a reason to wake the Agent on every subsequent scheduled run.
|
|
1587
|
+
alignmentFindings.push({ targetId: configuredTarget.id, alignment: structuredClone(current.alignment) });
|
|
1588
|
+
}
|
|
1499
1589
|
const sourceChange = before.source.commit === current.source.commit
|
|
1500
1590
|
? {
|
|
1501
1591
|
beforeCommit: before.source.commit,
|
|
@@ -1506,7 +1596,7 @@ export async function runObserver(config, previousState, options = {}) {
|
|
|
1506
1596
|
nonRuntimeFiles: [],
|
|
1507
1597
|
}
|
|
1508
1598
|
: await source.compare(current.source.repository, before.source.commit, current.source.commit);
|
|
1509
|
-
const change = createChange(configuredTarget, before, current, sourceChange);
|
|
1599
|
+
const change = createChange(configuredTarget, before, current, sourceChange, options.reverseDependencyIndex);
|
|
1510
1600
|
if (change.reasons.length === 0)
|
|
1511
1601
|
continue;
|
|
1512
1602
|
let reviewedChange = change;
|
|
@@ -1575,6 +1665,17 @@ export async function runObserver(config, previousState, options = {}) {
|
|
|
1575
1665
|
checkedAt,
|
|
1576
1666
|
targetsChecked: config.targets.length,
|
|
1577
1667
|
baselineTargets,
|
|
1668
|
+
alignmentFindings,
|
|
1669
|
+
...(options.reverseDependencyIndex === undefined ? {} : {
|
|
1670
|
+
reverseDependencyIndex: {
|
|
1671
|
+
observations: options.reverseDependencyIndex.observations,
|
|
1672
|
+
plugins: options.reverseDependencyIndex.plugins.length,
|
|
1673
|
+
dependencies: options.reverseDependencyIndex.dependencies.length,
|
|
1674
|
+
completeObservations: options.reverseDependencyIndex.coverage.completeObservations,
|
|
1675
|
+
incompleteObservations: options.reverseDependencyIndex.coverage.incompleteObservations,
|
|
1676
|
+
unresolvedEdges: options.reverseDependencyIndex.coverage.unresolvedEdges,
|
|
1677
|
+
},
|
|
1678
|
+
}),
|
|
1578
1679
|
changes,
|
|
1579
1680
|
pendingTasks: pendingTasks.map(task => task.id),
|
|
1580
1681
|
pendingTaskDetails: pendingTasks.map(summarizePendingTask),
|
|
@@ -1640,13 +1741,46 @@ function summarizePendingTask(task) {
|
|
|
1640
1741
|
removedEdges: (change.graph?.removedEdges ?? []).slice(0, 24),
|
|
1641
1742
|
addedUnresolved: (change.graph?.addedUnresolved ?? []).slice(0, 24),
|
|
1642
1743
|
removedUnresolved: (change.graph?.removedUnresolved ?? []).slice(0, 24),
|
|
1744
|
+
...(change.reverseDependencyImpacts === undefined ? {} : { reverseDependencyImpacts: structuredClone(change.reverseDependencyImpacts) }),
|
|
1643
1745
|
...(change.artifactReview === undefined ? {} : { artifactReview: structuredClone(change.artifactReview) }),
|
|
1644
1746
|
};
|
|
1645
1747
|
}
|
|
1748
|
+
function renderAlignment(lines, alignment, label) {
|
|
1749
|
+
lines.push(`${label}: ${alignment.status.toUpperCase()}`);
|
|
1750
|
+
for (const check of alignment.checks.filter(item => item.status !== 'aligned')) {
|
|
1751
|
+
lines.push(`${label} ${check.status}: ${check.code} — ${check.summary}`);
|
|
1752
|
+
if (check.upstream !== undefined || check.downstream !== undefined) {
|
|
1753
|
+
lines.push(` Upstream: ${check.upstream ?? 'unknown'}; downstream: ${check.downstream ?? 'unknown'}`);
|
|
1754
|
+
}
|
|
1755
|
+
if (check.remediation !== undefined)
|
|
1756
|
+
lines.push(` Alignment next step: ${check.remediation}`);
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
function renderReverseDependencyImpacts(lines, impacts, label = 'Downstream impact') {
|
|
1760
|
+
for (const impact of impacts.slice(0, 32)) {
|
|
1761
|
+
const from = impact.changedFrom.length === 0 ? 'not observed' : impact.changedFrom.join(', ');
|
|
1762
|
+
const to = impact.changedTo.length === 0 ? 'not observed' : impact.changedTo.join(', ');
|
|
1763
|
+
lines.push(`${label}: ${impact.dependency.name} (${from} → ${to}); ${impact.dependents.length}${impact.truncated ? '+' : ''} downstream plugin(s); coverage ${impact.coverage}`);
|
|
1764
|
+
for (const dependent of impact.dependents.slice(0, 8)) {
|
|
1765
|
+
const project = dependent.project === undefined ? '' : ` [${dependent.project.name}]`;
|
|
1766
|
+
lines.push(` ${dependent.plugin.name}@${dependent.plugin.version}${project} (${dependent.coverage})`);
|
|
1767
|
+
for (const path of dependent.paths.slice(0, 2))
|
|
1768
|
+
lines.push(` ${path.nodes.join(' → ')}`);
|
|
1769
|
+
if (dependent.paths.length > 2)
|
|
1770
|
+
lines.push(` ... ${dependent.paths.length - 2} more path(s)`);
|
|
1771
|
+
}
|
|
1772
|
+
if (impact.truncated)
|
|
1773
|
+
lines.push(' ... additional downstream plugins omitted from this bounded report');
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1646
1776
|
export function renderObserverReport(report) {
|
|
1647
1777
|
const lines = [];
|
|
1648
|
-
|
|
1778
|
+
const alignmentFindings = report.alignmentFindings.filter(item => item.alignment.status !== 'aligned');
|
|
1779
|
+
if (report.changes.length === 0 && report.errors.length === 0 && report.pendingTasks.length === 0 && alignmentFindings.length === 0) {
|
|
1649
1780
|
lines.push(`No meaningful upstream changes. Checked ${report.targetsChecked} target(s); DSH Agent was not called.`);
|
|
1781
|
+
if (report.reverseDependencyIndex !== undefined) {
|
|
1782
|
+
lines.push(`Loaded downstream reverse index: ${report.reverseDependencyIndex.plugins} plugin(s), ${report.reverseDependencyIndex.dependencies} dependency coordinate(s); coverage ${report.reverseDependencyIndex.completeObservations} complete / ${report.reverseDependencyIndex.incompleteObservations} incomplete.`);
|
|
1783
|
+
}
|
|
1650
1784
|
if (report.baselineTargets.length > 0)
|
|
1651
1785
|
lines.push(`Created baseline for: ${report.baselineTargets.join(', ')}.`);
|
|
1652
1786
|
return `${lines.join('\n')}\n`;
|
|
@@ -1655,11 +1789,23 @@ export function renderObserverReport(report) {
|
|
|
1655
1789
|
lines.push('');
|
|
1656
1790
|
lines.push(`Checked at: ${report.checkedAt}`);
|
|
1657
1791
|
lines.push(`Targets: ${report.targetsChecked}; baselines: ${report.baselineTargets.length}; changes: ${report.changes.length}`);
|
|
1792
|
+
if (report.reverseDependencyIndex !== undefined) {
|
|
1793
|
+
lines.push(`Downstream reverse index: ${report.reverseDependencyIndex.plugins} plugin(s), ${report.reverseDependencyIndex.dependencies} dependency coordinate(s); coverage ${report.reverseDependencyIndex.completeObservations} complete / ${report.reverseDependencyIndex.incompleteObservations} incomplete.`);
|
|
1794
|
+
}
|
|
1658
1795
|
lines.push('');
|
|
1659
1796
|
if (report.baselineTargets.length > 0) {
|
|
1660
1797
|
lines.push(`Baseline created: ${report.baselineTargets.join(', ')}`);
|
|
1661
1798
|
lines.push('');
|
|
1662
1799
|
}
|
|
1800
|
+
if (alignmentFindings.length > 0) {
|
|
1801
|
+
lines.push('## Upstream/downstream alignment findings');
|
|
1802
|
+
lines.push('');
|
|
1803
|
+
for (const item of alignmentFindings) {
|
|
1804
|
+
lines.push(`### ${item.targetId}`);
|
|
1805
|
+
renderAlignment(lines, item.alignment, 'Alignment');
|
|
1806
|
+
lines.push('');
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1663
1809
|
for (const change of report.changes) {
|
|
1664
1810
|
lines.push(`## ${change.targetId} (${change.ecosystem})`);
|
|
1665
1811
|
lines.push('');
|
|
@@ -1668,6 +1814,8 @@ export function renderObserverReport(report) {
|
|
|
1668
1814
|
lines.push(`Package: ${displayPackage(change.previous.package)} → ${displayPackage(change.current.package)}`);
|
|
1669
1815
|
lines.push(`Graph: ${displayGraph(change.previous.graph)} → ${displayGraph(change.current.graph)}`);
|
|
1670
1816
|
lines.push(`Meaningful: ${change.meaningful ? 'yes' : 'no'}`);
|
|
1817
|
+
if (change.current.alignment !== undefined)
|
|
1818
|
+
renderAlignment(lines, change.current.alignment, 'Upstream/downstream alignment');
|
|
1671
1819
|
for (const warning of change.current.warnings ?? [])
|
|
1672
1820
|
lines.push(`Warning: ${warning}`);
|
|
1673
1821
|
if (change.source.changedFiles.length > 0)
|
|
@@ -1691,6 +1839,8 @@ export function renderObserverReport(report) {
|
|
|
1691
1839
|
lines.push(`Coverage change: resolved dependency edges: ${removedUnresolved.slice(0, 8).join(', ')}`);
|
|
1692
1840
|
}
|
|
1693
1841
|
}
|
|
1842
|
+
if (change.reverseDependencyImpacts !== undefined)
|
|
1843
|
+
renderReverseDependencyImpacts(lines, change.reverseDependencyImpacts);
|
|
1694
1844
|
if (change.reasons.length > 0)
|
|
1695
1845
|
lines.push(`Reasons: ${change.reasons.join('; ')}`);
|
|
1696
1846
|
if (change.current.package !== undefined) {
|
|
@@ -1757,6 +1907,8 @@ export function renderObserverReport(report) {
|
|
|
1757
1907
|
lines.push(`New unresolved dependency edges: ${task.addedUnresolved.join(', ')}`);
|
|
1758
1908
|
if (task.removedUnresolved.length > 0)
|
|
1759
1909
|
lines.push(`Resolved dependency edges: ${task.removedUnresolved.join(', ')}`);
|
|
1910
|
+
if (task.reverseDependencyImpacts !== undefined)
|
|
1911
|
+
renderReverseDependencyImpacts(lines, task.reverseDependencyImpacts, 'Downstream impact');
|
|
1760
1912
|
if (task.reasons.length > 0)
|
|
1761
1913
|
lines.push(`Reasons: ${task.reasons.join('; ')}`);
|
|
1762
1914
|
if (task.artifactReview !== undefined) {
|