@tallyrelease/core 0.2.0-beta.8 → 0.3.0-beta.5
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/CHANGELOG.md +14 -0
- package/dist/base64.d.ts +4 -0
- package/dist/base64.js +40 -0
- package/dist/changelog.d.ts +65 -0
- package/dist/changelog.js +259 -0
- package/dist/config.d.ts +22 -0
- package/dist/conventional.js +6 -0
- package/dist/discover.d.ts +2 -0
- package/dist/discover.js +4 -4
- package/dist/floating.d.ts +14 -0
- package/dist/floating.js +44 -0
- package/dist/index.d.ts +10 -1
- package/dist/index.js +6 -0
- package/dist/marker.d.ts +17 -0
- package/dist/marker.js +48 -0
- package/dist/plan.d.ts +2 -0
- package/dist/plan.js +1 -0
- package/dist/plugin.d.ts +4 -0
- package/dist/propagate.d.ts +7 -1
- package/dist/propagate.js +1 -1
- package/dist/release-items.d.ts +35 -0
- package/dist/release-items.js +120 -0
- package/dist/release.d.ts +13 -2
- package/dist/release.js +19 -1
- package/dist/released.d.ts +25 -0
- package/dist/released.js +104 -0
- package/dist/tags.d.ts +2 -0
- package/dist/tags.js +4 -0
- package/dist/validate.d.ts +2 -1
- package/dist/validate.js +8 -1
- package/dist/version-files.d.ts +34 -0
- package/dist/version-files.js +247 -0
- package/dist/workspace.d.ts +3 -1
- package/dist/workspace.js +25 -1
- package/package.json +1 -1
package/dist/marker.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { decodeBase64, encodeBase64 } from "./base64.js";
|
|
2
|
+
const MARKER = /<!-- tallyrelease-plan:v1 ([A-Za-z0-9+/=]*) -->/u;
|
|
3
|
+
const isRelease = (value) => {
|
|
4
|
+
const r = value;
|
|
5
|
+
return (typeof r === "object" &&
|
|
6
|
+
r !== null &&
|
|
7
|
+
typeof r.id === "string" &&
|
|
8
|
+
typeof r.version === "string" &&
|
|
9
|
+
typeof r.tag === "string");
|
|
10
|
+
};
|
|
11
|
+
/** `<!-- tallyrelease-plan:v1 <base64 json> -->` holding only the marker fields. */
|
|
12
|
+
export const encodeMarker = ({ head, releaseCommit, releases, }) => {
|
|
13
|
+
const json = JSON.stringify({
|
|
14
|
+
head,
|
|
15
|
+
releaseCommit,
|
|
16
|
+
releases: releases.map(({ id, version, tag }) => ({ id, version, tag })),
|
|
17
|
+
});
|
|
18
|
+
return `<!-- tallyrelease-plan:v1 ${encodeBase64(json)} -->`;
|
|
19
|
+
};
|
|
20
|
+
/** The marker in a PR body; undefined when missing or malformed. */
|
|
21
|
+
export const decodeMarker = (body) => {
|
|
22
|
+
const data = MARKER.exec(body)?.[1];
|
|
23
|
+
const json = data === undefined ? undefined : decodeBase64(data);
|
|
24
|
+
if (json === undefined) {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
let value;
|
|
28
|
+
try {
|
|
29
|
+
value = JSON.parse(json);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
const m = value;
|
|
35
|
+
if (typeof m !== "object" ||
|
|
36
|
+
m === null ||
|
|
37
|
+
typeof m.head !== "string" ||
|
|
38
|
+
typeof m.releaseCommit !== "string" ||
|
|
39
|
+
!Array.isArray(m.releases) ||
|
|
40
|
+
!m.releases.every(isRelease)) {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
head: m.head,
|
|
45
|
+
releaseCommit: m.releaseCommit,
|
|
46
|
+
releases: m.releases.map(({ id, version, tag }) => ({ id, version, tag })),
|
|
47
|
+
};
|
|
48
|
+
};
|
package/dist/plan.d.ts
CHANGED
package/dist/plan.js
CHANGED
|
@@ -34,6 +34,7 @@ const planChanges = (intent) => (intent?.changes ?? []).map(({ change, via }) =>
|
|
|
34
34
|
breaking: change.breaking,
|
|
35
35
|
...(change.scope === undefined ? {} : { scope: change.scope }),
|
|
36
36
|
subject: change.subject,
|
|
37
|
+
...(change.notes === undefined ? {} : { notes: change.notes }),
|
|
37
38
|
...(change.pr === undefined ? {} : { pr: change.pr }),
|
|
38
39
|
...(via === undefined ? {} : { via }),
|
|
39
40
|
...(releaseAs === undefined ? {} : { releaseAs }),
|
package/dist/plugin.d.ts
CHANGED
|
@@ -125,6 +125,8 @@ export interface VersionFile {
|
|
|
125
125
|
}
|
|
126
126
|
export interface WriteCtx {
|
|
127
127
|
unit: Unit;
|
|
128
|
+
/** All units of the workspace. */
|
|
129
|
+
units: readonly Unit[];
|
|
128
130
|
/** Unit id to version in the current plan. */
|
|
129
131
|
versions: ReadonlyMap<string, string>;
|
|
130
132
|
}
|
|
@@ -183,6 +185,8 @@ export interface Change {
|
|
|
183
185
|
scope?: string;
|
|
184
186
|
subject: string;
|
|
185
187
|
body?: string;
|
|
188
|
+
/** `BREAKING CHANGE` footer text. */
|
|
189
|
+
notes?: string;
|
|
186
190
|
releaseAs?: ReleaseAsValue[];
|
|
187
191
|
pr?: number;
|
|
188
192
|
date?: string;
|
package/dist/propagate.d.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import type { Graph } from "./graph.js";
|
|
2
|
-
import type { BumpLevel, Unit } from "./plugin.js";
|
|
2
|
+
import type { BumpLevel, Edge, Unit } from "./plugin.js";
|
|
3
|
+
/**
|
|
4
|
+
* Edges between released units, `to` to `from` to `pinned`. A path through
|
|
5
|
+
* unreleased units counts as an edge with the first hop's `pinned`; several
|
|
6
|
+
* paths are OR-combined.
|
|
7
|
+
*/
|
|
8
|
+
export declare const releasedEdges: (edges: readonly Edge[], released: ReadonlySet<string>) => Map<string, Map<string, boolean>>;
|
|
3
9
|
/** Bump state of a released unit. */
|
|
4
10
|
export interface Pending {
|
|
5
11
|
readonly bump: BumpLevel | null;
|
package/dist/propagate.js
CHANGED
|
@@ -4,7 +4,7 @@ import { maxLevel, uniqueSorted } from "./util.js";
|
|
|
4
4
|
* unreleased units counts as an edge with the first hop's `pinned`; several
|
|
5
5
|
* paths are OR-combined.
|
|
6
6
|
*/
|
|
7
|
-
const releasedEdges = (edges, released) => {
|
|
7
|
+
export const releasedEdges = (edges, released) => {
|
|
8
8
|
const out = new Map();
|
|
9
9
|
for (const e of edges) {
|
|
10
10
|
const list = out.get(e.from) ?? [];
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ReleasePrMode } from "./config.js";
|
|
2
|
+
import type { MarkerRelease } from "./marker.js";
|
|
3
|
+
import type { Plan } from "./plan.js";
|
|
4
|
+
import type { Workspace } from "./workspace.js";
|
|
5
|
+
/** A group or an ungrouped released unit; releases as one. */
|
|
6
|
+
export interface ReleaseItem {
|
|
7
|
+
name: string;
|
|
8
|
+
kind: "unit" | "group";
|
|
9
|
+
mode: ReleasePrMode;
|
|
10
|
+
/** Sorted member ids; the unit itself for kind `unit`. */
|
|
11
|
+
units: string[];
|
|
12
|
+
/** Stable `next`. */
|
|
13
|
+
version: string;
|
|
14
|
+
/** Version tag of each member, in `units` order. */
|
|
15
|
+
tags: {
|
|
16
|
+
id: string;
|
|
17
|
+
tag: string;
|
|
18
|
+
}[];
|
|
19
|
+
}
|
|
20
|
+
/** Pending release items (stable `next`) in plan unit order. */
|
|
21
|
+
export declare const releaseItems: (workspace: Workspace, plan: Plan) => ReleaseItem[];
|
|
22
|
+
/** One release per item member. */
|
|
23
|
+
export declare const releaseEntries: (items: readonly ReleaseItem[]) => MarkerRelease[];
|
|
24
|
+
/**
|
|
25
|
+
* True when every marker release equals the plan's release of that unit and
|
|
26
|
+
* every item the marker touches is complete. Pending items outside the marker
|
|
27
|
+
* are ignored; an empty marker never equals.
|
|
28
|
+
*/
|
|
29
|
+
export declare const planEquals: (marker: readonly MarkerRelease[], plan: Plan, workspace: Workspace) => boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Release PR checks: `releasePr` only on ungrouped units (`E_CONFIG`), item
|
|
32
|
+
* names (`E_RELEASE_ITEM_NAME`) and patch-propagating edges between items in
|
|
33
|
+
* different release PRs (`E_RELEASE_PR_SPLIT_EDGE`).
|
|
34
|
+
*/
|
|
35
|
+
export declare const validateReleaseItems: (workspace: Workspace) => void;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { TallyError } from "./errors.js";
|
|
2
|
+
import { releasedEdges } from "./propagate.js";
|
|
3
|
+
import { renderTag } from "./tags.js";
|
|
4
|
+
/** Item of each released unit, by unit id. */
|
|
5
|
+
const itemsByUnit = (workspace) => {
|
|
6
|
+
const { releasePr } = workspace.release;
|
|
7
|
+
const byUnit = new Map();
|
|
8
|
+
for (const { name, members } of workspace.groups) {
|
|
9
|
+
const item = {
|
|
10
|
+
name,
|
|
11
|
+
kind: "group",
|
|
12
|
+
mode: releasePr.groups.get(name) ?? releasePr.default,
|
|
13
|
+
units: [...members],
|
|
14
|
+
};
|
|
15
|
+
for (const id of members) {
|
|
16
|
+
byUnit.set(id, item);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
for (const { id, version } of workspace.units) {
|
|
20
|
+
if (version !== "none" && !byUnit.has(id)) {
|
|
21
|
+
byUnit.set(id, {
|
|
22
|
+
name: id,
|
|
23
|
+
kind: "unit",
|
|
24
|
+
mode: releasePr.units.get(id) ?? releasePr.default,
|
|
25
|
+
units: [id],
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return byUnit;
|
|
30
|
+
};
|
|
31
|
+
/** Why `name` cannot name a release item, else undefined. */
|
|
32
|
+
const itemNameProblem = (name) => {
|
|
33
|
+
if (name === "shared") {
|
|
34
|
+
return "is reserved for the shared release PR";
|
|
35
|
+
}
|
|
36
|
+
return name.startsWith(".") ||
|
|
37
|
+
name.endsWith(".") ||
|
|
38
|
+
name.endsWith(".lock") ||
|
|
39
|
+
name.includes("..")
|
|
40
|
+
? "does not form a valid branch name"
|
|
41
|
+
: undefined;
|
|
42
|
+
};
|
|
43
|
+
/** Pending release items (stable `next`) in plan unit order. */
|
|
44
|
+
export const releaseItems = (workspace, plan) => {
|
|
45
|
+
const units = new Map(workspace.units.map((u) => [u.id, u]));
|
|
46
|
+
const byUnit = itemsByUnit(workspace);
|
|
47
|
+
const items = new Map();
|
|
48
|
+
for (const { id, next } of plan.units) {
|
|
49
|
+
const item = byUnit.get(id);
|
|
50
|
+
if (next === null || !item || items.has(item.name)) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
items.set(item.name, {
|
|
54
|
+
...item,
|
|
55
|
+
version: next,
|
|
56
|
+
tags: item.units.flatMap((u) => {
|
|
57
|
+
const unit = units.get(u);
|
|
58
|
+
return unit ? [{ id: u, tag: renderTag(unit, next) }] : [];
|
|
59
|
+
}),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
return [...items.values()];
|
|
63
|
+
};
|
|
64
|
+
/** One release per item member. */
|
|
65
|
+
export const releaseEntries = (items) => items.flatMap((item) => item.tags.map(({ id, tag }) => ({ id, version: item.version, tag })));
|
|
66
|
+
/**
|
|
67
|
+
* True when every marker release equals the plan's release of that unit and
|
|
68
|
+
* every item the marker touches is complete. Pending items outside the marker
|
|
69
|
+
* are ignored; an empty marker never equals.
|
|
70
|
+
*/
|
|
71
|
+
export const planEquals = (marker, plan, workspace) => {
|
|
72
|
+
const items = releaseItems(workspace, plan);
|
|
73
|
+
const entries = new Map(releaseEntries(items).map((e) => [e.id, e]));
|
|
74
|
+
const ids = new Set(marker.map((r) => r.id));
|
|
75
|
+
return (marker.length > 0 &&
|
|
76
|
+
marker.every((r) => {
|
|
77
|
+
const entry = entries.get(r.id);
|
|
78
|
+
return entry?.version === r.version && entry.tag === r.tag;
|
|
79
|
+
}) &&
|
|
80
|
+
items.every((item) => item.units.every((id) => ids.has(id)) ||
|
|
81
|
+
!item.units.some((id) => ids.has(id))));
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Release PR checks: `releasePr` only on ungrouped units (`E_CONFIG`), item
|
|
85
|
+
* names (`E_RELEASE_ITEM_NAME`) and patch-propagating edges between items in
|
|
86
|
+
* different release PRs (`E_RELEASE_PR_SPLIT_EDGE`).
|
|
87
|
+
*/
|
|
88
|
+
export const validateReleaseItems = (workspace) => {
|
|
89
|
+
const byUnit = itemsByUnit(workspace);
|
|
90
|
+
for (const id of workspace.release.releasePr.units.keys()) {
|
|
91
|
+
const item = byUnit.get(id);
|
|
92
|
+
if (item?.kind === "group") {
|
|
93
|
+
throw new TallyError("E_CONFIG", `unit "${id}" sets releasePr but is in group "${item.name}"; set it on the group`, { unit: id, group: item.name });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const groups = new Set(workspace.groups.map((g) => g.name));
|
|
97
|
+
for (const item of new Set(byUnit.values())) {
|
|
98
|
+
const problem = item.kind === "unit" && groups.has(item.name)
|
|
99
|
+
? "is also a group name"
|
|
100
|
+
: itemNameProblem(item.name);
|
|
101
|
+
if (problem !== undefined) {
|
|
102
|
+
throw new TallyError("E_RELEASE_ITEM_NAME", `release item "${item.name}" ${problem}`, { item: item.name });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const policies = new Map(workspace.units.map((u) => [u.id, u.propagate]));
|
|
106
|
+
const pr = (item) => item.mode === "shared" ? "shared" : item.name;
|
|
107
|
+
const edges = releasedEdges(workspace.graph.edges, new Set(byUnit.keys()));
|
|
108
|
+
for (const [to, incoming] of edges) {
|
|
109
|
+
const policy = policies.get(to) ?? "auto";
|
|
110
|
+
for (const [from, pinned] of incoming) {
|
|
111
|
+
const a = byUnit.get(from);
|
|
112
|
+
const b = byUnit.get(to);
|
|
113
|
+
const patch = policy === "auto" ? pinned : policy === "patch";
|
|
114
|
+
if (!a || !b || !patch || pr(a) === pr(b)) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
throw new TallyError("E_RELEASE_PR_SPLIT_EDGE", `edge "${from}" -> "${to}" propagates a patch between release items "${a.name}" and "${b.name}" in different release PRs`, { from, to, items: [a.name, b.name] });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
};
|
package/dist/release.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { TallyConfig } from "./config.js";
|
|
1
|
+
import type { ReleasePrMode, TallyConfig, VersionTemplate } from "./config.js";
|
|
2
2
|
import type { Warning } from "./errors.js";
|
|
3
3
|
import type { GroupFacts, RangeFacts, UnitFacts } from "./facts.js";
|
|
4
4
|
import type { BumpLevel, Change, Unit, VersionScheme } from "./plugin.js";
|
|
@@ -38,6 +38,17 @@ export interface ReleaseSettings {
|
|
|
38
38
|
readonly absorbed: "patch" | "minor";
|
|
39
39
|
/** In config order. */
|
|
40
40
|
readonly branches: readonly BranchRule[];
|
|
41
|
+
readonly releasePr: {
|
|
42
|
+
readonly default: ReleasePrMode;
|
|
43
|
+
/** Set by unit overlays, by unit id. */
|
|
44
|
+
readonly units: ReadonlyMap<string, ReleasePrMode>;
|
|
45
|
+
/** Set by groups, by group name. */
|
|
46
|
+
readonly groups: ReadonlyMap<string, ReleasePrMode>;
|
|
47
|
+
};
|
|
48
|
+
/** Floating tag formats. */
|
|
49
|
+
readonly floating: readonly string[];
|
|
50
|
+
/** Version file templates by unit id. */
|
|
51
|
+
readonly templates: ReadonlyMap<string, readonly VersionTemplate[]>;
|
|
41
52
|
}
|
|
42
53
|
/**
|
|
43
54
|
* A Change of a released unit; `via` names the first absorbed unit (by id) a
|
|
@@ -93,7 +104,7 @@ export interface ReleaseInput<V> {
|
|
|
93
104
|
*/
|
|
94
105
|
export declare const reach: (id: string, dependencies: ReadonlyMap<string, readonly string[]>, released: ReadonlySet<string>) => Set<string>;
|
|
95
106
|
/** Applies the defaults to `bumps` and `branches`. */
|
|
96
|
-
export declare const releaseSettings: (config: TallyConfig) => ReleaseSettings;
|
|
107
|
+
export declare const releaseSettings: (config: TallyConfig, units: readonly Unit[]) => ReleaseSettings;
|
|
97
108
|
/**
|
|
98
109
|
* Warns for qualified Release-As values naming unknown or unreleased units and
|
|
99
110
|
* for plain values on commits attributed to no released unit.
|
package/dist/release.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { normalizeRoot } from "./discover.js";
|
|
1
2
|
import { compare, maxLevel, uniqueSorted } from "./util.js";
|
|
2
3
|
const DEFAULT_TYPES = {
|
|
3
4
|
feat: "minor",
|
|
@@ -189,7 +190,7 @@ export const reach = (id, dependencies, released) => {
|
|
|
189
190
|
return found;
|
|
190
191
|
};
|
|
191
192
|
/** Applies the defaults to `bumps` and `branches`. */
|
|
192
|
-
export const releaseSettings = (config) => ({
|
|
193
|
+
export const releaseSettings = (config, units) => ({
|
|
193
194
|
bumps: new Map(Object.entries({ ...DEFAULT_TYPES, ...config.bumps?.types })),
|
|
194
195
|
absorbed: config.bumps?.absorbed ?? "patch",
|
|
195
196
|
preMajor: {
|
|
@@ -203,6 +204,23 @@ export const releaseSettings = (config) => ({
|
|
|
203
204
|
unit: b.unit ?? null,
|
|
204
205
|
group: b.group ?? null,
|
|
205
206
|
})),
|
|
207
|
+
releasePr: {
|
|
208
|
+
default: config.defaults?.releasePr ?? "shared",
|
|
209
|
+
units: new Map(Object.entries(config.units ?? {}).flatMap(([root, overlay]) => {
|
|
210
|
+
const unit = units.find((u) => u.root === normalizeRoot(root));
|
|
211
|
+
return unit && overlay.releasePr
|
|
212
|
+
? [[unit.id, overlay.releasePr]]
|
|
213
|
+
: [];
|
|
214
|
+
})),
|
|
215
|
+
groups: new Map(Object.entries(config.groups ?? {}).flatMap(([name, group]) => group.releasePr ? [[name, group.releasePr]] : [])),
|
|
216
|
+
},
|
|
217
|
+
floating: config.tags?.floating ?? [],
|
|
218
|
+
templates: new Map(Object.entries(config.units ?? {}).flatMap(([root, overlay]) => {
|
|
219
|
+
const unit = units.find((u) => u.root === normalizeRoot(root));
|
|
220
|
+
return unit && overlay.versionFiles
|
|
221
|
+
? [[unit.id, overlay.versionFiles]]
|
|
222
|
+
: [];
|
|
223
|
+
})),
|
|
206
224
|
});
|
|
207
225
|
/**
|
|
208
226
|
* Warns for qualified Release-As values naming unknown or unreleased units and
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Plan, ReleasedUnit } from "./plan.js";
|
|
2
|
+
import type { GitReader } from "./plugin.js";
|
|
3
|
+
import type { Workspace } from "./workspace.js";
|
|
4
|
+
/**
|
|
5
|
+
* `plan` with `released`: stable unit tags whose commit is on HEAD's
|
|
6
|
+
* first-parent chain and not an ancestor of `build.base`. With a null base
|
|
7
|
+
* only tags at HEAD, warning `W_RELEASED_NO_BASE` when earlier releases on the
|
|
8
|
+
* chain are left out.
|
|
9
|
+
*/
|
|
10
|
+
export declare const withReleased: (workspace: Workspace, plan: Plan, { git }: {
|
|
11
|
+
git: GitReader;
|
|
12
|
+
}) => Promise<Plan>;
|
|
13
|
+
/**
|
|
14
|
+
* Released entries of the named tags, in `plan.released` order; names that
|
|
15
|
+
* are not stable tags of a released unit are skipped. Callers add the tags a
|
|
16
|
+
* reconcile created by passing the union of those and `plan.released` tags.
|
|
17
|
+
*/
|
|
18
|
+
export declare const releasedFromTags: (workspace: Workspace, plan: Plan, { git, tags }: {
|
|
19
|
+
git: GitReader;
|
|
20
|
+
tags: readonly string[];
|
|
21
|
+
}) => Promise<ReleasedUnit[]>;
|
|
22
|
+
/** Tag of the highest stable version of each released unit with one. */
|
|
23
|
+
export declare const highestReleases: (workspace: Workspace, { git }: {
|
|
24
|
+
git: GitReader;
|
|
25
|
+
}) => Promise<Set<string>>;
|
package/dist/released.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { renderFloatingTags } from "./floating.js";
|
|
2
|
+
import { schemeRegistry } from "./schemes.js";
|
|
3
|
+
import { matchUnitTags } from "./tags.js";
|
|
4
|
+
import { compare } from "./util.js";
|
|
5
|
+
const stable = (scheme, version) => {
|
|
6
|
+
const v = scheme.parse(version);
|
|
7
|
+
return v !== undefined && !scheme.isPrerelease(v);
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Released entries of the stable tags `keep` accepts: oldest release commit
|
|
11
|
+
* on the first-parent chain first, then plan unit order (topological), then
|
|
12
|
+
* tag name.
|
|
13
|
+
*/
|
|
14
|
+
const releasedEntries = async (workspace, plan, { git, keep, }) => {
|
|
15
|
+
const matched = matchUnitTags(workspace.units, await git.tags(), schemeRegistry(workspace.schemes));
|
|
16
|
+
const line = plan.line?.kind ?? "default";
|
|
17
|
+
const candidates = workspace.units.flatMap((unit) => {
|
|
18
|
+
const m = matched.get(unit.id);
|
|
19
|
+
if (!m) {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
const names = m.tags.map((t) => t.name);
|
|
23
|
+
return m.tags
|
|
24
|
+
.filter((tag) => stable(m.scheme, tag.version))
|
|
25
|
+
.map((tag) => ({
|
|
26
|
+
id: unit.id,
|
|
27
|
+
path: unit.root,
|
|
28
|
+
version: tag.version,
|
|
29
|
+
tag: tag.name,
|
|
30
|
+
floatingTags: renderFloatingTags(unit, tag.version, {
|
|
31
|
+
tags: names,
|
|
32
|
+
scheme: m.scheme,
|
|
33
|
+
formats: workspace.release.floating,
|
|
34
|
+
}),
|
|
35
|
+
releaseSha: tag.sha,
|
|
36
|
+
catchUp: tag.sha !== plan.head,
|
|
37
|
+
pr: null,
|
|
38
|
+
line,
|
|
39
|
+
}));
|
|
40
|
+
});
|
|
41
|
+
const kept = await Promise.all(candidates.map(async (r) => await keep({ name: r.tag, sha: r.releaseSha, version: r.version })));
|
|
42
|
+
const released = candidates.filter((_, i) => kept[i]);
|
|
43
|
+
const shas = [...new Set(released.map((r) => r.releaseSha))];
|
|
44
|
+
const depths = await Promise.all(shas.map(async (sha) => await git.countFirstParent(null, sha)));
|
|
45
|
+
const depth = new Map(shas.map((sha, i) => [sha, depths[i] ?? 0]));
|
|
46
|
+
const rank = new Map(workspace.graph.order.map((id, i) => [id, i]));
|
|
47
|
+
return released.sort((a, b) => (depth.get(a.releaseSha) ?? 0) - (depth.get(b.releaseSha) ?? 0) ||
|
|
48
|
+
(rank.get(a.id) ?? 0) - (rank.get(b.id) ?? 0) ||
|
|
49
|
+
compare(a.tag, b.tag));
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* `plan` with `released`: stable unit tags whose commit is on HEAD's
|
|
53
|
+
* first-parent chain and not an ancestor of `build.base`. With a null base
|
|
54
|
+
* only tags at HEAD, warning `W_RELEASED_NO_BASE` when earlier releases on the
|
|
55
|
+
* chain are left out.
|
|
56
|
+
*/
|
|
57
|
+
export const withReleased = async (workspace, plan, { git }) => {
|
|
58
|
+
const { head } = plan;
|
|
59
|
+
const { base } = plan.build;
|
|
60
|
+
const atBase = base === null
|
|
61
|
+
? new Set()
|
|
62
|
+
: new Set((await git.tagsMerged(base)).map((t) => t.name));
|
|
63
|
+
let omitted = 0;
|
|
64
|
+
const released = await releasedEntries(workspace, plan, {
|
|
65
|
+
git,
|
|
66
|
+
keep: async (tag) => {
|
|
67
|
+
if (atBase.has(tag.name) ||
|
|
68
|
+
!(await git.isFirstParentAncestor(tag.sha, head))) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
if (base === null && tag.sha !== head) {
|
|
72
|
+
omitted += 1;
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
return true;
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
const warnings = omitted === 0
|
|
79
|
+
? []
|
|
80
|
+
: [
|
|
81
|
+
{
|
|
82
|
+
code: "W_RELEASED_NO_BASE",
|
|
83
|
+
message: "the build base is unknown; released leaves out earlier releases on the first-parent chain",
|
|
84
|
+
},
|
|
85
|
+
];
|
|
86
|
+
return { ...plan, released, warnings: [...plan.warnings, ...warnings] };
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Released entries of the named tags, in `plan.released` order; names that
|
|
90
|
+
* are not stable tags of a released unit are skipped. Callers add the tags a
|
|
91
|
+
* reconcile created by passing the union of those and `plan.released` tags.
|
|
92
|
+
*/
|
|
93
|
+
export const releasedFromTags = async (workspace, plan, { git, tags }) => {
|
|
94
|
+
const names = new Set(tags);
|
|
95
|
+
return await releasedEntries(workspace, plan, {
|
|
96
|
+
git,
|
|
97
|
+
keep: (tag) => names.has(tag.name),
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
/** Tag of the highest stable version of each released unit with one. */
|
|
101
|
+
export const highestReleases = async (workspace, { git }) => {
|
|
102
|
+
const matched = matchUnitTags(workspace.units, await git.tags(), schemeRegistry(workspace.schemes));
|
|
103
|
+
return new Set([...matched.values()].flatMap(({ scheme, tags }) => tags.findLast((t) => stable(scheme, t.version))?.name ?? []));
|
|
104
|
+
};
|
package/dist/tags.d.ts
CHANGED
|
@@ -15,6 +15,8 @@ export interface UnitTag {
|
|
|
15
15
|
}
|
|
16
16
|
/** Compiles `format`; literal parts and `{id}` are escaped, `{version}` must occur once. */
|
|
17
17
|
export declare const compileTagFormat: (format: string, unitId: string) => TagFormat;
|
|
18
|
+
/** The unit's tag for `version`. */
|
|
19
|
+
export declare const renderTag: (unit: Pick<Unit, "id" | "tagFormat">, version: string) => string;
|
|
18
20
|
/** Tags of a released unit and its scheme. */
|
|
19
21
|
export interface UnitTags {
|
|
20
22
|
readonly scheme: VersionScheme<any>;
|
package/dist/tags.js
CHANGED
|
@@ -15,6 +15,10 @@ export const compileTagFormat = (format, unitId) => {
|
|
|
15
15
|
match: (tag) => re.exec(tag)?.[1],
|
|
16
16
|
};
|
|
17
17
|
};
|
|
18
|
+
/** The unit's tag for `version`. */
|
|
19
|
+
export const renderTag = (unit, version) => unit.tagFormat
|
|
20
|
+
.replaceAll("{id}", unit.id)
|
|
21
|
+
.replace("{version}", () => version);
|
|
18
22
|
/**
|
|
19
23
|
* Tags of each released unit, sorted by scheme order, then name. Unreleased
|
|
20
24
|
* units never match. Throws `E_TAG_FORMAT_OVERLAP` if two released units render
|
package/dist/validate.d.ts
CHANGED
|
@@ -4,7 +4,8 @@ import type { Workspace } from "./workspace.js";
|
|
|
4
4
|
/**
|
|
5
5
|
* Release-only checks, never run by `affected`: scheme registry, schemes, tag
|
|
6
6
|
* formats and initial versions of released units, group members, branch rules,
|
|
7
|
-
* their absolute check targets and channels
|
|
7
|
+
* their absolute check targets and channels, release items, floating tag
|
|
8
|
+
* formats and version file templates. Returns the scheme registry.
|
|
8
9
|
*/
|
|
9
10
|
export declare const validateRelease: (workspace: Workspace) => ReadonlyMap<string, VersionScheme<any>>;
|
|
10
11
|
/**
|
package/dist/validate.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { validateBranches } from "./branches.js";
|
|
2
2
|
import { TallyError } from "./errors.js";
|
|
3
|
+
import { validateFloating } from "./floating.js";
|
|
4
|
+
import { validateReleaseItems } from "./release-items.js";
|
|
3
5
|
import { schemeRegistry } from "./schemes.js";
|
|
4
6
|
import { compileTagFormat, matchUnitTags, noMatchingTag } from "./tags.js";
|
|
5
7
|
import { uniqueSorted } from "./util.js";
|
|
8
|
+
import { validateTemplates } from "./version-files.js";
|
|
6
9
|
/**
|
|
7
10
|
* Release-only checks, never run by `affected`: scheme registry, schemes, tag
|
|
8
11
|
* formats and initial versions of released units, group members, branch rules,
|
|
9
|
-
* their absolute check targets and channels
|
|
12
|
+
* their absolute check targets and channels, release items, floating tag
|
|
13
|
+
* formats and version file templates. Returns the scheme registry.
|
|
10
14
|
*/
|
|
11
15
|
export const validateRelease = (workspace) => {
|
|
12
16
|
const schemes = schemeRegistry(workspace.schemes);
|
|
@@ -55,6 +59,9 @@ export const validateRelease = (workspace) => {
|
|
|
55
59
|
});
|
|
56
60
|
}
|
|
57
61
|
}
|
|
62
|
+
validateReleaseItems(workspace);
|
|
63
|
+
validateFloating(workspace.release.floating);
|
|
64
|
+
validateTemplates(workspace);
|
|
58
65
|
return schemes;
|
|
59
66
|
};
|
|
60
67
|
/**
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Warning } from "./errors.js";
|
|
2
|
+
import type { Plan } from "./plan.js";
|
|
3
|
+
import type { ReadonlyFs, VersionFile } from "./plugin.js";
|
|
4
|
+
import type { ReleaseItem } from "./release-items.js";
|
|
5
|
+
import type { Workspace } from "./workspace.js";
|
|
6
|
+
/** Checks template files and patterns (`E_CONFIG`); runs no plugin hook. */
|
|
7
|
+
export declare const validateTemplates: (workspace: Workspace) => void;
|
|
8
|
+
/**
|
|
9
|
+
* Plugin version files by unit id. Throws `E_VERSION_FILE_CLAIM` when a path
|
|
10
|
+
* is a plugin version file of two units or also a template. Templates may
|
|
11
|
+
* share a file.
|
|
12
|
+
*/
|
|
13
|
+
export declare const versionFileClaims: (workspace: Workspace) => Promise<ReadonlyMap<string, readonly VersionFile[]>>;
|
|
14
|
+
/**
|
|
15
|
+
* Files of a release of `items` (stable `next`): plugin version files,
|
|
16
|
+
* templates and `<root>/CHANGELOG.md` per unit, read through `fs`. Returns
|
|
17
|
+
* only files whose content changes, and each unit's changelog section;
|
|
18
|
+
* checks claims first. Templates skip first releases and warn
|
|
19
|
+
* `W_VERSION_FILE_NO_MATCH` without any match; a missing version file is
|
|
20
|
+
* `E_VERSION_FILE_MISSING`.
|
|
21
|
+
*/
|
|
22
|
+
export declare const releaseFiles: (workspace: Workspace, plan: Plan, opts: {
|
|
23
|
+
/** Files the release is based on. */
|
|
24
|
+
fs: ReadonlyFs;
|
|
25
|
+
items: readonly ReleaseItem[];
|
|
26
|
+
repoUrl: string | null;
|
|
27
|
+
/** `YYYY-MM-DD` changelog heading date. */
|
|
28
|
+
date: string;
|
|
29
|
+
}) => Promise<{
|
|
30
|
+
files: Map<string, string>;
|
|
31
|
+
/** Changelog section of each unit, by unit id. */
|
|
32
|
+
sections: Map<string, string>;
|
|
33
|
+
warnings: Warning[];
|
|
34
|
+
}>;
|