-
Notifications
You must be signed in to change notification settings - Fork 245
Expand file tree
/
Copy pathcatalogPlanDependencies.ts
More file actions
103 lines (96 loc) · 2.82 KB
/
Copy pathcatalogPlanDependencies.ts
File metadata and controls
103 lines (96 loc) · 2.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
import {
type CatalogPlanParams,
ErrCode,
type FullProduct,
RecaseError,
} from "@autumn/shared";
const referenceKey = (id: string, version?: number) =>
version === undefined ? id : `${id}@${version}`;
const targetId = (plan: CatalogPlanParams) => plan.new_plan_id ?? plan.plan_id;
const dependenciesForPlan = (plan: CatalogPlanParams) =>
(plan.licenses ?? []).map((license) => ({
id: license.license_plan_id,
version: license.version,
}));
export const sortCatalogPlansByDependencies = (
plans: CatalogPlanParams[],
): CatalogPlanParams[] => {
const byReference = new Map<string, CatalogPlanParams>();
for (const plan of plans) {
byReference.set(referenceKey(targetId(plan), plan.version), plan);
const latestKey = targetId(plan);
const latest = byReference.get(latestKey);
if (!latest || (plan.version ?? 0) > (latest.version ?? 0)) {
byReference.set(latestKey, plan);
}
}
const visiting = new Set<string>();
const visited = new Set<string>();
const sorted: CatalogPlanParams[] = [];
const visit = (plan: CatalogPlanParams) => {
const key = referenceKey(targetId(plan), plan.version);
if (visiting.has(key)) {
throw new RecaseError({
message: "Plan dependency cycle detected.",
code: ErrCode.InvalidRequest,
statusCode: 400,
});
}
if (visited.has(key)) return;
visiting.add(key);
if (plan.version !== undefined && plan.version > 1) {
const previous = byReference.get(
referenceKey(targetId(plan), plan.version - 1),
);
if (previous) visit(previous);
}
for (const reference of dependenciesForPlan(plan)) {
const dependency =
byReference.get(referenceKey(reference.id, reference.version)) ??
byReference.get(reference.id);
if (dependency && dependency !== plan) visit(dependency);
}
visiting.delete(key);
visited.add(key);
sorted.push(plan);
};
for (const plan of plans) visit(plan);
return sorted;
};
export const validateCatalogPlanVersionTargets = ({
plans,
products,
}: {
plans: CatalogPlanParams[];
products: FullProduct[];
}) => {
const existing = new Set(
products.map((product) => referenceKey(product.id, product.version)),
);
const latestById = new Map<string, number>();
for (const product of products) {
latestById.set(
product.id,
Math.max(latestById.get(product.id) ?? 0, product.version),
);
}
for (const plan of sortCatalogPlansByDependencies(plans)) {
const id = targetId(plan);
if (
plan.version === undefined ||
existing.has(referenceKey(id, plan.version))
) {
continue;
}
const expected = (latestById.get(id) ?? 0) + 1;
if (plan.version !== expected) {
throw new RecaseError({
message: `Plan ${id} version must be ${expected}, received ${plan.version}.`,
code: ErrCode.InvalidRequest,
statusCode: 400,
});
}
existing.add(referenceKey(id, plan.version));
latestById.set(id, plan.version);
}
};