@tallyrelease/core 0.1.1-beta.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.
package/dist/util.d.ts CHANGED
@@ -1,4 +1,7 @@
1
+ import type { BumpLevel } from "./plugin.js";
1
2
  /** Code-unit string order, independent of locale. */
2
3
  export declare const compare: (a: string, b: string) => number;
3
4
  /** Sorted distinct values. */
4
5
  export declare const uniqueSorted: (values: Iterable<string>) => string[];
6
+ /** The higher of two levels; null is lowest. */
7
+ export declare const maxLevel: (a: BumpLevel | null, b: BumpLevel | null) => BumpLevel | null;
package/dist/util.js CHANGED
@@ -1,4 +1,7 @@
1
+ const LEVELS = ["patch", "minor", "major"];
1
2
  /** Code-unit string order, independent of locale. */
2
3
  export const compare = (a, b) => a < b ? -1 : a > b ? 1 : 0;
3
4
  /** Sorted distinct values. */
4
5
  export const uniqueSorted = (values) => [...new Set(values)].sort(compare);
6
+ /** The higher of two levels; null is lowest. */
7
+ export const maxLevel = (a, b) => a === null || (b !== null && LEVELS.indexOf(b) > LEVELS.indexOf(a)) ? b : a;
@@ -0,0 +1,14 @@
1
+ import type { Warning } from "./errors.js";
2
+ import type { TagRef, VersionScheme } from "./plugin.js";
3
+ import type { Workspace } from "./workspace.js";
4
+ /**
5
+ * Release-only checks, never run by `affected`: scheme registry, schemes, tag
6
+ * formats and initial versions of released units, group members, branch rules,
7
+ * their absolute check targets and channels. Returns the scheme registry.
8
+ */
9
+ export declare const validateRelease: (workspace: Workspace) => ReadonlyMap<string, VersionScheme<any>>;
10
+ /**
11
+ * Checks `tags` against the released units: throws `E_TAG_FORMAT_OVERLAP`,
12
+ * returns `W_NO_MATCHING_TAG` warnings.
13
+ */
14
+ export declare const validateTags: (workspace: Workspace, tags: readonly TagRef[]) => Warning[];
@@ -0,0 +1,67 @@
1
+ import { validateBranches } from "./branches.js";
2
+ import { TallyError } from "./errors.js";
3
+ import { schemeRegistry } from "./schemes.js";
4
+ import { compileTagFormat, matchUnitTags, noMatchingTag } from "./tags.js";
5
+ import { uniqueSorted } from "./util.js";
6
+ /**
7
+ * Release-only checks, never run by `affected`: scheme registry, schemes, tag
8
+ * formats and initial versions of released units, group members, branch rules,
9
+ * their absolute check targets and channels. Returns the scheme registry.
10
+ */
11
+ export const validateRelease = (workspace) => {
12
+ const schemes = schemeRegistry(workspace.schemes);
13
+ for (const unit of workspace.units) {
14
+ if (unit.version === "none") {
15
+ continue;
16
+ }
17
+ const scheme = schemes.get(unit.scheme);
18
+ if (!scheme) {
19
+ throw new TallyError("E_SCHEME_UNKNOWN", `unit "${unit.id}" uses unknown scheme "${unit.scheme}"`, { unit: unit.id, scheme: unit.scheme });
20
+ }
21
+ compileTagFormat(unit.tagFormat, unit.id);
22
+ if (unit.initialVersion !== null &&
23
+ scheme.parse(unit.initialVersion) === undefined) {
24
+ throw new TallyError("E_INITIAL_VERSION", `initial version "${unit.initialVersion}" of unit "${unit.id}" is not a ${unit.scheme} version`, { unit: unit.id, version: unit.initialVersion });
25
+ }
26
+ }
27
+ const units = new Map(workspace.units.map((u) => [u.id, u]));
28
+ for (const { name, members } of workspace.groups) {
29
+ const unreleased = members.find((id) => units.get(id)?.version === "none");
30
+ if (unreleased !== undefined) {
31
+ throw new TallyError("E_GROUP_NOT_RELEASED", `group "${name}" names unit "${unreleased}", which is not released`, { group: name, unit: unreleased });
32
+ }
33
+ const schemeNames = uniqueSorted(members.map((id) => units.get(id)?.scheme ?? ""));
34
+ if (schemeNames.length > 1) {
35
+ throw new TallyError("E_GROUP_SCHEME", `members of group "${name}" use different schemes: ${schemeNames.join(", ")}`, { group: name, schemes: schemeNames });
36
+ }
37
+ }
38
+ validateBranches(workspace.release.branches);
39
+ const groupOf = new Map(workspace.groups.flatMap((g) => g.members.map((id) => [id, g.name])));
40
+ for (const rule of workspace.release.branches) {
41
+ const { unit, group } = rule;
42
+ const problem = unit !== null
43
+ ? units.get(unit)?.version === "none" || !units.has(unit)
44
+ ? `names unit "${unit}", which is not a released unit`
45
+ : groupOf.has(unit)
46
+ ? `names unit "${unit}" of group "${groupOf.get(unit) ?? ""}"; use group`
47
+ : undefined
48
+ : group !== null && !workspace.groups.some((g) => g.name === group)
49
+ ? `names unknown group "${group}"`
50
+ : undefined;
51
+ if (problem !== undefined) {
52
+ throw new TallyError("E_BRANCH_RULE", `branch rule "${rule.name}" ${problem}`, {
53
+ rule: rule.name,
54
+ ...(unit === null ? { group } : { unit }),
55
+ });
56
+ }
57
+ }
58
+ return schemes;
59
+ };
60
+ /**
61
+ * Checks `tags` against the released units: throws `E_TAG_FORMAT_OVERLAP`,
62
+ * returns `W_NO_MATCHING_TAG` warnings.
63
+ */
64
+ export const validateTags = (workspace, tags) => {
65
+ const matched = matchUnitTags(workspace.units, tags, schemeRegistry(workspace.schemes));
66
+ return noMatchingTag(workspace.units, [...matched].map(([id, m]) => ({ id, tags: m.tags })), uniqueSorted(tags.map((t) => t.name)));
67
+ };
@@ -1,7 +1,16 @@
1
1
  import type { Warning } from "./errors.js";
2
2
  import type { Graph } from "./graph.js";
3
- import type { Unit } from "./plugin.js";
3
+ import type { Group } from "./groups.js";
4
+ import type { IntentSource, Unit } from "./plugin.js";
4
5
  import type { PluginEnv } from "./plugins.js";
6
+ import type { ReleaseSettings } from "./release.js";
7
+ import type { SchemeEntry } from "./schemes.js";
8
+ /** An intent source with its plugin's validated options; callers build the `Ctx`. */
9
+ export interface IntentEntry {
10
+ readonly plugin: string;
11
+ readonly options: unknown;
12
+ readonly source: IntentSource;
13
+ }
5
14
  export interface WorkspaceInput extends PluginEnv {
6
15
  /** Repo-relative config file path, a global input; defaults to `CONFIG_FILE`. */
7
16
  configFile?: string;
@@ -16,6 +25,14 @@ export interface Workspace {
16
25
  readonly globalInputs: readonly string[];
17
26
  readonly ignore: readonly string[];
18
27
  readonly warnings: readonly Warning[];
28
+ /** Registered schemes, core first; clashes are checked on release. */
29
+ readonly schemes: readonly SchemeEntry[];
30
+ /** Core sources first, then enabled plugins in config order. */
31
+ readonly intents: readonly IntentEntry[];
32
+ /** Bumps and branch rules with defaults applied. */
33
+ readonly release: ReleaseSettings;
34
+ /** Fixed groups, sorted by name. */
35
+ readonly groups: readonly Group[];
19
36
  }
20
37
  /** Runs plugin hooks sequentially in config order and builds the graph. */
21
38
  export declare const loadWorkspace: (input: WorkspaceInput) => Promise<Workspace>;
package/dist/workspace.js CHANGED
@@ -1,7 +1,11 @@
1
+ import { corePlugin } from "./builtin.js";
1
2
  import { CONFIG_FILE } from "./config.js";
2
3
  import { discoverUnits } from "./discover.js";
3
4
  import { buildGraph } from "./graph.js";
5
+ import { resolveGroups } from "./groups.js";
4
6
  import { activatePlugins, runHook } from "./plugins.js";
7
+ import { releaseSettings } from "./release.js";
8
+ import { collectSchemes } from "./schemes.js";
5
9
  import { uniqueSorted } from "./util.js";
6
10
  const collectEdges = async (active, config, units) => {
7
11
  const edges = [];
@@ -46,6 +50,15 @@ export const loadWorkspace = async (input) => {
46
50
  const units = await discoverUnits(active, input);
47
51
  const graph = buildGraph(units, await collectEdges(active, input.config, units));
48
52
  const globalInputs = await collectGlobalInputs(active, input.config, input.configFile ?? CONFIG_FILE);
53
+ const groups = resolveGroups(units, input.config.groups, warnings);
54
+ const intents = [
55
+ { plugin: corePlugin, options: {} },
56
+ ...active.map(({ plugin, ctx }) => ({ plugin, options: ctx.options })),
57
+ ].flatMap(({ plugin, options }) => (plugin.intent ?? []).map((source) => ({
58
+ plugin: plugin.name,
59
+ options,
60
+ source,
61
+ })));
49
62
  return {
50
63
  repo: input.repo.name,
51
64
  units,
@@ -53,5 +66,9 @@ export const loadWorkspace = async (input) => {
53
66
  globalInputs,
54
67
  ignore: input.config.ignore ?? [],
55
68
  warnings: [...warnings, ...graph.warnings],
69
+ schemes: collectSchemes(active),
70
+ intents,
71
+ release: releaseSettings(input.config),
72
+ groups,
56
73
  };
57
74
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tallyrelease/core",
3
- "version": "0.1.1-beta.1",
3
+ "version": "0.2.0-beta.8",
4
4
  "description": "Pure planning core of tallyrelease",
5
5
  "repository": {
6
6
  "type": "git",
@@ -47,7 +47,8 @@
47
47
  "prettier": "@abinnovision/prettier-config",
48
48
  "dependencies": {
49
49
  "@standard-schema/spec": "^1.1.0",
50
- "picomatch": "^4.0.7"
50
+ "picomatch": "^4.0.7",
51
+ "semver": "^7.8.5"
51
52
  },
52
53
  "devDependencies": {
53
54
  "@abinnovision/eslint-config-base": "^3.4.2",
@@ -55,6 +56,7 @@
55
56
  "@internal/tsconfig": "0.0.0",
56
57
  "@types/node": "^24.19.2",
57
58
  "@types/picomatch": "^4.0.3",
59
+ "@types/semver": "^7.8.0",
58
60
  "@vitest/coverage-v8": "^5.0.4",
59
61
  "esbuild": "^0.28.2",
60
62
  "eslint": "^10.12.0",