@tallyrelease/core 0.5.0-beta.34 → 0.5.0-beta.41

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 CHANGED
@@ -24,9 +24,23 @@ export const example = definePlugin({
24
24
 
25
25
  See `Plugin` in [`src/plugin.ts`](https://github.com/abinnovision/tallyrelease/blob/main/packages/core/src/plugin.ts) for the available hooks (`discover`, `edges`, `inputs`, `versionFiles`, `schemes`, `intent`).
26
26
 
27
+ Core always registers the `semver` scheme and the Conventional Commits intent source. A plugin that reads commit messages can use `parseCommit` for the same parsing.
28
+
29
+ ### Tag formats
30
+
31
+ A plugin that sets `tagFormat` on its units can check the format with the same rules core applies. `compileTagFormat(format, unitId)` throws `E_TAG_FORMAT` unless `{version}` occurs exactly once, and returns a `TagFormat` whose `match(tag)` gives the `{version}` part of a matching tag. `renderTag(unit, version)` renders the tag of a version.
32
+
33
+ ```ts
34
+ import { compileTagFormat, renderTag } from "@tallyrelease/core";
35
+
36
+ const format = compileTagFormat("{id}-v{version}", "web");
37
+ format.match("web-v1.2.0"); // "1.2.0"
38
+ renderTag({ id: "web", tagFormat: "{id}-v{version}" }, "1.2.0"); // "web-v1.2.0"
39
+ ```
40
+
27
41
  ## Testing
28
42
 
29
- The `@tallyrelease/core/testing` export provides in-memory fakes: `memoryFs`, `memoryCtx` and `memoryGit`. The `@tallyrelease/core/testing/conformance` export provides `gitReaderConformance`, a shared `GitReader` spec that requires vitest.
43
+ The `@tallyrelease/core/testing` export provides in-memory fakes: `memoryFs`, `memoryCtx` and `memoryGit`. The `@tallyrelease/core/testing/conformance` export provides `gitReaderConformance` and `schemeConformance`, shared `GitReader` and `VersionScheme` specs that require vitest.
30
44
 
31
45
  ## Licence
32
46
 
package/dist/branches.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import { coreCodes } from "./codes.js";
2
- import { schemeRegistry } from "./schemes.js";
3
2
  import { maxLevel } from "./util.js";
4
3
  const SPECIAL = /[\\^$.*+?()[\]{}|/]/gu;
5
4
  const PART = /\{(major|minor)\}|\*/gu;
@@ -177,7 +176,7 @@ export const maintenanceCut = (workspace, plan, item) => {
177
176
  ? plan.groups.find((g) => g.name === item.name)
178
177
  : plan.units.find((u) => u.id === item.name);
179
178
  const unit = workspace.units.find((u) => u.id === item.units[0]);
180
- const scheme = unit && schemeRegistry(workspace.schemes).get(unit.scheme);
179
+ const scheme = unit && workspace.schemes.get(unit.scheme);
181
180
  if (plan.line?.kind !== "default" ||
182
181
  !scheme ||
183
182
  !base?.from ||
package/dist/changelog.js CHANGED
@@ -1,4 +1,3 @@
1
- import { schemeRegistry } from "./schemes.js";
2
1
  import { renderTag } from "./tags.js";
3
2
  import { compare } from "./util.js";
4
3
  /** Commit types in section order; hidden sections list only breaking or Release-As changes. */
@@ -238,7 +237,7 @@ export const updateChangelog = (content, section, { baseVersion, scheme, }) => {
238
237
  */
239
238
  export const releaseNotes = (workspace, id, read) => {
240
239
  const unit = workspace.units.find((u) => u.id === id);
241
- const scheme = unit && schemeRegistry(workspace.schemes).get(unit.scheme);
240
+ const scheme = unit && workspace.schemes.get(unit.scheme);
242
241
  const content = unit && read(changelogPath(unit));
243
242
  if (!scheme || content === undefined) {
244
243
  return undefined;
@@ -1,10 +1,11 @@
1
1
  import type { Warning } from "./errors.js";
2
2
  import type { Unit } from "./plugin.js";
3
- import type { ActivePlugin, PluginEnv } from "./plugins.js";
3
+ import type { ActivePlugin } from "./plugins.js";
4
+ import type { WorkspaceInput } from "./workspace.js";
4
5
  /** Drops `.` and empty segments; rejects absolute roots and `..` segments. */
5
6
  export declare const normalizeRoot: (root: string) => string;
6
7
  /**
7
8
  * Runs `discover` hooks in config order, applies config overlays (glob keys
8
9
  * first, then exact keys) and fills defaults. Returns units sorted by id.
9
10
  */
10
- export declare const discoverUnits: (active: readonly ActivePlugin[], env: Pick<PluginEnv, "config" | "fs" | "repo">, warnings?: Warning[]) => Promise<Unit[]>;
11
+ export declare const discoverUnits: (active: readonly ActivePlugin[], env: Pick<WorkspaceInput, "config" | "fs" | "repo">, warnings?: Warning[]) => Promise<Unit[]>;
package/dist/facts.js CHANGED
@@ -1,7 +1,6 @@
1
1
  import { attribution, matcher } from "./attribution.js";
2
2
  import { coreCodes } from "./codes.js";
3
3
  import { pluginCtx, runHook } from "./plugins.js";
4
- import { schemeRegistry } from "./schemes.js";
5
4
  import { matchUnitTags } from "./tags.js";
6
5
  import { compare, uniqueSorted } from "./util.js";
7
6
  const instant = (commit) => {
@@ -78,7 +77,7 @@ export const lineFacts = async (git, head) => {
78
77
  /** Tags of every unit and fixed group, relative to `head`. */
79
78
  export const tagFacts = async (workspace, git, line) => {
80
79
  const { head } = line;
81
- const schemes = schemeRegistry(workspace.schemes);
80
+ const { schemes } = workspace;
82
81
  const allTags = await git.tags();
83
82
  const tags = matchUnitTags(workspace.units, allTags, schemes);
84
83
  const merged = new Set((await git.tagsMerged(head)).map((t) => t.name));
package/dist/index.d.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  /** @deprecated Unused, will be removed in 1.0. */
2
2
  export declare const version = "0.0.0";
3
3
  export type { AffectedDoc, AffectedOptions, AffectedReason, AffectedUnit, BaseSource, } from "./model/affected-types.js";
4
- export type { ChangelogDependency, ChangelogInput } from "./changelog.js";
5
4
  export type { CodeDoc, CodeOf, CodeRegistry, ErrorCode, WarningCode, } from "./codes.js";
6
5
  export type { ParsedCommit } from "./conventional.js";
7
6
  export type { ExplainedChange, ExplainOptions, ReleaseAsFooter, UnitExplanation, } from "./explain.js";
@@ -9,34 +8,30 @@ export type { BranchRule, Line as PlanLine } from "./model/branch-types.js";
9
8
  export type { GroupPlan, Plan, PlanChange, PlanOptions, ReleasedUnit, UnitPlan, } from "./model/plan-types.js";
10
9
  export type { DepConfig, GroupConfig, ReleasePrMode, TallyConfig, UnitOverlay, VersionTemplate, } from "./config.js";
11
10
  export type { Warning } from "./errors.js";
12
- export type { Graph, SourcedEdge } from "./graph.js";
11
+ export type { Graph } from "./graph.js";
13
12
  export type { Group } from "./groups.js";
14
13
  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";
15
14
  export type { Marker, MarkerRelease } from "./marker.js";
16
- export type { ActivePlugin, PluginEnv } from "./plugins.js";
17
15
  export type { ReleaseItem } from "./release-items.js";
18
16
  export type { ReleaseSettings } from "./release-settings.js";
19
- export type { SchemeEntry } from "./schemes.js";
20
17
  export type { SemVer } from "./semver.js";
18
+ export type { TagFormat } from "./tags.js";
21
19
  export type { IntentEntry, Workspace, WorkspaceInput } from "./workspace.js";
22
20
  export { computeAffected } from "./affected.js";
23
21
  export { maintenanceCut } from "./branches.js";
24
- export { changelogSection, releaseNotes, renderChangelogSection, updateChangelog, } from "./changelog.js";
22
+ export { releaseNotes } from "./changelog.js";
25
23
  export { coreCodes, defineCodes } from "./codes.js";
26
24
  export { CONFIG_FILE } from "./config.js";
27
- export { conventionalCommits, parseCommit } from "./conventional.js";
28
- export { discoverUnits } from "./discover.js";
25
+ export { parseCommit } from "./conventional.js";
29
26
  export { TallyError } from "./errors.js";
30
27
  export { explainUnit } from "./explain.js";
31
- export { renderFloatingTags } from "./floating.js";
32
- export { buildGraph } from "./graph.js";
33
28
  export { definePlugin, defaultUnitId, PLUGIN_API_VERSION } from "./plugin.js";
34
29
  export { computePlan } from "./plan.js";
35
30
  export { decodeMarker, encodeMarker } from "./marker.js";
36
- export { activatePlugins } from "./plugins.js";
37
31
  export { planEquals, releaseEntries, releaseItems } from "./release-items.js";
38
32
  export { releasedFromTags, withReleased } from "./released.js";
39
33
  export { semver } from "./semver.js";
34
+ export { compileTagFormat, renderTag } from "./tags.js";
40
35
  export { validateRelease, validateTags } from "./validate.js";
41
36
  export { releaseFiles, versionFileClaims } from "./version-files.js";
42
37
  export { loadWorkspace } from "./workspace.js";
package/dist/index.js CHANGED
@@ -2,22 +2,19 @@
2
2
  export const version = "0.0.0";
3
3
  export { computeAffected } from "./affected.js";
4
4
  export { maintenanceCut } from "./branches.js";
5
- export { changelogSection, releaseNotes, renderChangelogSection, updateChangelog, } from "./changelog.js";
5
+ export { releaseNotes } from "./changelog.js";
6
6
  export { coreCodes, defineCodes } from "./codes.js";
7
7
  export { CONFIG_FILE } from "./config.js";
8
- export { conventionalCommits, parseCommit } from "./conventional.js";
9
- export { discoverUnits } from "./discover.js";
8
+ export { parseCommit } from "./conventional.js";
10
9
  export { TallyError } from "./errors.js";
11
10
  export { explainUnit } from "./explain.js";
12
- export { renderFloatingTags } from "./floating.js";
13
- export { buildGraph } from "./graph.js";
14
11
  export { definePlugin, defaultUnitId, PLUGIN_API_VERSION } from "./plugin.js";
15
12
  export { computePlan } from "./plan.js";
16
13
  export { decodeMarker, encodeMarker } from "./marker.js";
17
- export { activatePlugins } from "./plugins.js";
18
14
  export { planEquals, releaseEntries, releaseItems } from "./release-items.js";
19
15
  export { releasedFromTags, withReleased } from "./released.js";
20
16
  export { semver } from "./semver.js";
17
+ export { compileTagFormat, renderTag } from "./tags.js";
21
18
  export { validateRelease, validateTags } from "./validate.js";
22
19
  export { releaseFiles, versionFileClaims } from "./version-files.js";
23
20
  export { loadWorkspace } from "./workspace.js";
package/dist/plan.js CHANGED
@@ -327,7 +327,7 @@ const resolveReleases = (workspace, input) => {
327
327
  export const resolvePlan = (workspace, input) => resolveReleases(workspace, input).plan;
328
328
  /** `computePlan` keeping the collected facts, for `explainUnit`. */
329
329
  export const planWithFacts = async (workspace, git, opts) => {
330
- const schemes = validateRelease(workspace);
330
+ validateRelease(workspace);
331
331
  const affected = await computeAffected(workspace, git, opts);
332
332
  const facts = await collectPlanFacts(workspace, git, {
333
333
  head: opts.head,
@@ -337,7 +337,7 @@ export const planWithFacts = async (workspace, git, opts) => {
337
337
  const { plan, releases } = resolveReleases(workspace, {
338
338
  affected,
339
339
  facts,
340
- schemes,
340
+ schemes: workspace.schemes,
341
341
  all: opts.all ?? false,
342
342
  });
343
343
  return { plan, facts, releases };
package/dist/plugins.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import type { TallyConfig } from "./config.js";
2
1
  import type { Warning } from "./errors.js";
3
- import type { Ctx, MaybePromise, Plugin, ReadonlyFs } from "./plugin.js";
2
+ import type { Ctx, MaybePromise, Plugin } from "./plugin.js";
3
+ import type { WorkspaceInput } from "./workspace.js";
4
4
  /** An enabled plugin with its validated options. */
5
5
  export interface ActivePlugin {
6
6
  readonly plugin: Plugin;
@@ -8,19 +8,12 @@ export interface ActivePlugin {
8
8
  /** False when `plugins.<name>.globalInputs: false`. */
9
9
  readonly globalInputs: boolean;
10
10
  }
11
- export interface PluginEnv {
12
- config: TallyConfig;
13
- /** Available plugins; `config.plugins` selects and orders them. */
14
- plugins: readonly Plugin[];
15
- fs: ReadonlyFs;
16
- repo: {
17
- name: string;
18
- };
19
- debug?: (message: string) => void;
20
- }
21
11
  /** Frozen `Ctx` factory; warnings are tagged with the plugin name. */
22
- export declare const pluginCtx: (env: Pick<PluginEnv, "fs" | "repo" | "debug">, warnings: Warning[]) => (name: string, options: unknown) => Ctx;
23
- /** Resolves enabled plugins in config order and validates their options. */
24
- export declare const activatePlugins: (env: PluginEnv, warnings: Warning[]) => Promise<ActivePlugin[]>;
12
+ export declare const pluginCtx: (env: Pick<WorkspaceInput, "fs" | "repo" | "debug">, warnings: Warning[]) => (name: string, options: unknown) => Ctx;
13
+ /**
14
+ * Resolves core, then enabled plugins in config order, and validates their
15
+ * options.
16
+ */
17
+ export declare const activatePlugins: (env: WorkspaceInput, warnings: Warning[]) => Promise<ActivePlugin[]>;
25
18
  /** Awaits a plugin hook, wrapping thrown errors as `E_PLUGIN_HOOK`. */
26
19
  export declare const runHook: <T>(plugin: string, hook: string, call: () => MaybePromise<T>) => Promise<T>;
package/dist/plugins.js CHANGED
@@ -1,4 +1,4 @@
1
- import { CORE_PLUGIN } from "./builtin.js";
1
+ import { CORE_PLUGIN, corePlugin } from "./builtin.js";
2
2
  import { coreCodes } from "./codes.js";
3
3
  import { assertPlugin } from "./plugin.js";
4
4
  /** Frozen `Ctx` factory; warnings are tagged with the plugin name. */
@@ -13,9 +13,18 @@ export const pluginCtx = (env, warnings) => (name, options) => Object.freeze({
13
13
  debug: (message) => env.debug?.(`${name}: ${message}`),
14
14
  }),
15
15
  });
16
- /** Resolves enabled plugins in config order and validates their options. */
16
+ /**
17
+ * Resolves core, then enabled plugins in config order, and validates their
18
+ * options.
19
+ */
17
20
  export const activatePlugins = async (env, warnings) => {
18
- const active = [];
21
+ const active = [
22
+ {
23
+ plugin: corePlugin,
24
+ ctx: pluginCtx(env, warnings)(CORE_PLUGIN, {}),
25
+ globalInputs: true,
26
+ },
27
+ ];
19
28
  for (const [name, raw] of Object.entries(env.config.plugins)) {
20
29
  const plugin = env.plugins.find((p) => p.name === name);
21
30
  if (!plugin) {
package/dist/released.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import { coreCodes } from "./codes.js";
2
2
  import { renderFloatingTags } from "./floating.js";
3
- import { schemeRegistry } from "./schemes.js";
4
3
  import { matchUnitTags } from "./tags.js";
5
4
  import { compare } from "./util.js";
6
5
  const stable = (scheme, version) => {
@@ -13,7 +12,7 @@ const stable = (scheme, version) => {
13
12
  * tag name.
14
13
  */
15
14
  const releasedEntries = async (workspace, plan, { git, keep, }) => {
16
- const matched = matchUnitTags(workspace.units, await git.tags(), schemeRegistry(workspace.schemes));
15
+ const matched = matchUnitTags(workspace.units, await git.tags(), workspace.schemes);
17
16
  const line = plan.line?.kind ?? "default";
18
17
  const built = new Set(plan.units.filter((u) => u.affected !== null).map((u) => u.id));
19
18
  const candidates = workspace.units.flatMap((unit) => {
package/dist/schemes.d.ts CHANGED
@@ -1,12 +1,8 @@
1
1
  import type { Plugin, VersionScheme } from "./plugin.js";
2
- export interface SchemeEntry {
3
- readonly plugin: string;
4
- readonly name: string;
5
- readonly scheme: VersionScheme<any>;
6
- }
7
- /** Schemes of core and the given plugins, in that order. */
8
- export declare const collectSchemes: (active: readonly {
9
- readonly plugin: Plugin;
10
- }[]) => SchemeEntry[];
11
- /** Name to scheme; throws `E_SCHEME_CLASH` on a duplicate name. */
12
- export declare const schemeRegistry: (list: readonly SchemeEntry[]) => ReadonlyMap<string, VersionScheme<any>>;
2
+ /**
3
+ * Name to scheme of `plugins`; the first registration of a name wins and a
4
+ * duplicate is recorded for `assertSchemes`.
5
+ */
6
+ export declare const schemeRegistry: (plugins: readonly Plugin[]) => ReadonlyMap<string, VersionScheme<any>>;
7
+ /** Throws the `E_SCHEME_CLASH` recorded by `schemeRegistry`. */
8
+ export declare const assertSchemes: (registry: ReadonlyMap<string, unknown>) => void;
package/dist/schemes.js CHANGED
@@ -1,22 +1,34 @@
1
- import { corePlugin } from "./builtin.js";
2
1
  import { coreCodes } from "./codes.js";
3
- /** Schemes of core and the given plugins, in that order. */
4
- export const collectSchemes = (active) => [corePlugin, ...active.map((a) => a.plugin)].flatMap((plugin) => Object.entries(plugin.schemes ?? {}).map(([name, scheme]) => ({
5
- plugin: plugin.name,
6
- name,
7
- scheme,
8
- })));
9
- /** Name to scheme; throws `E_SCHEME_CLASH` on a duplicate name. */
10
- export const schemeRegistry = (list) => {
2
+ const clashes = new WeakMap();
3
+ /**
4
+ * Name to scheme of `plugins`; the first registration of a name wins and a
5
+ * duplicate is recorded for `assertSchemes`.
6
+ */
7
+ export const schemeRegistry = (plugins) => {
11
8
  const owners = new Map();
12
9
  const registry = new Map();
13
- for (const { plugin, name, scheme } of list) {
14
- const owner = owners.get(name);
15
- if (owner !== undefined) {
16
- throw coreCodes.error("E_SCHEME_CLASH", `scheme "${name}" is registered by "${owner}" and "${plugin}"`, { scheme: name, plugins: [owner, plugin] });
10
+ let clash;
11
+ for (const plugin of plugins) {
12
+ for (const [name, scheme] of Object.entries(plugin.schemes ?? {})) {
13
+ const owner = owners.get(name);
14
+ if (owner !== undefined) {
15
+ clash ??= { scheme: name, plugins: [owner, plugin.name] };
16
+ continue;
17
+ }
18
+ owners.set(name, plugin.name);
19
+ registry.set(name, scheme);
17
20
  }
18
- owners.set(name, plugin);
19
- registry.set(name, scheme);
21
+ }
22
+ if (clash) {
23
+ clashes.set(registry, clash);
20
24
  }
21
25
  return registry;
22
26
  };
27
+ /** Throws the `E_SCHEME_CLASH` recorded by `schemeRegistry`. */
28
+ export const assertSchemes = (registry) => {
29
+ const clash = clashes.get(registry);
30
+ if (clash) {
31
+ const [first, second] = clash.plugins;
32
+ throw coreCodes.error("E_SCHEME_CLASH", `scheme "${clash.scheme}" is registered by "${first}" and "${second}"`, { scheme: clash.scheme, plugins: [first, second] });
33
+ }
34
+ };
@@ -1,5 +1,5 @@
1
1
  import type { Dag } from "./index.js";
2
- import type { GitReader, MaybePromise } from "../plugin.js";
2
+ import type { GitReader, MaybePromise, VersionScheme } from "../plugin.js";
3
3
  /** Builds a reader for `dag`; `sha` maps a commit id to the reader's sha. */
4
4
  export type ReaderFactory = (dag: Dag) => MaybePromise<{
5
5
  git: GitReader;
@@ -13,3 +13,9 @@ export type ReaderFactory = (dag: Dag) => MaybePromise<{
13
13
  * `introduced` runs on its own fixture.
14
14
  */
15
15
  export declare const gitReaderConformance: (makeReader: ReaderFactory) => void;
16
+ /**
17
+ * Shared `VersionScheme` spec. Versions are derived from `initial()`:
18
+ * `parse`/`format` round-trip, `compare` order, `bump` monotonicity per level,
19
+ * and `release`/`prerelease` keeping the core triple.
20
+ */
21
+ export declare const schemeConformance: <V>(scheme: VersionScheme<V>) => void;
@@ -202,3 +202,94 @@ export const gitReaderConformance = (makeReader) => {
202
202
  });
203
203
  });
204
204
  };
205
+ const LEVELS = ["major", "minor", "patch"];
206
+ const PRE_CTX = {
207
+ unit: {
208
+ id: "u",
209
+ root: ".",
210
+ plugin: "conformance",
211
+ include: [],
212
+ exclude: [],
213
+ version: "tag",
214
+ scheme: "conformance",
215
+ tagFormat: "v{version}",
216
+ propagate: "auto",
217
+ tags: [],
218
+ initialVersion: null,
219
+ meta: {},
220
+ },
221
+ head: "0".repeat(40),
222
+ baseTag: null,
223
+ tags: [],
224
+ firstParent: { sinceLineBase: 3, total: 7 },
225
+ channel: "rc",
226
+ };
227
+ /**
228
+ * Shared `VersionScheme` spec. Versions are derived from `initial()`:
229
+ * `parse`/`format` round-trip, `compare` order, `bump` monotonicity per level,
230
+ * and `release`/`prerelease` keeping the core triple.
231
+ */
232
+ export const schemeConformance = (scheme) => {
233
+ describe("VersionScheme conformance", () => {
234
+ const initial = scheme.initial();
235
+ const bumped = (level) => scheme.bump(initial, level);
236
+ it("round-trips parse and format", () => {
237
+ for (const v of [initial, ...LEVELS.map(bumped)]) {
238
+ const raw = scheme.format(v);
239
+ const parsed = scheme.parse(raw);
240
+ expect(parsed).toBeDefined();
241
+ expect(scheme.format(parsed)).toBe(raw);
242
+ expect(scheme.compare(parsed, v)).toBe(0);
243
+ }
244
+ });
245
+ it("rejects input that is not a version", () => {
246
+ expect(scheme.parse("")).toBeUndefined();
247
+ expect(scheme.parse("not a version")).toBeUndefined();
248
+ });
249
+ it("compares consistently", () => {
250
+ const low = initial;
251
+ const high = bumped("patch");
252
+ expect(scheme.compare(low, low)).toBe(0);
253
+ expect(scheme.compare(low, high)).toBeLessThan(0);
254
+ expect(scheme.compare(high, low)).toBeGreaterThan(0);
255
+ });
256
+ it.each(LEVELS)("bumps %s above the previous version", (level) => {
257
+ const next = bumped(level);
258
+ expect(scheme.compare(next, initial)).toBeGreaterThan(0);
259
+ expect(scheme.isPrerelease(next)).toBe(false);
260
+ });
261
+ it("orders bump levels major > minor > patch", () => {
262
+ const [major, minor, patch] = LEVELS.map(bumped);
263
+ expect(scheme.compare(major, minor)).toBeGreaterThan(0);
264
+ expect(scheme.compare(minor, patch)).toBeGreaterThan(0);
265
+ });
266
+ it("resets lower core parts on a bump", () => {
267
+ const { major, minor, patch } = scheme.core(initial);
268
+ expect(scheme.core(bumped("major"))).toEqual({
269
+ major: major + 1,
270
+ minor: 0,
271
+ patch: 0,
272
+ });
273
+ expect(scheme.core(bumped("minor"))).toEqual({
274
+ major,
275
+ minor: minor + 1,
276
+ patch: 0,
277
+ });
278
+ expect(scheme.core(bumped("patch"))).toEqual({
279
+ major,
280
+ minor,
281
+ patch: patch + 1,
282
+ });
283
+ });
284
+ it("keeps the core when releasing or prereleasing", () => {
285
+ const next = bumped("minor");
286
+ const release = scheme.release(next, PRE_CTX);
287
+ const pre = scheme.prerelease(next, PRE_CTX);
288
+ expect(scheme.core(release)).toEqual(scheme.core(next));
289
+ expect(scheme.core(pre)).toEqual(scheme.core(next));
290
+ expect(scheme.isPrerelease(release)).toBe(false);
291
+ expect(scheme.compare(pre, release)).toBeLessThanOrEqual(0);
292
+ expect(scheme.parse(scheme.format(pre))).toBeDefined();
293
+ });
294
+ });
295
+ };
@@ -1,5 +1,4 @@
1
1
  import type { Ctx, GitReader, ReadonlyFs } from "../plugin.js";
2
- import type { ReaderFactory } from "./conformance.js";
3
2
  /** In-memory `ReadonlyFs` over a path-to-content map. */
4
3
  export declare const memoryFs: (files: Record<string, string>) => ReadonlyFs;
5
4
  export interface MemoryCtxOptions<O> {
@@ -43,6 +42,3 @@ export interface MemoryGit extends GitReader {
43
42
  }
44
43
  /** In-memory `GitReader` over a commit graph. */
45
44
  export declare const memoryGit: (dag: Dag) => MemoryGit;
46
- export type { ReaderFactory } from "./conformance.js";
47
- /** Re-export of `@tallyrelease/core/testing/conformance`; requires vitest. */
48
- export declare const gitReaderConformance: (makeReader: ReaderFactory) => void;
@@ -1,13 +1,5 @@
1
1
  import picomatch from "picomatch/posix.js";
2
2
  import { compare, uniqueSorted } from "../util.js";
3
- // Loaded optionally so the fakes work without vitest installed.
4
- // eslint-disable-next-line no-restricted-syntax
5
- const conformance = await import("./conformance.js").catch((error) => {
6
- if (error.code === "ERR_MODULE_NOT_FOUND") {
7
- return null;
8
- }
9
- throw error;
10
- });
11
3
  const SKIPPED = new Set([".git", "node_modules"]);
12
4
  const changed = (a, b) => uniqueSorted([...a.keys(), ...b.keys()]).filter((p) => a.get(p) !== b.get(p));
13
5
  /** In-memory `ReadonlyFs` over a path-to-content map. */
@@ -149,10 +141,3 @@ export const memoryGit = (dag) => {
149
141
  parents: (ref) => [...node(ref).parents],
150
142
  };
151
143
  };
152
- /** Re-export of `@tallyrelease/core/testing/conformance`; requires vitest. */
153
- export const gitReaderConformance = (makeReader) => {
154
- if (conformance === null) {
155
- throw new Error("gitReaderConformance requires vitest");
156
- }
157
- conformance.gitReaderConformance(makeReader);
158
- };
@@ -1,13 +1,13 @@
1
1
  import type { Warning } from "./errors.js";
2
- import type { TagRef, VersionScheme } from "./plugin.js";
2
+ import type { TagRef } from "./plugin.js";
3
3
  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
7
  * their absolute check targets and channels, release items, floating tag
8
- * formats and version file templates. Returns the scheme registry.
8
+ * formats and version file templates.
9
9
  */
10
- export declare const validateRelease: (workspace: Workspace) => ReadonlyMap<string, VersionScheme<any>>;
10
+ export declare const validateRelease: (workspace: Workspace) => void;
11
11
  /**
12
12
  * Checks `tags` against the released units: throws `E_TAG_FORMAT_OVERLAP`,
13
13
  * returns `W_NO_MATCHING_TAG` warnings.
package/dist/validate.js CHANGED
@@ -2,7 +2,7 @@ import { validateBranches } from "./branches.js";
2
2
  import { coreCodes } from "./codes.js";
3
3
  import { validateFloating } from "./floating.js";
4
4
  import { validateReleaseItems } from "./release-items.js";
5
- import { schemeRegistry } from "./schemes.js";
5
+ import { assertSchemes } from "./schemes.js";
6
6
  import { compileTagFormat, matchUnitTags, noMatchingTag } from "./tags.js";
7
7
  import { uniqueSorted } from "./util.js";
8
8
  import { validateTemplates } from "./version-files.js";
@@ -10,10 +10,11 @@ import { validateTemplates } from "./version-files.js";
10
10
  * Release-only checks, never run by `affected`: scheme registry, schemes, tag
11
11
  * formats and initial versions of released units, group members, branch rules,
12
12
  * their absolute check targets and channels, release items, floating tag
13
- * formats and version file templates. Returns the scheme registry.
13
+ * formats and version file templates.
14
14
  */
15
15
  export const validateRelease = (workspace) => {
16
- const schemes = schemeRegistry(workspace.schemes);
16
+ const { schemes } = workspace;
17
+ assertSchemes(schemes);
17
18
  for (const unit of workspace.units) {
18
19
  if (unit.version === "none") {
19
20
  continue;
@@ -62,13 +63,12 @@ export const validateRelease = (workspace) => {
62
63
  validateReleaseItems(workspace);
63
64
  validateFloating(workspace.release.floating);
64
65
  validateTemplates(workspace);
65
- return schemes;
66
66
  };
67
67
  /**
68
68
  * Checks `tags` against the released units: throws `E_TAG_FORMAT_OVERLAP`,
69
69
  * returns `W_NO_MATCHING_TAG` warnings.
70
70
  */
71
71
  export const validateTags = (workspace, tags) => {
72
- const matched = matchUnitTags(workspace.units, tags, schemeRegistry(workspace.schemes));
72
+ const matched = matchUnitTags(workspace.units, tags, workspace.schemes);
73
73
  return noMatchingTag(workspace.units, [...matched].map(([id, m]) => ({ id, tags: m.tags })), uniqueSorted(tags.map((t) => t.name)));
74
74
  };
@@ -2,7 +2,6 @@ import { changelogPath, changelogSection, updateChangelog, } from "./changelog.j
2
2
  import { coreCodes } from "./codes.js";
3
3
  import { normalizeRoot } from "./discover.js";
4
4
  import { runHook } from "./plugins.js";
5
- import { schemeRegistry } from "./schemes.js";
6
5
  const TOKEN = /\{([^{}]*)\}/gu;
7
6
  const SPECIAL = /[\\^$.*+?()[\]{}|/]/gu;
8
7
  const CORE_TOKENS = ["major", "minor", "patch"];
@@ -229,7 +228,7 @@ export const versionFileClaims = async (workspace) => {
229
228
  export const releaseFiles = async (workspace, plan, opts) => {
230
229
  const { fs, items, repoUrl, date } = opts;
231
230
  const pluginFiles = await versionFileClaims(workspace);
232
- const schemes = schemeRegistry(workspace.schemes);
231
+ const { schemes } = workspace;
233
232
  const units = new Map(workspace.units.map((u) => [u.id, u]));
234
233
  const planned = new Map(plan.units.map((u) => [u.id, u]));
235
234
  const versions = new Map(items.flatMap((i) => i.units.map((id) => [id, i.version])));
@@ -1,17 +1,24 @@
1
+ import type { TallyConfig } from "./config.js";
1
2
  import type { Warning } from "./errors.js";
2
3
  import type { Graph } from "./graph.js";
3
4
  import type { Group } from "./groups.js";
4
- import type { IntentSource, Unit, VersionFile } from "./plugin.js";
5
- import type { PluginEnv } from "./plugins.js";
5
+ import type { IntentSource, Plugin, ReadonlyFs, Unit, VersionFile, VersionScheme } from "./plugin.js";
6
6
  import type { ReleaseSettings } from "./release-settings.js";
7
- import type { SchemeEntry } from "./schemes.js";
8
7
  /** An intent source with its plugin's validated options; callers build the `Ctx`. */
9
8
  export interface IntentEntry {
10
9
  readonly plugin: string;
11
10
  readonly options: unknown;
12
11
  readonly source: IntentSource;
13
12
  }
14
- export interface WorkspaceInput extends PluginEnv {
13
+ export interface WorkspaceInput {
14
+ config: TallyConfig;
15
+ /** Available plugins; `config.plugins` selects and orders them. */
16
+ plugins: readonly Plugin[];
17
+ fs: ReadonlyFs;
18
+ repo: {
19
+ name: string;
20
+ };
21
+ debug?: (message: string) => void;
15
22
  /** Repo-relative config file path, a global input; defaults to `CONFIG_FILE`. */
16
23
  configFile?: string;
17
24
  }
@@ -25,8 +32,8 @@ export interface Workspace {
25
32
  readonly globalInputs: readonly string[];
26
33
  readonly ignore: readonly string[];
27
34
  readonly warnings: readonly Warning[];
28
- /** Registered schemes, core first; clashes are checked on release. */
29
- readonly schemes: readonly SchemeEntry[];
35
+ /** Registered schemes by name; `validateRelease` throws on a clash. */
36
+ readonly schemes: ReadonlyMap<string, VersionScheme<any>>;
30
37
  /** Core sources first, then enabled plugins in config order. */
31
38
  readonly intents: readonly IntentEntry[];
32
39
  /** Bumps and branch rules with defaults applied. */
package/dist/workspace.js CHANGED
@@ -1,11 +1,10 @@
1
- import { corePlugin } from "./builtin.js";
2
1
  import { CONFIG_FILE } from "./config.js";
3
2
  import { discoverUnits } from "./discover.js";
4
3
  import { buildGraph } from "./graph.js";
5
4
  import { resolveGroups } from "./groups.js";
6
5
  import { activatePlugins, runHook } from "./plugins.js";
7
6
  import { releaseSettings } from "./release-settings.js";
8
- import { collectSchemes } from "./schemes.js";
7
+ import { schemeRegistry } from "./schemes.js";
9
8
  import { uniqueSorted } from "./util.js";
10
9
  const collectEdges = async (active, config, units) => {
11
10
  const edges = [];
@@ -74,12 +73,9 @@ export const loadWorkspace = async (input) => {
74
73
  const graph = buildGraph(units, await collectEdges(active, input.config, units));
75
74
  const globalInputs = await collectGlobalInputs(active, input.config, input.configFile ?? CONFIG_FILE);
76
75
  const groups = resolveGroups(units, input.config.groups, warnings);
77
- const intents = [
78
- { plugin: corePlugin, options: {} },
79
- ...active.map(({ plugin, ctx }) => ({ plugin, options: ctx.options })),
80
- ].flatMap(({ plugin, options }) => (plugin.intent ?? []).map((source) => ({
76
+ const intents = active.flatMap(({ plugin, ctx }) => (plugin.intent ?? []).map((source) => ({
81
77
  plugin: plugin.name,
82
- options,
78
+ options: ctx.options,
83
79
  source,
84
80
  })));
85
81
  return {
@@ -89,7 +85,7 @@ export const loadWorkspace = async (input) => {
89
85
  globalInputs,
90
86
  ignore: input.config.ignore ?? [],
91
87
  warnings: [...warnings, ...graph.warnings],
92
- schemes: collectSchemes(active),
88
+ schemes: schemeRegistry(active.map((a) => a.plugin)),
93
89
  intents,
94
90
  release: releaseSettings(input.config, units),
95
91
  groups,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tallyrelease/core",
3
- "version": "0.5.0-beta.34",
3
+ "version": "0.5.0-beta.41",
4
4
  "description": "Pure planning core of tallyrelease",
5
5
  "repository": {
6
6
  "type": "git",
@@ -66,7 +66,7 @@
66
66
  "eslint": "^10.13.0",
67
67
  "prettier": "^3.9.10",
68
68
  "typescript": "^6.0.3",
69
- "vite": "^8.3.4",
69
+ "vite": "^8.3.5",
70
70
  "vitest": "^5.0.5",
71
71
  "yaml": "^2.9.1"
72
72
  },