@tallyrelease/core 0.1.1 → 0.2.0-beta.8

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.
@@ -0,0 +1,81 @@
1
+ import type { Warning } from "./errors.js";
2
+ import type { Change, GitReader, ReadonlyFs } from "./plugin.js";
3
+ import type { UnitTag } from "./tags.js";
4
+ import type { Workspace } from "./workspace.js";
5
+ export interface FactsOptions {
6
+ head: string;
7
+ fs: ReadonlyFs;
8
+ debug?: (message: string) => void;
9
+ }
10
+ /** A Change with the ids of the units it is attributed to. */
11
+ export interface AttributedChange {
12
+ readonly change: Change;
13
+ /** Sorted. */
14
+ readonly units: readonly string[];
15
+ }
16
+ /**
17
+ * Changes of `(from, head]`, in core commit order, attributed to all units. A
18
+ * unit's consumers read only its own range (`UnitFacts.from`), filtered to the
19
+ * unit and the units it absorbs.
20
+ */
21
+ export interface RangeFacts {
22
+ readonly from: string | null;
23
+ readonly changes: readonly AttributedChange[];
24
+ }
25
+ export interface UnitFacts {
26
+ readonly id: string;
27
+ /** Fixed group name. */
28
+ readonly group: string | null;
29
+ /**
30
+ * Range key, null from the root: the base tag commit; for a group member
31
+ * its own base tag when that descends from the group base tag, else the
32
+ * group base tag.
33
+ */
34
+ readonly from: string | null;
35
+ /** Every matched tag, reachable or not, scheme-sorted. */
36
+ readonly tags: readonly UnitTag[];
37
+ /** Highest stable tag merged into head. */
38
+ readonly baseTag: UnitTag | null;
39
+ /** Source of the `from` version: the group base tag for members, else `baseTag`. */
40
+ readonly fromTag: UnitTag | null;
41
+ /** Highest stable tag on head's first-parent chain. */
42
+ readonly lineBaseTag: UnitTag | null;
43
+ readonly sinceLineBase: number;
44
+ readonly total: number;
45
+ /** Changes since the base tag attributed to this unit, core commit order. */
46
+ readonly changes: readonly AttributedChange[];
47
+ /** Qualified Release-As values naming this unit, core commit order. */
48
+ readonly releaseAs: readonly {
49
+ sha?: string;
50
+ version: string;
51
+ }[];
52
+ }
53
+ /**
54
+ * A fixed group. Base and line base are the highest member tags as for a
55
+ * unit; equal versions prefer the tag whose commit descends from the other's.
56
+ */
57
+ export interface GroupFacts {
58
+ readonly name: string;
59
+ readonly members: readonly string[];
60
+ /** Highest stable member tag merged into head. */
61
+ readonly baseTag: UnitTag | null;
62
+ /** Highest stable member tag on head's first-parent chain. */
63
+ readonly lineBaseTag: UnitTag | null;
64
+ readonly sinceLineBase: number;
65
+ }
66
+ /** Git and intent data of every released unit, collected before release math. */
67
+ export interface PlanFacts {
68
+ readonly head: string;
69
+ /** Released units, sorted by id. */
70
+ readonly units: readonly UnitFacts[];
71
+ /** One entry per distinct base tag commit. */
72
+ readonly ranges: readonly RangeFacts[];
73
+ /** Sorted by name. */
74
+ readonly groups: readonly GroupFacts[];
75
+ /** Every tag name, sorted. */
76
+ readonly tagNames: readonly string[];
77
+ /** Warnings logged by intent sources and for unknown explicit units. */
78
+ readonly warnings: readonly Warning[];
79
+ }
80
+ /** Runs every `GitReader` and intent source call a plan needs. */
81
+ export declare const collectPlanFacts: (workspace: Workspace, git: GitReader, opts: FactsOptions) => Promise<PlanFacts>;
package/dist/facts.js ADDED
@@ -0,0 +1,250 @@
1
+ import { attribution, matcher } from "./attribution.js";
2
+ import { TallyError } from "./errors.js";
3
+ import { pluginCtx, runHook } from "./plugins.js";
4
+ import { schemeRegistry } from "./schemes.js";
5
+ import { matchUnitTags } from "./tags.js";
6
+ import { compare, uniqueSorted } from "./util.js";
7
+ const instant = (commit) => {
8
+ const time = Date.parse(commit.date);
9
+ if (Number.isNaN(time)) {
10
+ throw new TallyError("E_GIT_DATE", `commit ${commit.sha} has an invalid committer date "${commit.date}"`, { sha: commit.sha, date: commit.date });
11
+ }
12
+ return time;
13
+ };
14
+ /** Committer date descending, compared as instants, then sha. */
15
+ const byCommitOrder = (a, b) => instant(b) - instant(a) || compare(a.sha, b.sha);
16
+ const releaseAsKey = (r) => `${r.unit ?? ""}@${r.version}`;
17
+ /** Keeps the first commit-derived Change per dedupe key, unioning `releaseAs`. */
18
+ const dedupe = (changes) => {
19
+ const kept = [];
20
+ const index = new Map();
21
+ for (const change of changes) {
22
+ const key = change.sha === undefined
23
+ ? undefined
24
+ : [
25
+ change.sha,
26
+ change.type,
27
+ String(change.breaking),
28
+ uniqueSorted(change.units ?? []).join(","),
29
+ ].join("\0");
30
+ const at = key === undefined ? undefined : index.get(key);
31
+ const prior = at === undefined ? undefined : kept[at];
32
+ if (at === undefined || !prior) {
33
+ if (key !== undefined) {
34
+ index.set(key, kept.length);
35
+ }
36
+ kept.push(change);
37
+ continue;
38
+ }
39
+ if (change.releaseAs) {
40
+ const values = new Map([...(prior.releaseAs ?? []), ...change.releaseAs].map((r) => [
41
+ releaseAsKey(r),
42
+ r,
43
+ ]));
44
+ kept[at] = { ...prior, releaseAs: [...values.values()] };
45
+ }
46
+ }
47
+ return kept;
48
+ };
49
+ /** Runs every `GitReader` and intent source call a plan needs. */
50
+ export const collectPlanFacts = async (workspace, git, opts) => {
51
+ const { head } = opts;
52
+ const warnings = [];
53
+ const ctx = pluginCtx({ ...opts, repo: { name: workspace.repo } }, warnings);
54
+ const intents = workspace.intents.map((entry) => ({
55
+ ...entry,
56
+ ctx: ctx(entry.plugin, entry.options),
57
+ }));
58
+ const schemes = schemeRegistry(workspace.schemes);
59
+ const allTags = await git.tags();
60
+ const tags = matchUnitTags(workspace.units, allTags, schemes);
61
+ const merged = new Set((await git.tagsMerged(head)).map((t) => t.name));
62
+ const total = await git.countFirstParent(null, head);
63
+ const ignored = matcher(workspace.ignore);
64
+ const isGlobal = matcher(workspace.globalInputs);
65
+ const owners = attribution(workspace.units);
66
+ const known = new Set(workspace.units.map((u) => u.id));
67
+ // File-less commits belong to every released unit (release-please parity).
68
+ const released = workspace.units
69
+ .filter((u) => u.version !== "none")
70
+ .map((u) => u.id);
71
+ const warned = new Set();
72
+ const warnUnknown = (plugin, changes) => {
73
+ for (const id of changes.flatMap((c) => c.units ?? [])) {
74
+ const key = `${plugin}\0${id}`;
75
+ if (!known.has(id) && !warned.has(key)) {
76
+ warned.add(key);
77
+ warnings.push({
78
+ code: "W_UNIT_UNKNOWN",
79
+ message: `intent names unknown unit "${id}"`,
80
+ plugin,
81
+ });
82
+ }
83
+ }
84
+ };
85
+ const counts = new Map();
86
+ const countSince = async (sha) => {
87
+ const cached = counts.get(sha);
88
+ if (cached !== undefined) {
89
+ return cached;
90
+ }
91
+ const count = await git.countFirstParent(sha, head);
92
+ counts.set(sha, count);
93
+ return count;
94
+ };
95
+ const ranges = new Map();
96
+ const range = async (from) => {
97
+ const cached = ranges.get(from);
98
+ if (cached) {
99
+ return cached;
100
+ }
101
+ const commits = Object.freeze([...(await git.log(from, head))].sort(byCommitOrder));
102
+ const position = new Map(commits.map((c, i) => [c.sha, i]));
103
+ const commitUnits = new Map(commits.map((c) => [
104
+ c.sha,
105
+ c.files.length === 0
106
+ ? released
107
+ : uniqueSorted(c.files
108
+ .filter((f) => !ignored(f) && !isGlobal(f))
109
+ .flatMap(owners)),
110
+ ]));
111
+ const changes = [];
112
+ for (const { plugin, source, ctx: sourceCtx } of intents) {
113
+ // eslint-disable-next-line no-await-in-loop -- hooks run sequentially in config order
114
+ const collected = await runHook(plugin, "intent", () => source.collect(sourceCtx, { from, to: head, commits }));
115
+ warnUnknown(plugin, collected);
116
+ changes.push(...collected);
117
+ }
118
+ const attributed = dedupe(changes)
119
+ .map((change) => ({
120
+ change,
121
+ units: change.units
122
+ ? uniqueSorted(change.units.filter((id) => known.has(id)))
123
+ : (commitUnits.get(change.sha ?? "") ?? []),
124
+ }))
125
+ .sort((a, b) => (position.get(a.change.sha ?? "") ?? commits.length) -
126
+ (position.get(b.change.sha ?? "") ?? commits.length));
127
+ const result = { from, changes: attributed };
128
+ ranges.set(from, result);
129
+ return result;
130
+ };
131
+ const ancestry = new Map();
132
+ const isAncestor = async (a, b) => {
133
+ const key = `${a}\0${b}`;
134
+ const cached = ancestry.get(key);
135
+ if (cached !== undefined) {
136
+ return cached;
137
+ }
138
+ const result = await git.isAncestor(a, b);
139
+ ancestry.set(key, result);
140
+ return result;
141
+ };
142
+ /** The higher tag; equal versions prefer the tag descending from the other. */
143
+ const higher = async (scheme, a, b) => {
144
+ if (a === null || b === null) {
145
+ return a ?? b;
146
+ }
147
+ const x = scheme.parse(a.version);
148
+ const y = scheme.parse(b.version);
149
+ // Members with another scheme fail validateRelease (E_GROUP_SCHEME).
150
+ if (x === undefined || y === undefined) {
151
+ return a;
152
+ }
153
+ const order = scheme.compare(y, x);
154
+ if (order !== 0) {
155
+ return order > 0 ? b : a;
156
+ }
157
+ return a.sha !== b.sha && (await isAncestor(a.sha, b.sha)) ? b : a;
158
+ };
159
+ const bases = new Map();
160
+ for (const unit of workspace.units) {
161
+ const matched = tags.get(unit.id);
162
+ if (!matched) {
163
+ // Unreleased.
164
+ continue;
165
+ }
166
+ const { scheme } = matched;
167
+ const stable = matched.tags.filter((t) => merged.has(t.name) && !scheme.isPrerelease(scheme.parse(t.version)));
168
+ let lineBaseTag = null;
169
+ for (const tag of [...stable].reverse()) {
170
+ // eslint-disable-next-line no-await-in-loop -- stops at the first match
171
+ if (await git.isFirstParentAncestor(tag.sha, head)) {
172
+ lineBaseTag = tag;
173
+ break;
174
+ }
175
+ }
176
+ bases.set(unit.id, { scheme, baseTag: stable.at(-1) ?? null, lineBaseTag });
177
+ }
178
+ const sinceLine = async (tag) => tag ? await countSince(tag.sha) : total;
179
+ const groups = [];
180
+ for (const { name, members } of workspace.groups) {
181
+ let baseTag = null;
182
+ let lineBaseTag = null;
183
+ for (const id of members) {
184
+ const own = bases.get(id);
185
+ if (own) {
186
+ // eslint-disable-next-line no-await-in-loop -- memoized ancestry
187
+ baseTag = await higher(own.scheme, baseTag, own.baseTag);
188
+ // eslint-disable-next-line no-await-in-loop -- memoized ancestry
189
+ lineBaseTag = await higher(own.scheme, lineBaseTag, own.lineBaseTag);
190
+ }
191
+ }
192
+ groups.push({
193
+ name,
194
+ members,
195
+ baseTag,
196
+ lineBaseTag,
197
+ // eslint-disable-next-line no-await-in-loop -- memoized per line base
198
+ sinceLineBase: await sinceLine(lineBaseTag),
199
+ });
200
+ }
201
+ const groupOf = new Map(groups.flatMap((g) => g.members.map((id) => [id, g])));
202
+ const units = [];
203
+ for (const unit of workspace.units) {
204
+ const matched = tags.get(unit.id);
205
+ const own = bases.get(unit.id);
206
+ if (!matched || !own) {
207
+ continue;
208
+ }
209
+ const { baseTag, lineBaseTag } = own;
210
+ const group = groupOf.get(unit.id);
211
+ const fromTag = group ? group.baseTag : baseTag;
212
+ const rangeBase = group &&
213
+ baseTag &&
214
+ fromTag &&
215
+ baseTag.sha !== fromTag.sha &&
216
+ // eslint-disable-next-line no-await-in-loop -- memoized ancestry
217
+ (await isAncestor(fromTag.sha, baseTag.sha))
218
+ ? baseTag
219
+ : fromTag;
220
+ // eslint-disable-next-line no-await-in-loop -- intent sources run sequentially
221
+ const { from, changes } = await range(rangeBase?.sha ?? null);
222
+ units.push({
223
+ id: unit.id,
224
+ group: group?.name ?? null,
225
+ from,
226
+ tags: matched.tags,
227
+ baseTag,
228
+ fromTag,
229
+ lineBaseTag,
230
+ // eslint-disable-next-line no-await-in-loop -- memoized per line base
231
+ sinceLineBase: await sinceLine(lineBaseTag),
232
+ total,
233
+ changes: changes.filter((c) => c.units.includes(unit.id)),
234
+ releaseAs: changes.flatMap(({ change }) => (change.releaseAs ?? [])
235
+ .filter((r) => r.unit === unit.id)
236
+ .map((r) => ({
237
+ ...(change.sha === undefined ? {} : { sha: change.sha }),
238
+ version: r.version,
239
+ }))),
240
+ });
241
+ }
242
+ return {
243
+ head,
244
+ units,
245
+ ranges: [...ranges.values()],
246
+ groups,
247
+ tagNames: uniqueSorted(allTags.map((t) => t.name)),
248
+ warnings,
249
+ };
250
+ };
@@ -0,0 +1,15 @@
1
+ import type { GroupConfig } from "./config.js";
2
+ import type { Warning } from "./errors.js";
3
+ import type { Unit } from "./plugin.js";
4
+ /** A fixed group: its members release together with one version. */
5
+ export interface Group {
6
+ readonly name: string;
7
+ /** Sorted unit ids. */
8
+ readonly members: readonly string[];
9
+ }
10
+ /**
11
+ * Resolves group members: `units` by id, `tags` to released units with any of
12
+ * the tags. Throws `E_GROUP_UNKNOWN_UNIT` and `E_GROUP_OVERLAP`; a group
13
+ * selecting nothing warns `W_GROUP_EMPTY` and is dropped.
14
+ */
15
+ export declare const resolveGroups: (units: readonly Unit[], config: Readonly<Record<string, GroupConfig>> | undefined, warnings: Warning[]) => Group[];
package/dist/groups.js ADDED
@@ -0,0 +1,41 @@
1
+ import { TallyError } from "./errors.js";
2
+ import { compare, uniqueSorted } from "./util.js";
3
+ /**
4
+ * Resolves group members: `units` by id, `tags` to released units with any of
5
+ * the tags. Throws `E_GROUP_UNKNOWN_UNIT` and `E_GROUP_OVERLAP`; a group
6
+ * selecting nothing warns `W_GROUP_EMPTY` and is dropped.
7
+ */
8
+ export const resolveGroups = (units, config, warnings) => {
9
+ const known = new Set(units.map((u) => u.id));
10
+ const owner = new Map();
11
+ const groups = [];
12
+ for (const name of Object.keys(config ?? {}).sort(compare)) {
13
+ const { units: ids = [], tags = [] } = config?.[name] ?? {};
14
+ const unknown = ids.find((id) => !known.has(id));
15
+ if (unknown !== undefined) {
16
+ throw new TallyError("E_GROUP_UNKNOWN_UNIT", `group "${name}" names unknown unit "${unknown}"`, { group: name, unit: unknown });
17
+ }
18
+ const members = uniqueSorted([
19
+ ...ids,
20
+ ...units
21
+ .filter((u) => u.version !== "none" && u.tags.some((t) => tags.includes(t)))
22
+ .map((u) => u.id),
23
+ ]);
24
+ if (members.length === 0) {
25
+ warnings.push({
26
+ code: "W_GROUP_EMPTY",
27
+ message: `group "${name}" selects no unit`,
28
+ });
29
+ continue;
30
+ }
31
+ for (const id of members) {
32
+ const other = owner.get(id);
33
+ if (other !== undefined) {
34
+ throw new TallyError("E_GROUP_OVERLAP", `unit "${id}" is in groups "${other}" and "${name}"`, { unit: id, groups: [other, name] });
35
+ }
36
+ owner.set(id, name);
37
+ }
38
+ groups.push({ name, members });
39
+ }
40
+ return groups;
41
+ };
package/dist/index.d.ts CHANGED
@@ -1,16 +1,28 @@
1
1
  export declare const version = "0.0.0";
2
2
  export type { AffectedDoc, AffectedOptions, AffectedReason, AffectedUnit, BaseSource, } from "./affected.js";
3
- export type { DepConfig, TallyConfig, UnitOverlay } from "./config.js";
3
+ export type { ParsedCommit } from "./conventional.js";
4
+ export type { ExplainedChange, ExplainOptions, ReleaseAsFooter, UnitExplanation, } from "./explain.js";
5
+ export type { GroupPlan, Plan, PlanChange, PlanLine, PlanOptions, ReleasedUnit, UnitPlan, } from "./plan.js";
6
+ export type { DepConfig, GroupConfig, TallyConfig, UnitOverlay, } from "./config.js";
4
7
  export type { Warning } from "./errors.js";
5
8
  export type { Graph, SourcedEdge } from "./graph.js";
6
- export type { BumpLevel, Change, Ctx, Edge, EdgeDraft, GitReader, IntentSource, MaybePromise, Plugin, PreCtx, Propagate, RawCommit, ReadonlyFs, TagRef, Unit, UnitDraft, VersionFile, VersionMode, VersionScheme, VersionSource, WriteCtx, } from "./plugin.js";
9
+ export type { Group } from "./groups.js";
10
+ export type { BumpLevel, Change, Ctx, Edge, EdgeDraft, GitReader, IntentSource, MaybePromise, Plugin, PreCtx, Propagate, RawCommit, ReadonlyFs, ReleaseAsValue, TagRef, Unit, UnitDraft, VersionFile, VersionMode, VersionScheme, VersionSource, WriteCtx, } from "./plugin.js";
7
11
  export type { ActivePlugin, PluginEnv } from "./plugins.js";
8
- export type { Workspace, WorkspaceInput } from "./workspace.js";
12
+ export type { BranchRule, ReleaseSettings } from "./release.js";
13
+ export type { SchemeEntry } from "./schemes.js";
14
+ export type { SemVer } from "./semver.js";
15
+ export type { IntentEntry, Workspace, WorkspaceInput } from "./workspace.js";
9
16
  export { computeAffected } from "./affected.js";
10
17
  export { CONFIG_FILE } from "./config.js";
18
+ export { conventionalCommits, parseCommit } from "./conventional.js";
11
19
  export { discoverUnits } from "./discover.js";
12
20
  export { TallyError } from "./errors.js";
21
+ export { explainUnit } from "./explain.js";
13
22
  export { buildGraph } from "./graph.js";
14
23
  export { definePlugin, defaultUnitId, PLUGIN_API_VERSION } from "./plugin.js";
24
+ export { computePlan } from "./plan.js";
15
25
  export { activatePlugins } from "./plugins.js";
26
+ export { semver } from "./semver.js";
27
+ export { validateRelease, validateTags } from "./validate.js";
16
28
  export { loadWorkspace } from "./workspace.js";
package/dist/index.js CHANGED
@@ -1,9 +1,14 @@
1
1
  export const version = "0.0.0";
2
2
  export { computeAffected } from "./affected.js";
3
3
  export { CONFIG_FILE } from "./config.js";
4
+ export { conventionalCommits, parseCommit } from "./conventional.js";
4
5
  export { discoverUnits } from "./discover.js";
5
6
  export { TallyError } from "./errors.js";
7
+ export { explainUnit } from "./explain.js";
6
8
  export { buildGraph } from "./graph.js";
7
9
  export { definePlugin, defaultUnitId, PLUGIN_API_VERSION } from "./plugin.js";
10
+ export { computePlan } from "./plan.js";
8
11
  export { activatePlugins } from "./plugins.js";
12
+ export { semver } from "./semver.js";
13
+ export { validateRelease, validateTags } from "./validate.js";
9
14
  export { loadWorkspace } from "./workspace.js";
package/dist/plan.d.ts ADDED
@@ -0,0 +1,108 @@
1
+ import type { AffectedDoc, AffectedOptions, AffectedReason } from "./affected.js";
2
+ import type { Line } from "./branches.js";
3
+ import type { PlanFacts } from "./facts.js";
4
+ import type { BumpLevel, GitReader, ReadonlyFs, VersionScheme } from "./plugin.js";
5
+ import type { UnitRelease } from "./release.js";
6
+ import type { Workspace } from "./workspace.js";
7
+ export interface PlanChange {
8
+ /** Absent for file-based changes. */
9
+ sha?: string;
10
+ type: string;
11
+ breaking: boolean;
12
+ scope?: string;
13
+ subject: string;
14
+ pr?: number;
15
+ /** Absorbed unit the change came through. */
16
+ via?: string;
17
+ /** Release-As value of this commit for the unit. */
18
+ releaseAs?: string;
19
+ }
20
+ export interface UnitPlan {
21
+ id: string;
22
+ /** Unit root, `.` for the repo root. */
23
+ path: string;
24
+ plugin: string;
25
+ scheme: string;
26
+ tags: string[];
27
+ group: string | null;
28
+ affected: {
29
+ reason: AffectedReason;
30
+ via: string[];
31
+ } | null;
32
+ kind: "prerelease" | "rebuild" | "none";
33
+ baseTag: string | null;
34
+ lineBaseTag: string | null;
35
+ /** Null on a first release. */
36
+ from: string | null;
37
+ bump: BumpLevel | null;
38
+ releaseAs: string | null;
39
+ propagatedFrom: string[];
40
+ absorbedFrom: string[];
41
+ next: string | null;
42
+ version: string | null;
43
+ /** The planned commit. */
44
+ sha: string;
45
+ /** Core commit order. */
46
+ changes: PlanChange[];
47
+ }
48
+ export interface GroupPlan {
49
+ name: string;
50
+ members: string[];
51
+ baseTag: string | null;
52
+ from: string | null;
53
+ bump: BumpLevel | null;
54
+ next: string | null;
55
+ version: string | null;
56
+ }
57
+ export interface ReleasedUnit {
58
+ id: string;
59
+ path: string;
60
+ version: string;
61
+ tag: string;
62
+ floatingTags: string[];
63
+ releaseSha: string;
64
+ catchUp: boolean;
65
+ pr: number | null;
66
+ line: "default" | "maintenance";
67
+ }
68
+ /** `tallyrelease plan --json`, schema 1. */
69
+ export interface Plan extends Omit<AffectedDoc, "units"> {
70
+ line: Line | null;
71
+ /** Topological order, cycles by id. */
72
+ units: UnitPlan[];
73
+ /** Sorted by name. */
74
+ groups: GroupPlan[];
75
+ /** Stable releases in `(build.base, head]`; empty until filled. */
76
+ released: ReleasedUnit[];
77
+ }
78
+ export interface PlanOptions extends AffectedOptions {
79
+ /** Files at `head`, for intent source contexts. */
80
+ fs: ReadonlyFs;
81
+ /** Also list released units outside the build set with a pending release. */
82
+ all?: boolean;
83
+ debug?: (message: string) => void;
84
+ }
85
+ /** Inputs of `resolvePlan`, all collected before release math. */
86
+ export interface ResolveInput {
87
+ affected: AffectedDoc;
88
+ facts: PlanFacts;
89
+ schemes: ReadonlyMap<string, VersionScheme<any>>;
90
+ all: boolean;
91
+ }
92
+ /** Release math over collected facts; no I/O. */
93
+ export declare const resolvePlan: (workspace: Workspace, input: ResolveInput) => Plan;
94
+ /** A plan with the facts it was resolved from. */
95
+ export interface PlanResult {
96
+ plan: Plan;
97
+ facts: PlanFacts;
98
+ /** Releases of the released units with a scheme. */
99
+ releases: ReadonlyMap<string, UnitRelease>;
100
+ }
101
+ /** `computePlan` keeping the collected facts, for `explainUnit`. */
102
+ export declare const planWithFacts: (workspace: Workspace, git: GitReader, opts: PlanOptions) => Promise<PlanResult>;
103
+ /**
104
+ * Plans `head`: validates release config, computes the build set, collects
105
+ * git and intent facts and resolves versions and kinds.
106
+ */
107
+ export declare const computePlan: (workspace: Workspace, git: GitReader, opts: PlanOptions) => Promise<Plan>;
108
+ export type { Line as PlanLine } from "./branches.js";