@tallyrelease/core 0.4.2-beta.6 → 0.5.0-beta.14

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 CHANGED
@@ -33,48 +33,48 @@
33
33
 
34
34
  * **core:** count prereleases from a unit's introduction without a line base ([#48](https://github.com/abinnovision/tallyrelease/issues/48)) ([06a591b](https://github.com/abinnovision/tallyrelease/commit/06a591b3522cf60ce5bde56e393f464617ffa101))
35
35
 
36
- ## [0.3.0](https://github.com/abinnovision/tally-release/compare/core-v0.2.0...core-v0.3.0) (2026-10-16)
36
+ ## [0.3.0](https://github.com/abinnovision/tallyrelease/compare/core-v0.2.0...core-v0.3.0) (2026-10-16)
37
37
 
38
38
 
39
39
  ### Features
40
40
 
41
- * **cli:** add tallyrelease pr for shared and separate release PRs ([#34](https://github.com/abinnovision/tally-release/issues/34)) ([5dbb294](https://github.com/abinnovision/tally-release/commit/5dbb2944a6f9535511710cbcfa5c6ddfccb14fe8))
42
- * **cli:** add tallyrelease run and fill released in plans ([#36](https://github.com/abinnovision/tally-release/issues/36)) ([8d90546](https://github.com/abinnovision/tally-release/commit/8d905469ed398a6daaa0e09f4fd9eb9c4c79c818))
43
- * **core:** add changelog writer, release items and PR marker ([#30](https://github.com/abinnovision/tally-release/issues/30)) ([e74244e](https://github.com/abinnovision/tally-release/commit/e74244ebc4752e3de39614159ef0761f1cd2a33c))
44
- * tag merged release PRs with tallyrelease release ([#35](https://github.com/abinnovision/tally-release/issues/35)) ([0058045](https://github.com/abinnovision/tally-release/commit/005804542b3f0b18a1a5afdb1a559231f8d10b4d))
45
- * write version files and changelogs with tallyrelease version ([#33](https://github.com/abinnovision/tally-release/issues/33)) ([5961ce0](https://github.com/abinnovision/tally-release/commit/5961ce0c56b7ddbb4bff2cf154d5b5556212e8cb))
41
+ * **cli:** add tallyrelease pr for shared and separate release PRs ([#34](https://github.com/abinnovision/tallyrelease/issues/34)) ([5dbb294](https://github.com/abinnovision/tallyrelease/commit/5dbb2944a6f9535511710cbcfa5c6ddfccb14fe8))
42
+ * **cli:** add tallyrelease run and fill released in plans ([#36](https://github.com/abinnovision/tallyrelease/issues/36)) ([8d90546](https://github.com/abinnovision/tallyrelease/commit/8d905469ed398a6daaa0e09f4fd9eb9c4c79c818))
43
+ * **core:** add changelog writer, release items and PR marker ([#30](https://github.com/abinnovision/tallyrelease/issues/30)) ([e74244e](https://github.com/abinnovision/tallyrelease/commit/e74244ebc4752e3de39614159ef0761f1cd2a33c))
44
+ * tag merged release PRs with tallyrelease release ([#35](https://github.com/abinnovision/tallyrelease/issues/35)) ([0058045](https://github.com/abinnovision/tallyrelease/commit/005804542b3f0b18a1a5afdb1a559231f8d10b4d))
45
+ * write version files and changelogs with tallyrelease version ([#33](https://github.com/abinnovision/tallyrelease/issues/33)) ([5961ce0](https://github.com/abinnovision/tallyrelease/commit/5961ce0c56b7ddbb4bff2cf154d5b5556212e8cb))
46
46
 
47
- ## [0.2.0](https://github.com/abinnovision/tally-release/compare/core-v0.1.1...core-v0.2.0) (2026-10-15)
47
+ ## [0.2.0](https://github.com/abinnovision/tallyrelease/compare/core-v0.1.1...core-v0.2.0) (2026-10-15)
48
48
 
49
49
 
50
50
  ### Features
51
51
 
52
- * add fixed groups ([#23](https://github.com/abinnovision/tally-release/issues/23)) ([9822ded](https://github.com/abinnovision/tally-release/commit/9822dedcb572b852348e7df3577d101bf4d70e62))
53
- * **cli:** add plan and explain commands ([#20](https://github.com/abinnovision/tally-release/issues/20)) ([4a68ff1](https://github.com/abinnovision/tally-release/commit/4a68ff10092b44fb994fd62f86a51b7af4bdbd5f))
54
- * **core:** absorb internal changes as patch by default ([#22](https://github.com/abinnovision/tally-release/issues/22)) ([947b85f](https://github.com/abinnovision/tally-release/commit/947b85fd442ca650f0a47ee2349ffe41b9ea5b69))
55
- * **core:** absorb unreleased dependencies and propagate releases ([#19](https://github.com/abinnovision/tally-release/issues/19)) ([bb987ba](https://github.com/abinnovision/tally-release/commit/bb987ba04fec52cea611cd0b8c9ed2e851fb0c9c))
56
- * **core:** add semver scheme and conventional commit parser ([#16](https://github.com/abinnovision/tally-release/issues/16)) ([2012c0b](https://github.com/abinnovision/tally-release/commit/2012c0ba035d551335f14ba709ec6db5d5ea1582))
57
- * **core:** compute release versions with computePlan ([#18](https://github.com/abinnovision/tally-release/issues/18)) ([0341c3e](https://github.com/abinnovision/tally-release/commit/0341c3eea127abf32c1eda59344481a3ee506e84))
58
- * **core:** match unit tags and collect plan facts ([#17](https://github.com/abinnovision/tally-release/issues/17)) ([828f9bd](https://github.com/abinnovision/tally-release/commit/828f9bd427b6e69419bdbb00d6570edee0cd9e0e))
59
- * enforce maintenance branch limits ([#24](https://github.com/abinnovision/tally-release/issues/24)) ([c5e382b](https://github.com/abinnovision/tally-release/commit/c5e382bb1d58430555b85d8076659272353c1e30))
52
+ * add fixed groups ([#23](https://github.com/abinnovision/tallyrelease/issues/23)) ([9822ded](https://github.com/abinnovision/tallyrelease/commit/9822dedcb572b852348e7df3577d101bf4d70e62))
53
+ * **cli:** add plan and explain commands ([#20](https://github.com/abinnovision/tallyrelease/issues/20)) ([4a68ff1](https://github.com/abinnovision/tallyrelease/commit/4a68ff10092b44fb994fd62f86a51b7af4bdbd5f))
54
+ * **core:** absorb internal changes as patch by default ([#22](https://github.com/abinnovision/tallyrelease/issues/22)) ([947b85f](https://github.com/abinnovision/tallyrelease/commit/947b85fd442ca650f0a47ee2349ffe41b9ea5b69))
55
+ * **core:** absorb unreleased dependencies and propagate releases ([#19](https://github.com/abinnovision/tallyrelease/issues/19)) ([bb987ba](https://github.com/abinnovision/tallyrelease/commit/bb987ba04fec52cea611cd0b8c9ed2e851fb0c9c))
56
+ * **core:** add semver scheme and conventional commit parser ([#16](https://github.com/abinnovision/tallyrelease/issues/16)) ([2012c0b](https://github.com/abinnovision/tallyrelease/commit/2012c0ba035d551335f14ba709ec6db5d5ea1582))
57
+ * **core:** compute release versions with computePlan ([#18](https://github.com/abinnovision/tallyrelease/issues/18)) ([0341c3e](https://github.com/abinnovision/tallyrelease/commit/0341c3eea127abf32c1eda59344481a3ee506e84))
58
+ * **core:** match unit tags and collect plan facts ([#17](https://github.com/abinnovision/tallyrelease/issues/17)) ([828f9bd](https://github.com/abinnovision/tallyrelease/commit/828f9bd427b6e69419bdbb00d6570edee0cd9e0e))
59
+ * enforce maintenance branch limits ([#24](https://github.com/abinnovision/tallyrelease/issues/24)) ([c5e382b](https://github.com/abinnovision/tallyrelease/commit/c5e382bb1d58430555b85d8076659272353c1e30))
60
60
 
61
- ## [0.1.1](https://github.com/abinnovision/tally-release/compare/core-v0.1.0...core-v0.1.1) (2026-10-15)
61
+ ## [0.1.1](https://github.com/abinnovision/tallyrelease/compare/core-v0.1.0...core-v0.1.1) (2026-10-15)
62
62
 
63
63
 
64
64
  ### Bug Fixes
65
65
 
66
- * add unit tags and an affected --tag filter ([#14](https://github.com/abinnovision/tally-release/issues/14)) ([733e556](https://github.com/abinnovision/tally-release/commit/733e55642dc582e24e06d2e2b118110547ace742))
66
+ * add unit tags and an affected --tag filter ([#14](https://github.com/abinnovision/tallyrelease/issues/14)) ([733e556](https://github.com/abinnovision/tallyrelease/commit/733e55642dc582e24e06d2e2b118110547ace742))
67
67
 
68
68
  ## 0.1.0 (2026-10-15)
69
69
 
70
70
 
71
71
  ### Features
72
72
 
73
- * bootstrap tallyrelease with build selection (v0.1) ([#1](https://github.com/abinnovision/tally-release/issues/1)) ([4efde72](https://github.com/abinnovision/tally-release/commit/4efde72e32091df3c84a2b497272a3a0efdac659))
74
- * **cli:** align human-readable output and add a local build base ([#8](https://github.com/abinnovision/tally-release/issues/8)) ([96cb5f0](https://github.com/abinnovision/tally-release/commit/96cb5f010f3090806da4317a50c23ba17884c5c5))
75
- * make units build-only unless opted into release ([#6](https://github.com/abinnovision/tally-release/issues/6)) ([4b01768](https://github.com/abinnovision/tally-release/commit/4b0176809d38504eec22e35168f57ca17840ef13))
73
+ * bootstrap tallyrelease with build selection (v0.1) ([#1](https://github.com/abinnovision/tallyrelease/issues/1)) ([4efde72](https://github.com/abinnovision/tallyrelease/commit/4efde72e32091df3c84a2b497272a3a0efdac659))
74
+ * **cli:** align human-readable output and add a local build base ([#8](https://github.com/abinnovision/tallyrelease/issues/8)) ([96cb5f0](https://github.com/abinnovision/tallyrelease/commit/96cb5f010f3090806da4317a50c23ba17884c5c5))
75
+ * make units build-only unless opted into release ([#6](https://github.com/abinnovision/tallyrelease/issues/6)) ([4b01768](https://github.com/abinnovision/tallyrelease/commit/4b0176809d38504eec22e35168f57ca17840ef13))
76
76
 
77
77
 
78
78
  ### Bug Fixes
79
79
 
80
- * **cli:** clearer errors and package docs for the 0.1.0 release ([#11](https://github.com/abinnovision/tally-release/issues/11)) ([85fc7ed](https://github.com/abinnovision/tally-release/commit/85fc7ed70693bae01c9b90319d808f72f5eef6f6))
80
+ * **cli:** clearer errors and package docs for the 0.1.0 release ([#11](https://github.com/abinnovision/tallyrelease/issues/11)) ([85fc7ed](https://github.com/abinnovision/tallyrelease/commit/85fc7ed70693bae01c9b90319d808f72f5eef6f6))
package/dist/branches.js CHANGED
@@ -1,10 +1,10 @@
1
- import { TallyError } from "./errors.js";
1
+ import { coreCodes } from "./codes.js";
2
2
  import { schemeRegistry } from "./schemes.js";
3
3
  import { maxLevel } from "./util.js";
4
4
  const SPECIAL = /[\\^$.*+?()[\]{}|/]/gu;
5
5
  const PART = /\{(major|minor)\}|\*/gu;
6
6
  const CHANNEL = /^[0-9A-Za-z-]+$/u;
7
- const ruleError = (rule, problem) => new TallyError("E_BRANCH_RULE", `branch rule "${rule.name}" ${problem}`, {
7
+ const ruleError = (rule, problem) => coreCodes.error("E_BRANCH_RULE", `branch rule "${rule.name}" ${problem}`, {
8
8
  rule: rule.name,
9
9
  });
10
10
  /** Anchored regex of a rule name; throws `E_BRANCH_RULE`. */
@@ -72,16 +72,16 @@ export const validateBranches = (rules) => {
72
72
  continue;
73
73
  }
74
74
  if (rule.name.includes("*")) {
75
- throw new TallyError("E_CHANNEL_WILDCARD", `branch rule "${rule.name}" contains * and cannot set a channel`, { rule: rule.name, channel });
75
+ throw coreCodes.error("E_CHANNEL_WILDCARD", `branch rule "${rule.name}" contains * and cannot set a channel`, { rule: rule.name, channel });
76
76
  }
77
77
  const literal = channel.replace(/\{(major|minor)\}/gu, (token) => rule.name.includes(token) ? "0" : token);
78
78
  if (!CHANNEL.test(literal) ||
79
79
  !/[A-Za-z-]/u.test(channel.replace(/\{(major|minor)\}/gu, ""))) {
80
- throw new TallyError("E_CHANNEL_INVALID", `channel "${channel}" of branch rule "${rule.name}" must match ${CHANNEL.source}, contain a letter or dash and use only tokens captured by the rule`, { rule: rule.name, channel });
80
+ throw coreCodes.error("E_CHANNEL_INVALID", `channel "${channel}" of branch rule "${rule.name}" must match ${CHANNEL.source}, contain a letter or dash and use only tokens captured by the rule`, { rule: rule.name, channel });
81
81
  }
82
82
  const other = channels.get(channel);
83
83
  if (other !== undefined) {
84
- throw new TallyError("E_CHANNEL_DUPLICATE", `channel "${channel}" is set by branch rules "${other}" and "${rule.name}"`, { channel, rules: [other, rule.name] });
84
+ throw coreCodes.error("E_CHANNEL_DUPLICATE", `channel "${channel}" is set by branch rules "${other}" and "${rule.name}"`, { channel, rules: [other, rule.name] });
85
85
  }
86
86
  channels.set(channel, rule.name);
87
87
  }
@@ -139,7 +139,7 @@ export const checkLimits = (line, items, { branch, implicit }) => {
139
139
  if (from.major !== absolute.major ||
140
140
  (absolute.minor !== undefined && from.minor !== absolute.minor)) {
141
141
  const expected = [absolute.major, absolute.minor ?? "x"].join(".");
142
- throw new TallyError("E_CAP_BASE", `branch "${branch}": ${itemLabel(target)}${implicit ? " (the only release item)" : ""} is at ${from.version}, off the ${expected} line of branch rule "${line.rule}"; the branch was cut from or merged with a commit carrying a newer tag, or names a next major, which maintenance rules do not support (use a default-line rule with a channel)`, {
142
+ throw coreCodes.error("E_CAP_BASE", `branch "${branch}": ${itemLabel(target)}${implicit ? " (the only release item)" : ""} is at ${from.version}, off the ${expected} line of branch rule "${line.rule}"; the branch was cut from or merged with a commit carrying a newer tag, or names a next major, which maintenance rules do not support (use a default-line rule with a channel)`, {
143
143
  ...itemDetails(target),
144
144
  branch,
145
145
  rule: line.rule,
@@ -157,7 +157,7 @@ export const checkLimits = (line, items, { branch, implicit }) => {
157
157
  const commits = item.commits.filter((c) => (c.level !== null && maxLevel(c.level, limit) !== limit) ||
158
158
  (c.releaseAs !== undefined && !within(limit, from, c.releaseAs)));
159
159
  const listed = commits.map(commitLabel).join("; ");
160
- throw new TallyError("E_CAP", `${itemLabel(item)}: ${from.version} to ${next.version} exceeds the ${limit} limit of branch rule "${line.rule}"${listed === "" ? "" : `; commits: ${listed}`}`, {
160
+ throw coreCodes.error("E_CAP", `${itemLabel(item)}: ${from.version} to ${next.version} exceeds the ${limit} limit of branch rule "${line.rule}"${listed === "" ? "" : `; commits: ${listed}`}`, {
161
161
  ...itemDetails(item),
162
162
  rule: line.rule,
163
163
  limit,
@@ -0,0 +1,26 @@
1
+ import { TallyError } from "./errors.js";
2
+ import type { Warning } from "./errors.js";
3
+ type Checked<R> = {
4
+ [K in keyof R]: K extends `${"E" | "W"}_${string}` ? CodeDoc : never;
5
+ };
6
+ /** Documentation of one error or warning code. */
7
+ export interface CodeDoc {
8
+ cause: string;
9
+ fix: string;
10
+ }
11
+ export type ErrorCode<C extends string> = Extract<C, `E_${string}`>;
12
+ export type WarningCode<C extends string> = Extract<C, `W_${string}`>;
13
+ /** Documented codes and constructors typed to them. */
14
+ export interface CodeRegistry<C extends string> {
15
+ readonly docs: Readonly<Record<C, CodeDoc>>;
16
+ error: (code: ErrorCode<C>, message: string, details?: Record<string, unknown>) => TallyError;
17
+ warning: (code: WarningCode<C>, message: string, extra?: Pick<Warning, "plugin" | "unit">) => Warning;
18
+ }
19
+ export type CodeOf<R> = R extends CodeRegistry<infer C> ? C : never;
20
+ /** Builds a registry from `E_*` and `W_*` codes and their documentation. */
21
+ export declare const defineCodes: <R extends Record<string, CodeDoc>>(docs: R & Checked<R>) => CodeRegistry<keyof R & string>;
22
+ /**
23
+ * Codes raised by core. Codes are only added, never removed or renamed.
24
+ */
25
+ export declare const coreCodes: CodeRegistry<"E_BRANCH_RULE" | "E_CAP" | "E_CAP_BASE" | "E_CHANNEL_DUPLICATE" | "E_CHANNEL_INVALID" | "E_CHANNEL_WILDCARD" | "E_CONFIG" | "E_DUPLICATE_ROOT" | "E_DUPLICATE_UNIT" | "E_EDGE_SELF" | "E_EDGE_UNKNOWN_UNIT" | "E_GIT_DATE" | "E_GROUP_NOT_RELEASED" | "E_GROUP_OVERLAP" | "E_GROUP_SCHEME" | "E_GROUP_UNKNOWN_UNIT" | "E_INITIAL_VERSION" | "E_PLUGIN_API" | "E_PLUGIN_HOOK" | "E_PLUGIN_NAME" | "E_PLUGIN_OPTIONS" | "E_PLUGIN_UNKNOWN" | "E_RELEASE_ITEM_NAME" | "E_RELEASE_PR_SPLIT_EDGE" | "E_ROOT_INVALID" | "E_ROOT_MISSING" | "E_SCHEME_CLASH" | "E_SCHEME_UNKNOWN" | "E_TAG_FORMAT" | "E_TAG_FORMAT_OVERLAP" | "E_UNIT_GLOB" | "E_UNIT_ID" | "E_UNIT_TAG" | "E_UNIT_UNKNOWN" | "E_VERSION_FILE_CLAIM" | "E_VERSION_FILE_MISSING" | "E_VERSION_INVALID" | "W_CYCLE" | "W_GROUP_EMPTY" | "W_INITIAL_BELOW_PRERELEASE" | "W_NO_BRANCH_RULE" | "W_NO_MATCHING_TAG" | "W_NO_RELEASED_UNITS" | "W_PLUGIN" | "W_RELEASED_NO_BASE" | "W_RELEASE_AS_CONFLICT" | "W_RELEASE_AS_IGNORED" | "W_RELEASE_AS_INVALID" | "W_RELEASE_AS_UNKNOWN" | "W_RELEASE_NOT_BUILT" | "W_UNIT_GLOB_UNMATCHED" | "W_UNIT_UNKNOWN" | "W_VERSION_FILE_NO_MATCH">;
26
+ export {};
package/dist/codes.js ADDED
@@ -0,0 +1,224 @@
1
+ import { TallyError } from "./errors.js";
2
+ /** Builds a registry from `E_*` and `W_*` codes and their documentation. */
3
+ export const defineCodes = (docs) => ({
4
+ docs,
5
+ error: (code, message, details) => new TallyError(code, message, details),
6
+ warning: (code, message, extra) => ({ code, message, ...extra }),
7
+ });
8
+ /**
9
+ * Codes raised by core. Codes are only added, never removed or renamed.
10
+ */
11
+ export const coreCodes = defineCodes({
12
+ E_BRANCH_RULE: {
13
+ cause: "An entry of `branches` is invalid. The `name` is empty, uses a token other than `{major}` or `{minor}`, repeats a token, uses `{minor}` without `{major}` or has an unbalanced brace. Or `unit` and `group` are both set, `unit` or `group` is set on a rule that is not a `maintenance` rule with `{major}`, or `unit` names a unit that is not released, is in a group or does not exist, or `group` names an unknown group.",
14
+ fix: "Correct the rule named in the message. Use either `unit` (ungrouped released unit) or `group`, only on `line: maintenance` rules whose `name` contains `{major}`, and name a unit or group that exists in the config.",
15
+ },
16
+ E_CAP: {
17
+ cause: "On a maintenance branch, a release item would leave the line of its current version, for example a breaking change or `Release-As` on a `release/1.x` branch. The message lists the offending commits.",
18
+ fix: "Remove or reword the commits that exceed the limit, for example by moving them to the default branch, or use a `Release-As` value inside the line. If the branch should release a new major, use a `default` line rule with a `channel` instead of a `maintenance` rule.",
19
+ },
20
+ E_CAP_BASE: {
21
+ cause: "On a maintenance branch, the unit or group named by the rule (or the only release item) is at a version outside the line captured by the branch name. Typically the branch was cut from or merged with a commit that carries a newer tag.",
22
+ fix: "Cut the branch from the last release of that line, or remove the newer commits from it. Set `unit` or `group` on the rule to name the item the line applies to.",
23
+ },
24
+ E_CHANNEL_DUPLICATE: {
25
+ cause: "Two `branches` rules set the same `channel`.",
26
+ fix: "Give each rule a distinct `channel`, or remove the `channel` from one of them.",
27
+ },
28
+ E_CHANNEL_INVALID: {
29
+ cause: "A rule's `channel` is not a valid prerelease identifier: it must match `[0-9A-Za-z-]+`, contain a letter or dash, and use only `{major}` and `{minor}` tokens that the rule's `name` captures. The same code is raised when the channel does not form a valid prerelease for a version.",
30
+ fix: "Use letters, digits and dashes only (for example `beta` or `rc-{major}`) and capture any token you use in the rule `name`.",
31
+ },
32
+ E_CHANNEL_WILDCARD: {
33
+ cause: "A `branches` rule sets a `channel` while its `name` contains `*`, so the channel would be ambiguous.",
34
+ fix: "Remove the `*` from the rule `name`, or remove the `channel` from that rule.",
35
+ },
36
+ E_CONFIG: {
37
+ cause: "The config file is not valid YAML, does not match the schema (unknown key, wrong type), or fails a semantic check: a unit sets `releasePr` while being in a group, a `tags.floating` format uses a token other than `{id}`, `{major}` or `{minor}`, or a `versionFiles` entry has a bad `file` or `pattern`.",
38
+ fix: "Fix the key named in the message. A `versionFiles` `pattern` needs literal text and either `{version}` or the `{major}`, `{minor}`, `{patch}` tokens (not mixed), and `file` must be a repo-relative path without `..`. Set `releasePr` on the group instead of on its members.",
39
+ },
40
+ E_DUPLICATE_ROOT: {
41
+ cause: "Two plugins discover a unit at the same root.",
42
+ fix: "Disable one of the plugins under `plugins`, or move one of the packages so each root is discovered once.",
43
+ },
44
+ E_DUPLICATE_UNIT: {
45
+ cause: "Two units resolve to the same id.",
46
+ fix: "Give one of them a distinct `id` in `units.<root>`.",
47
+ },
48
+ E_EDGE_SELF: {
49
+ cause: "A dependency edge points from a unit to itself. The message names the source, either a plugin or `config` (a `deps` entry).",
50
+ fix: "Remove the edge, or correct `from` or `to` in the `deps` entry.",
51
+ },
52
+ E_EDGE_UNKNOWN_UNIT: {
53
+ cause: "A dependency edge names a unit id that does not exist. The message names the source, either a plugin or `config` (a `deps` entry).",
54
+ fix: "Correct `from` or `to` in the `deps` entry so both are existing unit ids.",
55
+ },
56
+ E_GIT_DATE: {
57
+ cause: "A commit has a committer date that cannot be parsed.",
58
+ fix: "Check the commit named in the message with `git log --format=%cI <sha>`. Rewrite the history if the date is corrupt, or plan with a `--base` after that commit.",
59
+ },
60
+ E_GROUP_NOT_RELEASED: {
61
+ cause: "A group member is a unit that is not released.",
62
+ fix: "Set `release: true` on the unit (or `defaults.release: true`), or remove it from the group's `units` or `tags` selection.",
63
+ },
64
+ E_GROUP_OVERLAP: {
65
+ cause: "A unit is a member of two groups.",
66
+ fix: "Adjust `groups.<name>.units` and `groups.<name>.tags` so every unit belongs to at most one group.",
67
+ },
68
+ E_GROUP_SCHEME: {
69
+ cause: "Members of a group use different version schemes.",
70
+ fix: "Set the same `scheme` on all members of the group, or split the group.",
71
+ },
72
+ E_GROUP_UNKNOWN_UNIT: {
73
+ cause: "A group's `units` list names a unit id that does not exist.",
74
+ fix: "Correct the id in `groups.<name>.units`.",
75
+ },
76
+ E_INITIAL_VERSION: {
77
+ cause: "A unit's `initialVersion` is not a valid version for its scheme.",
78
+ fix: "Set `initialVersion` (on the unit or in `defaults`) to a version its scheme accepts, for example `1.0.0` for `semver`.",
79
+ },
80
+ E_PLUGIN_API: {
81
+ cause: "A plugin uses an API version that this tallyrelease does not support.",
82
+ fix: "Use a plugin built for this version of tallyrelease, or upgrade or downgrade the CLI to match the plugin.",
83
+ },
84
+ E_PLUGIN_HOOK: {
85
+ cause: "A plugin hook (such as `discover`, `edges`, `intent` or `write`) threw an error. The message names the plugin, the hook and the reason.",
86
+ fix: "Fix the cause given in the message, usually a malformed manifest or version file in the repository, or the plugin options in `plugins.<name>`.",
87
+ },
88
+ E_PLUGIN_NAME: {
89
+ cause: "A plugin name does not match `^[a-z][a-z0-9-]*$`, or uses the name `core`, which is reserved.",
90
+ fix: "Rename the plugin to lowercase letters, digits and dashes starting with a letter, and not `core`.",
91
+ },
92
+ E_PLUGIN_OPTIONS: {
93
+ cause: "The options under `plugins.<name>` fail the plugin's option validation.",
94
+ fix: "Fix the options named in the message, or remove them.",
95
+ },
96
+ E_PLUGIN_UNKNOWN: {
97
+ cause: "`plugins` lists a name that matches no built-in plugin (`node`, `php`, `kustomize`).",
98
+ fix: "Correct the plugin name in `plugins`, or remove the entry.",
99
+ },
100
+ E_RELEASE_ITEM_NAME: {
101
+ cause: "A release item (a group or an ungrouped released unit) has a name that cannot be used in a release PR branch: it is `shared`, starts or ends with `.`, ends with `.lock`, contains `..`, or a unit id equals a group name.",
102
+ fix: "Rename the unit (`units.<root>.id`) or the group so the name is valid and unique.",
103
+ },
104
+ E_RELEASE_PR_SPLIT_EDGE: {
105
+ cause: "A dependency edge that propagates a patch release connects two release items that are in different release PRs.",
106
+ fix: "Put both items in the same release PR with `releasePr` (or `defaults.releasePr`), group them under `groups`, or set `propagate: rebuild` on the dependent unit.",
107
+ },
108
+ E_ROOT_INVALID: {
109
+ cause: "A unit root, such as a key under `units` or a root discovered by a plugin, is absolute or contains `..` segments.",
110
+ fix: "Use repo-relative paths without `..`.",
111
+ },
112
+ E_ROOT_MISSING: {
113
+ cause: "The root of a unit does not exist in the working tree, for example a key under `units` that points to a missing directory.",
114
+ fix: "Create the directory, or correct or remove the `units` key.",
115
+ },
116
+ E_SCHEME_CLASH: {
117
+ cause: "Two plugins register a version scheme under the same name.",
118
+ fix: "Disable one of the plugins under `plugins`.",
119
+ },
120
+ E_SCHEME_UNKNOWN: {
121
+ cause: "A released unit's `scheme` is not registered by any enabled plugin.",
122
+ fix: "Correct `scheme` on the unit, or enable the plugin that provides it under `plugins`. The default scheme is `semver`.",
123
+ },
124
+ E_TAG_FORMAT: {
125
+ cause: "A unit's `tagFormat` does not contain `{version}` exactly once.",
126
+ fix: "Set `tagFormat` (on the unit or in `defaults`) to a template with a single `{version}`, for example `{id}-v{version}`.",
127
+ },
128
+ E_TAG_FORMAT_OVERLAP: {
129
+ cause: "Two released units render the same tag format, or one existing tag matches the format of more than one released unit.",
130
+ fix: "Give the units distinct `tagFormat` values, usually by including `{id}`.",
131
+ },
132
+ E_UNIT_GLOB: {
133
+ cause: "A glob key under `units` uses `*` inside a path segment instead of as a whole segment, or sets `id`, `include`, `exclude` or `versionFiles`, which globs cannot set.",
134
+ fix: "Use `*` only as a whole path segment (`packages/*`), and move those fields to an exact key.",
135
+ },
136
+ E_UNIT_ID: {
137
+ cause: "A unit id contains characters other than letters, digits, `.`, `_` and `-`, or is `.` or `..`.",
138
+ fix: "Set a valid `id` in `units.<root>`.",
139
+ },
140
+ E_UNIT_TAG: {
141
+ cause: "An entry in a unit's `tags` contains characters other than letters, digits, `.`, `_` and `-`.",
142
+ fix: "Rename the label in `units.<root>.tags`.",
143
+ },
144
+ E_UNIT_UNKNOWN: {
145
+ cause: "`explain` was given a unit id that does not exist.",
146
+ fix: "Pass an existing unit id. List ids with `tallyrelease graph`.",
147
+ },
148
+ E_VERSION_FILE_CLAIM: {
149
+ cause: "A file is the version file of two units, or is both a plugin version file and a `versionFiles` template target.",
150
+ fix: "Remove the duplicate `versionFiles` entry, or set one file per unit.",
151
+ },
152
+ E_VERSION_FILE_MISSING: {
153
+ cause: "A version file of a unit (from a plugin or a `versionFiles` entry) does not exist in the commit the release is based on.",
154
+ fix: "Create the file, or correct or remove the `versionFiles` entry.",
155
+ },
156
+ E_VERSION_INVALID: {
157
+ cause: "Bumping a version produced a string that is not a valid version. This does not happen for a valid current version.",
158
+ fix: "Report it as a bug with the version named in the message and the `--verbose` output.",
159
+ },
160
+ W_CYCLE: {
161
+ cause: "The dependency graph has a cycle. The message shows the path.",
162
+ fix: "Remove one of the edges, for example a `deps` entry. The plan still runs, but the order of units in a cycle is not meaningful.",
163
+ },
164
+ W_GROUP_EMPTY: {
165
+ cause: "A group in `groups` selects no released unit through `units` or `tags`, so it is dropped.",
166
+ fix: "Add member ids to `groups.<name>.units`, tag units with a label listed in `groups.<name>.tags`, or remove the group.",
167
+ },
168
+ W_INITIAL_BELOW_PRERELEASE: {
169
+ cause: "A unit's initial version does not exceed an existing prerelease tag of that unit.",
170
+ fix: "Raise `initialVersion` above the prerelease tag named in the message, or remove the stale tag.",
171
+ },
172
+ W_NO_BRANCH_RULE: {
173
+ cause: "No `branches` rule matches the branch, so versions in the plan are null.",
174
+ fix: "Add a rule for the branch under `branches`, for example `{ name: main, line: default }`, or pass `--branch`.",
175
+ },
176
+ W_NO_MATCHING_TAG: {
177
+ cause: "A released unit has no tag matching its `tagFormat`, but an existing tag contains its id.",
178
+ fix: "Rename the tag or set `tagFormat` on the unit so it matches existing tags.",
179
+ },
180
+ W_NO_RELEASED_UNITS: {
181
+ cause: "A branch rule matches, but no unit is released.",
182
+ fix: "Set `release: true` on units, or `defaults.release: true`.",
183
+ },
184
+ W_PLUGIN: {
185
+ cause: "A plugin logged a warning. The text is prefixed with the plugin name.",
186
+ fix: "Follow the message. It usually points at a manifest or option the plugin could not use.",
187
+ },
188
+ W_RELEASED_NO_BASE: {
189
+ cause: "The build base is unknown, so `released` in the plan only lists releases at HEAD and leaves out earlier releases on the first-parent chain.",
190
+ fix: "Pass `--base <ref>` or make the base resolvable (see `W_NO_FORGE` and `W_NO_LAST_SUCCESS`).",
191
+ },
192
+ W_RELEASE_AS_CONFLICT: {
193
+ cause: "Several different `Release-As` values apply to a unit. The newest valid one wins and the message names it.",
194
+ fix: "Remove the extra `Release-As` footers if the winner is not the intended one.",
195
+ },
196
+ W_RELEASE_AS_IGNORED: {
197
+ cause: "A `Release-As` footer is ignored because its commit is not attributed to a released unit, or it names a unit that is not released.",
198
+ fix: "Name a released unit (`Release-As: <unit>@<version>`), or remove the footer.",
199
+ },
200
+ W_RELEASE_AS_INVALID: {
201
+ cause: "A `Release-As` value is not a version of the unit's scheme, or is a prerelease, or does not exceed the unit's current version. It is ignored.",
202
+ fix: "Correct the footer value in the commit message, or drop it.",
203
+ },
204
+ W_RELEASE_AS_UNKNOWN: {
205
+ cause: "A `Release-As: <unit>@<version>` footer names a unit that does not exist.",
206
+ fix: "Correct the unit id in the footer.",
207
+ },
208
+ W_RELEASE_NOT_BUILT: {
209
+ cause: "A release of a unit is at HEAD, but the unit is not in the build set. It is listed as a catch-up.",
210
+ fix: "Build and publish the unit for that release, or ignore the warning if it was already built.",
211
+ },
212
+ W_UNIT_GLOB_UNMATCHED: {
213
+ cause: "A glob key under `units` matches no discovered unit. Globs never create units.",
214
+ fix: "Correct the glob, or remove it.",
215
+ },
216
+ W_UNIT_UNKNOWN: {
217
+ cause: "An intent source of a plugin names a unit id that does not exist.",
218
+ fix: "Correct the unit id in the intent source, or add the unit to the config.",
219
+ },
220
+ W_VERSION_FILE_NO_MATCH: {
221
+ cause: "A `versionFiles` `pattern` of a unit matches nothing in its file, so the file is not updated for the release.",
222
+ fix: "Make `pattern` match the text that holds the previous version, or remove the entry.",
223
+ },
224
+ });
package/dist/discover.js CHANGED
@@ -1,4 +1,4 @@
1
- import { TallyError } from "./errors.js";
1
+ import { coreCodes } from "./codes.js";
2
2
  import { defaultUnitId } from "./plugin.js";
3
3
  import { runHook } from "./plugins.js";
4
4
  import { compare, uniqueSorted } from "./util.js";
@@ -16,11 +16,11 @@ const GLOB_REJECTED = ["id", "include", "exclude", "versionFiles"];
16
16
  const globSegments = (key, root, overlay) => {
17
17
  const segments = root.split("/");
18
18
  if (segments.some((s) => s !== "*" && s.includes("*"))) {
19
- throw new TallyError("E_UNIT_GLOB", `unit overlay "${key}" may use "*" only as a whole path segment`, { key });
19
+ throw coreCodes.error("E_UNIT_GLOB", `unit overlay "${key}" may use "*" only as a whole path segment`, { key });
20
20
  }
21
21
  const field = GLOB_REJECTED.find((f) => overlay[f] !== undefined);
22
22
  if (field !== undefined) {
23
- throw new TallyError("E_UNIT_GLOB", `unit overlay "${key}" cannot set "${field}" on a glob key`, { key, field });
23
+ throw coreCodes.error("E_UNIT_GLOB", `unit overlay "${key}" cannot set "${field}" on a glob key`, { key, field });
24
24
  }
25
25
  return segments;
26
26
  };
@@ -46,7 +46,7 @@ const applyOverlay = (entry, { release, tags, initialVersion, ...overlay }) => {
46
46
  export const normalizeRoot = (root) => {
47
47
  const segments = root.split("/");
48
48
  if (root.startsWith("/") || segments.includes("..")) {
49
- throw new TallyError("E_ROOT_INVALID", `root "${root}" must be repo-relative without ".." segments`, { root });
49
+ throw coreCodes.error("E_ROOT_INVALID", `root "${root}" must be repo-relative without ".." segments`, { root });
50
50
  }
51
51
  return segments.filter((s) => s !== "" && s !== ".").join("/") || ".";
52
52
  };
@@ -67,7 +67,7 @@ export const discoverUnits = async (active, env, warnings = []) => {
67
67
  const root = normalizeRoot(draft.root);
68
68
  const prior = byRoot.get(root);
69
69
  if (prior) {
70
- throw new TallyError("E_DUPLICATE_ROOT", `root "${root}" is discovered by "${prior.plugin}" and "${plugin.name}"`, { root, plugins: [prior.plugin, plugin.name] });
70
+ throw coreCodes.error("E_DUPLICATE_ROOT", `root "${root}" is discovered by "${prior.plugin}" and "${plugin.name}"`, { root, plugins: [prior.plugin, plugin.name] });
71
71
  }
72
72
  byRoot.set(root, {
73
73
  plugin: plugin.name,
@@ -82,7 +82,7 @@ export const discoverUnits = async (active, env, warnings = []) => {
82
82
  const root = normalizeRoot(key);
83
83
  const invalid = overlay.tags?.find((t) => !UNIT_TAG.test(t));
84
84
  if (invalid !== undefined) {
85
- throw new TallyError("E_UNIT_TAG", `tag "${invalid}" of unit at "${root}" must match ${UNIT_TAG.source}`, { tag: invalid, root });
85
+ throw coreCodes.error("E_UNIT_TAG", `tag "${invalid}" of unit at "${root}" must match ${UNIT_TAG.source}`, { tag: invalid, root });
86
86
  }
87
87
  if (root.includes("*")) {
88
88
  globs.push([key, globSegments(key, root, overlay), overlay]);
@@ -102,10 +102,7 @@ export const discoverUnits = async (active, env, warnings = []) => {
102
102
  for (const [key, segments, overlay] of globs) {
103
103
  const matched = [...byRoot.entries()].filter(([root]) => globMatches(segments, root));
104
104
  if (matched.length === 0) {
105
- warnings.push({
106
- code: "W_UNIT_GLOB_UNMATCHED",
107
- message: `unit overlay "${key}" matches no unit`,
108
- });
105
+ warnings.push(coreCodes.warning("W_UNIT_GLOB_UNMATCHED", `unit overlay "${key}" matches no unit`));
109
106
  }
110
107
  for (const [, entry] of matched) {
111
108
  applyOverlay(entry, overlay);
@@ -118,11 +115,11 @@ export const discoverUnits = async (active, env, warnings = []) => {
118
115
  for (const entry of byRoot.values()) {
119
116
  const { id, root } = entry.draft;
120
117
  if (!UNIT_ID.test(id)) {
121
- throw new TallyError("E_UNIT_ID", `unit id "${id}" at "${root}" must match ${UNIT_ID.source}`, { id, root, plugin: entry.plugin });
118
+ throw coreCodes.error("E_UNIT_ID", `unit id "${id}" at "${root}" must match ${UNIT_ID.source}`, { id, root, plugin: entry.plugin });
122
119
  }
123
120
  const prior = byId.get(id);
124
121
  if (prior) {
125
- throw new TallyError("E_DUPLICATE_UNIT", `unit id "${id}" is used by "${prior.plugin}" at "${prior.draft.root}" and "${entry.plugin}" at "${root}"`, {
122
+ throw coreCodes.error("E_DUPLICATE_UNIT", `unit id "${id}" is used by "${prior.plugin}" at "${prior.draft.root}" and "${entry.plugin}" at "${root}"`, {
126
123
  id,
127
124
  owners: [
128
125
  { plugin: prior.plugin, root: prior.draft.root },
@@ -132,7 +129,7 @@ export const discoverUnits = async (active, env, warnings = []) => {
132
129
  }
133
130
  byId.set(id, entry);
134
131
  if (root !== "." && !env.fs.exists(root)) {
135
- throw new TallyError("E_ROOT_MISSING", `root "${root}" of unit "${id}" does not exist`, { id, root });
132
+ throw coreCodes.error("E_ROOT_MISSING", `root "${root}" of unit "${id}" does not exist`, { id, root });
136
133
  }
137
134
  }
138
135
  const roots = [...byRoot.keys()].sort(compare);
package/dist/explain.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import { attribution, matcher } from "./attribution.js";
2
2
  import { changelogPath } from "./changelog.js";
3
- import { TallyError } from "./errors.js";
3
+ import { coreCodes } from "./codes.js";
4
4
  import { planWithFacts } from "./plan.js";
5
5
  /** Explains one unit of the plan of `opts.head`; throws `E_UNIT_UNKNOWN`. */
6
6
  export const explainUnit = async (workspace, git, { unit: id, ...opts }) => {
7
7
  if (!workspace.units.some((u) => u.id === id)) {
8
- throw new TallyError("E_UNIT_UNKNOWN", `unknown unit "${id}"`, {
8
+ throw coreCodes.error("E_UNIT_UNKNOWN", `unknown unit "${id}"`, {
9
9
  unit: id,
10
10
  });
11
11
  }
package/dist/facts.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { attribution, matcher } from "./attribution.js";
2
- import { TallyError } from "./errors.js";
2
+ import { coreCodes } from "./codes.js";
3
3
  import { pluginCtx, runHook } from "./plugins.js";
4
4
  import { schemeRegistry } from "./schemes.js";
5
5
  import { matchUnitTags } from "./tags.js";
@@ -7,7 +7,7 @@ import { compare, uniqueSorted } from "./util.js";
7
7
  const instant = (commit) => {
8
8
  const time = Date.parse(commit.date);
9
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 });
10
+ throw coreCodes.error("E_GIT_DATE", `commit ${commit.sha} has an invalid committer date "${commit.date}"`, { sha: commit.sha, date: commit.date });
11
11
  }
12
12
  return time;
13
13
  };
@@ -74,11 +74,7 @@ export const collectPlanFacts = async (workspace, git, opts) => {
74
74
  const key = `${plugin}\0${id}`;
75
75
  if (!known.has(id) && !warned.has(key)) {
76
76
  warned.add(key);
77
- warnings.push({
78
- code: "W_UNIT_UNKNOWN",
79
- message: `intent names unknown unit "${id}"`,
80
- plugin,
81
- });
77
+ warnings.push(coreCodes.warning("W_UNIT_UNKNOWN", `intent names unknown unit "${id}"`, { plugin }));
82
78
  }
83
79
  }
84
80
  };
package/dist/floating.js CHANGED
@@ -1,4 +1,4 @@
1
- import { TallyError } from "./errors.js";
1
+ import { coreCodes } from "./codes.js";
2
2
  import { compileTagFormat } from "./tags.js";
3
3
  const TOKEN = /\{([^{}]*)\}/gu;
4
4
  const TOKENS = new Set(["id", "major", "minor"]);
@@ -7,7 +7,7 @@ export const validateFloating = (formats) => {
7
7
  for (const format of formats) {
8
8
  const unknown = [...format.matchAll(TOKEN)].find(([, token]) => !TOKENS.has(token ?? ""));
9
9
  if (unknown) {
10
- throw new TallyError("E_CONFIG", `floating tag format "${format}" uses unknown token "${unknown[0]}"; use {id}, {major} or {minor}`, { format });
10
+ throw coreCodes.error("E_CONFIG", `floating tag format "${format}" uses unknown token "${unknown[0]}"; use {id}, {major} or {minor}`, { format });
11
11
  }
12
12
  }
13
13
  };
package/dist/graph.js CHANGED
@@ -1,4 +1,4 @@
1
- import { TallyError } from "./errors.js";
1
+ import { coreCodes } from "./codes.js";
2
2
  import { compare, uniqueSorted } from "./util.js";
3
3
  /** Tarjan's algorithm; components are emitted after everything they reach. */
4
4
  const stronglyConnected = (ids, next) => {
@@ -73,11 +73,11 @@ export const buildGraph = (units, drafts) => {
73
73
  for (const { from, to, reason, pinned, source } of drafts) {
74
74
  for (const id of [from, to]) {
75
75
  if (!known.has(id)) {
76
- throw new TallyError("E_EDGE_UNKNOWN_UNIT", `edge ${from} -> ${to} from "${source}" names unknown unit "${id}"`, { from, to, source, unit: id });
76
+ throw coreCodes.error("E_EDGE_UNKNOWN_UNIT", `edge ${from} -> ${to} from "${source}" names unknown unit "${id}"`, { from, to, source, unit: id });
77
77
  }
78
78
  }
79
79
  if (from === to) {
80
- throw new TallyError("E_EDGE_SELF", `edge ${from} -> ${to} from "${source}" points to itself`, { from, to, source });
80
+ throw coreCodes.error("E_EDGE_SELF", `edge ${from} -> ${to} from "${source}" points to itself`, { from, to, source });
81
81
  }
82
82
  const key = `${from}\n${to}`;
83
83
  const edge = merged.get(key);
@@ -115,10 +115,7 @@ export const buildGraph = (units, drafts) => {
115
115
  const components = stronglyConnected(ids, dependencies);
116
116
  const warnings = components
117
117
  .filter((c) => c.length > 1)
118
- .map((c) => ({
119
- code: "W_CYCLE",
120
- message: `dependency cycle: ${cyclePath(c, dependents).join(" -> ")}`,
121
- }));
118
+ .map((c) => coreCodes.warning("W_CYCLE", `dependency cycle: ${cyclePath(c, dependents).join(" -> ")}`));
122
119
  return {
123
120
  edges,
124
121
  dependents,
package/dist/groups.js CHANGED
@@ -1,4 +1,4 @@
1
- import { TallyError } from "./errors.js";
1
+ import { coreCodes } from "./codes.js";
2
2
  import { compare, uniqueSorted } from "./util.js";
3
3
  /**
4
4
  * Resolves group members: `units` by id, `tags` to released units with any of
@@ -13,7 +13,7 @@ export const resolveGroups = (units, config, warnings) => {
13
13
  const { units: ids = [], tags = [] } = config?.[name] ?? {};
14
14
  const unknown = ids.find((id) => !known.has(id));
15
15
  if (unknown !== undefined) {
16
- throw new TallyError("E_GROUP_UNKNOWN_UNIT", `group "${name}" names unknown unit "${unknown}"`, { group: name, unit: unknown });
16
+ throw coreCodes.error("E_GROUP_UNKNOWN_UNIT", `group "${name}" names unknown unit "${unknown}"`, { group: name, unit: unknown });
17
17
  }
18
18
  const members = uniqueSorted([
19
19
  ...ids,
@@ -22,16 +22,13 @@ export const resolveGroups = (units, config, warnings) => {
22
22
  .map((u) => u.id),
23
23
  ]);
24
24
  if (members.length === 0) {
25
- warnings.push({
26
- code: "W_GROUP_EMPTY",
27
- message: `group "${name}" selects no unit`,
28
- });
25
+ warnings.push(coreCodes.warning("W_GROUP_EMPTY", `group "${name}" selects no unit`));
29
26
  continue;
30
27
  }
31
28
  for (const id of members) {
32
29
  const other = owner.get(id);
33
30
  if (other !== undefined) {
34
- throw new TallyError("E_GROUP_OVERLAP", `unit "${id}" is in groups "${other}" and "${name}"`, { unit: id, groups: [other, name] });
31
+ throw coreCodes.error("E_GROUP_OVERLAP", `unit "${id}" is in groups "${other}" and "${name}"`, { unit: id, groups: [other, name] });
35
32
  }
36
33
  owner.set(id, name);
37
34
  }
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  export declare const version = "0.0.0";
3
3
  export type { AffectedDoc, AffectedOptions, AffectedReason, AffectedUnit, BaseSource, } from "./affected.js";
4
4
  export type { ChangelogDependency, ChangelogInput } from "./changelog.js";
5
+ export type { CodeDoc, CodeOf, CodeRegistry, ErrorCode, WarningCode, } from "./codes.js";
5
6
  export type { ParsedCommit } from "./conventional.js";
6
7
  export type { ExplainedChange, ExplainOptions, ReleaseAsFooter, UnitExplanation, } from "./explain.js";
7
8
  export type { GroupPlan, Plan, PlanChange, PlanLine, PlanOptions, ReleasedUnit, UnitPlan, } from "./plan.js";
@@ -20,6 +21,7 @@ export type { IntentEntry, Workspace, WorkspaceInput } from "./workspace.js";
20
21
  export { computeAffected } from "./affected.js";
21
22
  export { maintenanceCut } from "./branches.js";
22
23
  export { changelogSection, releaseNotes, renderChangelogSection, updateChangelog, } from "./changelog.js";
24
+ export { coreCodes, defineCodes } from "./codes.js";
23
25
  export { CONFIG_FILE } from "./config.js";
24
26
  export { conventionalCommits, parseCommit } from "./conventional.js";
25
27
  export { discoverUnits } from "./discover.js";
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@ export const version = "0.0.0";
3
3
  export { computeAffected } from "./affected.js";
4
4
  export { maintenanceCut } from "./branches.js";
5
5
  export { changelogSection, releaseNotes, renderChangelogSection, updateChangelog, } from "./changelog.js";
6
+ export { coreCodes, defineCodes } from "./codes.js";
6
7
  export { CONFIG_FILE } from "./config.js";
7
8
  export { conventionalCommits, parseCommit } from "./conventional.js";
8
9
  export { discoverUnits } from "./discover.js";
package/dist/plan.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { computeAffected } from "./affected.js";
2
2
  import { checkLimits, matchBranch } from "./branches.js";
3
+ import { coreCodes } from "./codes.js";
3
4
  import { collectPlanFacts } from "./facts.js";
4
5
  import { propagate } from "./propagate.js";
5
6
  import { changeLevel, footerOf, reach, releaseAsWarnings, releaseGroup, releaseUnit, unitIntent, } from "./release.js";
@@ -9,18 +10,12 @@ import { validateRelease } from "./validate.js";
9
10
  const lineWarnings = (workspace, branch, line) => {
10
11
  if (!line) {
11
12
  return [
12
- {
13
- code: "W_NO_BRANCH_RULE",
14
- message: `no branch rule matches "${branch}"; versions are null`,
15
- },
13
+ coreCodes.warning("W_NO_BRANCH_RULE", `no branch rule matches "${branch}"; versions are null`),
16
14
  ];
17
15
  }
18
16
  return workspace.units.every((u) => u.version === "none")
19
17
  ? [
20
- {
21
- code: "W_NO_RELEASED_UNITS",
22
- message: `branch rule "${line.rule}" matches, but no unit is released`,
23
- },
18
+ coreCodes.warning("W_NO_RELEASED_UNITS", `branch rule "${line.rule}" matches, but no unit is released`),
24
19
  ]
25
20
  : [];
26
21
  };
package/dist/plugin.js CHANGED
@@ -1,14 +1,14 @@
1
- import { TallyError } from "./errors.js";
1
+ import { coreCodes } from "./codes.js";
2
2
  const PLUGIN_NAME = /^[a-z][a-z0-9-]*$/;
3
3
  export const PLUGIN_API_VERSION = 1;
4
4
  /** Throws `E_PLUGIN_API` or `E_PLUGIN_NAME` for an unsupported plugin. */
5
5
  export const assertPlugin = (plugin) => {
6
6
  const name = String(plugin.name);
7
7
  if (plugin.apiVersion !== PLUGIN_API_VERSION) {
8
- throw new TallyError("E_PLUGIN_API", `plugin "${name}" uses API version ${String(plugin.apiVersion)}, supported is ${String(PLUGIN_API_VERSION)}`, { plugin: name, apiVersion: plugin.apiVersion });
8
+ throw coreCodes.error("E_PLUGIN_API", `plugin "${name}" uses API version ${String(plugin.apiVersion)}, supported is ${String(PLUGIN_API_VERSION)}`, { plugin: name, apiVersion: plugin.apiVersion });
9
9
  }
10
10
  if (typeof plugin.name !== "string" || !PLUGIN_NAME.test(plugin.name)) {
11
- throw new TallyError("E_PLUGIN_NAME", `plugin name "${name}" must match ${PLUGIN_NAME.source}`, { plugin: name });
11
+ throw coreCodes.error("E_PLUGIN_NAME", `plugin name "${name}" must match ${PLUGIN_NAME.source}`, { plugin: name });
12
12
  }
13
13
  };
14
14
  /** Validates name and API version, returns the plugin unchanged. */
package/dist/plugins.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { CORE_PLUGIN } from "./builtin.js";
2
- import { TallyError } from "./errors.js";
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. */
5
5
  export const pluginCtx = (env, warnings) => (name, options) => Object.freeze({
@@ -8,7 +8,7 @@ export const pluginCtx = (env, warnings) => (name, options) => Object.freeze({
8
8
  fs: env.fs,
9
9
  log: Object.freeze({
10
10
  warn: (message) => {
11
- warnings.push({ code: "W_PLUGIN", message, plugin: name });
11
+ warnings.push(coreCodes.warning("W_PLUGIN", message, { plugin: name }));
12
12
  },
13
13
  debug: (message) => env.debug?.(`${name}: ${message}`),
14
14
  }),
@@ -19,13 +19,13 @@ export const activatePlugins = async (env, warnings) => {
19
19
  for (const [name, raw] of Object.entries(env.config.plugins)) {
20
20
  const plugin = env.plugins.find((p) => p.name === name);
21
21
  if (!plugin) {
22
- throw new TallyError("E_PLUGIN_UNKNOWN", `unknown plugin "${name}"`, {
22
+ throw coreCodes.error("E_PLUGIN_UNKNOWN", `unknown plugin "${name}"`, {
23
23
  plugin: name,
24
24
  });
25
25
  }
26
26
  assertPlugin(plugin);
27
27
  if (plugin.name === CORE_PLUGIN) {
28
- throw new TallyError("E_PLUGIN_NAME", `plugin name "${CORE_PLUGIN}" is reserved for core built-ins`, { plugin: name });
28
+ throw coreCodes.error("E_PLUGIN_NAME", `plugin name "${CORE_PLUGIN}" is reserved for core built-ins`, { plugin: name });
29
29
  }
30
30
  const { globalInputs, ...rest } = raw ?? {};
31
31
  let options = rest;
@@ -33,7 +33,7 @@ export const activatePlugins = async (env, warnings) => {
33
33
  // eslint-disable-next-line no-await-in-loop -- plugins load sequentially in config order
34
34
  const result = await plugin.options["~standard"].validate(rest);
35
35
  if (result.issues) {
36
- throw new TallyError("E_PLUGIN_OPTIONS", `invalid options for plugin "${name}": ${result.issues.map((i) => i.message).join("; ")}`, { plugin: name, issues: result.issues });
36
+ throw coreCodes.error("E_PLUGIN_OPTIONS", `invalid options for plugin "${name}": ${result.issues.map((i) => i.message).join("; ")}`, { plugin: name, issues: result.issues });
37
37
  }
38
38
  options = result.value;
39
39
  }
@@ -49,6 +49,6 @@ export const runHook = async (plugin, hook, call) => {
49
49
  }
50
50
  catch (error) {
51
51
  const reason = error instanceof Error ? error.message : String(error);
52
- throw new TallyError("E_PLUGIN_HOOK", `plugin "${plugin}" failed in ${hook}: ${reason}`, { plugin, hook, error });
52
+ throw coreCodes.error("E_PLUGIN_HOOK", `plugin "${plugin}" failed in ${hook}: ${reason}`, { plugin, hook, error });
53
53
  }
54
54
  };
@@ -1,4 +1,4 @@
1
- import { TallyError } from "./errors.js";
1
+ import { coreCodes } from "./codes.js";
2
2
  import { releasedEdges } from "./propagate.js";
3
3
  import { renderTag } from "./tags.js";
4
4
  /** Item of each released unit, by unit id. */
@@ -90,7 +90,7 @@ export const validateReleaseItems = (workspace) => {
90
90
  for (const id of workspace.release.releasePr.units.keys()) {
91
91
  const item = byUnit.get(id);
92
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 });
93
+ throw coreCodes.error("E_CONFIG", `unit "${id}" sets releasePr but is in group "${item.name}"; set it on the group`, { unit: id, group: item.name });
94
94
  }
95
95
  }
96
96
  const groups = new Set(workspace.groups.map((g) => g.name));
@@ -99,7 +99,7 @@ export const validateReleaseItems = (workspace) => {
99
99
  ? "is also a group name"
100
100
  : itemNameProblem(item.name);
101
101
  if (problem !== undefined) {
102
- throw new TallyError("E_RELEASE_ITEM_NAME", `release item "${item.name}" ${problem}`, { item: item.name });
102
+ throw coreCodes.error("E_RELEASE_ITEM_NAME", `release item "${item.name}" ${problem}`, { item: item.name });
103
103
  }
104
104
  }
105
105
  const policies = new Map(workspace.units.map((u) => [u.id, u.propagate]));
@@ -114,7 +114,7 @@ export const validateReleaseItems = (workspace) => {
114
114
  if (!a || !b || !patch || pr(a) === pr(b)) {
115
115
  continue;
116
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] });
117
+ throw coreCodes.error("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
118
  }
119
119
  }
120
120
  };
package/dist/release.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { coreCodes } from "./codes.js";
1
2
  import { normalizeRoot } from "./discover.js";
2
3
  import { compare, maxLevel, uniqueSorted } from "./util.js";
3
4
  const DEFAULT_TYPES = {
@@ -54,21 +55,13 @@ const pickReleaseAs = (input, warnings) => {
54
55
  ? `is not a ${unit.scheme} version`
55
56
  : releaseAsProblem(scheme, version, from);
56
57
  if (problem !== undefined || version === undefined) {
57
- warnings.push({
58
- code: "W_RELEASE_AS_INVALID",
59
- message: `unit "${unit.id}": Release-As "${raw}"${at(sha)} ${problem ?? ""}; ignored`,
60
- unit: unit.id,
61
- });
58
+ warnings.push(coreCodes.warning("W_RELEASE_AS_INVALID", `unit "${unit.id}": Release-As "${raw}"${at(sha)} ${problem ?? ""}; ignored`, { unit: unit.id }));
62
59
  }
63
60
  else if (!winner) {
64
61
  winner = { ...(sha === undefined ? {} : { sha }), raw, version };
65
62
  }
66
63
  else if (scheme.compare(version, winner.version) !== 0) {
67
- warnings.push({
68
- code: "W_RELEASE_AS_CONFLICT",
69
- message: `unit "${unit.id}": Release-As "${winner.raw}"${at(winner.sha)} wins over "${raw}"${at(sha)}`,
70
- unit: unit.id,
71
- });
64
+ warnings.push(coreCodes.warning("W_RELEASE_AS_CONFLICT", `unit "${unit.id}": Release-As "${winner.raw}"${at(winner.sha)} wins over "${raw}"${at(sha)}`, { unit: unit.id }));
72
65
  }
73
66
  }
74
67
  }
@@ -119,11 +112,7 @@ const initialBelowPrerelease = (scheme, initial, facts) => {
119
112
  return scheme.compare(initial, version) > 0
120
113
  ? []
121
114
  : [
122
- {
123
- code: "W_INITIAL_BELOW_PRERELEASE",
124
- message: `unit "${facts.id}": initial version ${scheme.format(initial)} does not exceed prerelease tag "${highest.name}"`,
125
- unit: facts.id,
126
- },
115
+ coreCodes.warning("W_INITIAL_BELOW_PRERELEASE", `unit "${facts.id}": initial version ${scheme.format(initial)} does not exceed prerelease tag "${highest.name}"`, { unit: facts.id }),
127
116
  ];
128
117
  };
129
118
  /** Normalized value of the first valid Release-As of a commit. */
@@ -231,11 +220,7 @@ export const releaseAsWarnings = (units, ranges) => {
231
220
  const released = new Set(units.filter((u) => u.version !== "none").map((u) => u.id));
232
221
  const warnings = new Map();
233
222
  const warn = (code, message, unit) => {
234
- warnings.set(`${code}\0${message}`, {
235
- code,
236
- message,
237
- ...(unit === undefined ? {} : { unit }),
238
- });
223
+ warnings.set(`${code}\0${message}`, coreCodes.warning(code, message, unit === undefined ? {} : { unit }));
239
224
  };
240
225
  for (const { changes } of ranges) {
241
226
  for (const { change, units: owners } of changes) {
package/dist/released.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { coreCodes } from "./codes.js";
1
2
  import { renderFloatingTags } from "./floating.js";
2
3
  import { schemeRegistry } from "./schemes.js";
3
4
  import { matchUnitTags } from "./tags.js";
@@ -83,18 +84,11 @@ export const withReleased = async (workspace, plan, { git }) => {
83
84
  ...(omitted === 0
84
85
  ? []
85
86
  : [
86
- {
87
- code: "W_RELEASED_NO_BASE",
88
- message: "the build base is unknown; released leaves out earlier releases on the first-parent chain",
89
- },
87
+ coreCodes.warning("W_RELEASED_NO_BASE", "the build base is unknown; released leaves out earlier releases on the first-parent chain"),
90
88
  ]),
91
89
  ...released
92
90
  .filter((r) => r.releaseSha === head && r.catchUp)
93
- .map((r) => ({
94
- code: "W_RELEASE_NOT_BUILT",
95
- message: `release ${r.tag} is at HEAD but unit ${r.id} is not in the build set; it is listed as a catch-up`,
96
- unit: r.id,
97
- })),
91
+ .map((r) => coreCodes.warning("W_RELEASE_NOT_BUILT", `release ${r.tag} is at HEAD but unit ${r.id} is not in the build set; it is listed as a catch-up`, { unit: r.id })),
98
92
  ];
99
93
  return { ...plan, released, warnings: [...plan.warnings, ...warnings] };
100
94
  };
package/dist/schemes.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { corePlugin } from "./builtin.js";
2
- import { TallyError } from "./errors.js";
2
+ import { coreCodes } from "./codes.js";
3
3
  /** Schemes of core and the given plugins, in that order. */
4
4
  export const collectSchemes = (active) => [corePlugin, ...active.map((a) => a.plugin)].flatMap((plugin) => Object.entries(plugin.schemes ?? {}).map(([name, scheme]) => ({
5
5
  plugin: plugin.name,
@@ -13,7 +13,7 @@ export const schemeRegistry = (list) => {
13
13
  for (const { plugin, name, scheme } of list) {
14
14
  const owner = owners.get(name);
15
15
  if (owner !== undefined) {
16
- throw new TallyError("E_SCHEME_CLASH", `scheme "${name}" is registered by "${owner}" and "${plugin}"`, { scheme: name, plugins: [owner, plugin] });
16
+ throw coreCodes.error("E_SCHEME_CLASH", `scheme "${name}" is registered by "${owner}" and "${plugin}"`, { scheme: name, plugins: [owner, plugin] });
17
17
  }
18
18
  owners.set(name, plugin);
19
19
  registry.set(name, scheme);
package/dist/semver.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import lib from "semver";
2
- import { TallyError } from "./errors.js";
2
+ import { coreCodes } from "./codes.js";
3
3
  const format = (v) => {
4
4
  const pre = v.prerelease.length > 0 ? `-${v.prerelease.join(".")}` : "";
5
5
  const build = v.build.length > 0 ? `+${v.build.join(".")}` : "";
@@ -23,7 +23,7 @@ const parse = (raw) => {
23
23
  const mustParse = (raw) => {
24
24
  const v = parse(raw);
25
25
  if (!v) {
26
- throw new TallyError("E_VERSION_INVALID", `invalid version "${raw}"`, {
26
+ throw coreCodes.error("E_VERSION_INVALID", `invalid version "${raw}"`, {
27
27
  version: raw,
28
28
  });
29
29
  }
@@ -46,7 +46,7 @@ export const semver = {
46
46
  const raw = `${[major, minor, patch].join(".")}-${ctx.channel}.${n}`;
47
47
  const v = parse(raw);
48
48
  if (!v) {
49
- throw new TallyError("E_CHANNEL_INVALID", `channel "${ctx.channel}" does not form a valid prerelease`, { channel: ctx.channel });
49
+ throw coreCodes.error("E_CHANNEL_INVALID", `channel "${ctx.channel}" does not form a valid prerelease`, { channel: ctx.channel });
50
50
  }
51
51
  return v;
52
52
  },
package/dist/tags.js CHANGED
@@ -1,12 +1,12 @@
1
- import { TallyError } from "./errors.js";
1
+ import { coreCodes } from "./codes.js";
2
2
  import { compare } from "./util.js";
3
3
  const SPECIAL = /[\\^$.*+?()[\]{}|/]/gu;
4
- const overlap = (units, detail) => new TallyError("E_TAG_FORMAT_OVERLAP", `released units "${units.join('", "')}" share tags (${Object.values(detail).join(", ")})`, { units, ...detail });
4
+ const overlap = (units, detail) => coreCodes.error("E_TAG_FORMAT_OVERLAP", `released units "${units.join('", "')}" share tags (${Object.values(detail).join(", ")})`, { units, ...detail });
5
5
  /** Compiles `format`; literal parts and `{id}` are escaped, `{version}` must occur once. */
6
6
  export const compileTagFormat = (format, unitId) => {
7
7
  const parts = format.split("{version}");
8
8
  if (parts.length !== 2) {
9
- throw new TallyError("E_TAG_FORMAT", `tag format "${format}" of unit "${unitId}" must contain {version} exactly once`, { unit: unitId, format });
9
+ throw coreCodes.error("E_TAG_FORMAT", `tag format "${format}" of unit "${unitId}" must contain {version} exactly once`, { unit: unitId, format });
10
10
  }
11
11
  const literal = (part) => part.replaceAll("{id}", unitId).replace(SPECIAL, "\\$&");
12
12
  const re = new RegExp(`^${literal(parts[0] ?? "")}(.+)${literal(parts[1] ?? "")}$`, "u");
@@ -30,7 +30,7 @@ export const matchUnitTags = (units, tags, schemes) => {
30
30
  .map((unit) => {
31
31
  const scheme = schemes.get(unit.scheme);
32
32
  if (!scheme) {
33
- throw new TallyError("E_SCHEME_UNKNOWN", `unit "${unit.id}" uses unknown scheme "${unit.scheme}"`, { unit: unit.id, scheme: unit.scheme });
33
+ throw coreCodes.error("E_SCHEME_UNKNOWN", `unit "${unit.id}" uses unknown scheme "${unit.scheme}"`, { unit: unit.id, scheme: unit.scheme });
34
34
  }
35
35
  return {
36
36
  unit,
@@ -86,11 +86,7 @@ export const noMatchingTag = (units, matched, tagNames) => {
86
86
  return [];
87
87
  }
88
88
  return [
89
- {
90
- code: "W_NO_MATCHING_TAG",
91
- message: `released unit "${m.id}" has no tag matching "${unit.tagFormat.replaceAll("{id}", m.id)}", but "${similar}" contains its id`,
92
- unit: m.id,
93
- },
89
+ coreCodes.warning("W_NO_MATCHING_TAG", `released unit "${m.id}" has no tag matching "${unit.tagFormat.replaceAll("{id}", m.id)}", but "${similar}" contains its id`, { unit: m.id }),
94
90
  ];
95
91
  });
96
92
  };
package/dist/validate.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { validateBranches } from "./branches.js";
2
- import { TallyError } from "./errors.js";
2
+ import { coreCodes } from "./codes.js";
3
3
  import { validateFloating } from "./floating.js";
4
4
  import { validateReleaseItems } from "./release-items.js";
5
5
  import { schemeRegistry } from "./schemes.js";
@@ -20,23 +20,23 @@ export const validateRelease = (workspace) => {
20
20
  }
21
21
  const scheme = schemes.get(unit.scheme);
22
22
  if (!scheme) {
23
- throw new TallyError("E_SCHEME_UNKNOWN", `unit "${unit.id}" uses unknown scheme "${unit.scheme}"`, { unit: unit.id, scheme: unit.scheme });
23
+ throw coreCodes.error("E_SCHEME_UNKNOWN", `unit "${unit.id}" uses unknown scheme "${unit.scheme}"`, { unit: unit.id, scheme: unit.scheme });
24
24
  }
25
25
  compileTagFormat(unit.tagFormat, unit.id);
26
26
  if (unit.initialVersion !== null &&
27
27
  scheme.parse(unit.initialVersion) === undefined) {
28
- 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 });
28
+ throw coreCodes.error("E_INITIAL_VERSION", `initial version "${unit.initialVersion}" of unit "${unit.id}" is not a ${unit.scheme} version`, { unit: unit.id, version: unit.initialVersion });
29
29
  }
30
30
  }
31
31
  const units = new Map(workspace.units.map((u) => [u.id, u]));
32
32
  for (const { name, members } of workspace.groups) {
33
33
  const unreleased = members.find((id) => units.get(id)?.version === "none");
34
34
  if (unreleased !== undefined) {
35
- throw new TallyError("E_GROUP_NOT_RELEASED", `group "${name}" names unit "${unreleased}", which is not released`, { group: name, unit: unreleased });
35
+ throw coreCodes.error("E_GROUP_NOT_RELEASED", `group "${name}" names unit "${unreleased}", which is not released`, { group: name, unit: unreleased });
36
36
  }
37
37
  const schemeNames = uniqueSorted(members.map((id) => units.get(id)?.scheme ?? ""));
38
38
  if (schemeNames.length > 1) {
39
- throw new TallyError("E_GROUP_SCHEME", `members of group "${name}" use different schemes: ${schemeNames.join(", ")}`, { group: name, schemes: schemeNames });
39
+ throw coreCodes.error("E_GROUP_SCHEME", `members of group "${name}" use different schemes: ${schemeNames.join(", ")}`, { group: name, schemes: schemeNames });
40
40
  }
41
41
  }
42
42
  validateBranches(workspace.release.branches);
@@ -53,7 +53,7 @@ export const validateRelease = (workspace) => {
53
53
  ? `names unknown group "${group}"`
54
54
  : undefined;
55
55
  if (problem !== undefined) {
56
- throw new TallyError("E_BRANCH_RULE", `branch rule "${rule.name}" ${problem}`, {
56
+ throw coreCodes.error("E_BRANCH_RULE", `branch rule "${rule.name}" ${problem}`, {
57
57
  rule: rule.name,
58
58
  ...(unit === null ? { group } : { unit }),
59
59
  });
@@ -1,6 +1,6 @@
1
1
  import { changelogPath, changelogSection, updateChangelog, } from "./changelog.js";
2
+ import { coreCodes } from "./codes.js";
2
3
  import { normalizeRoot } from "./discover.js";
3
- import { TallyError } from "./errors.js";
4
4
  import { runHook } from "./plugins.js";
5
5
  import { schemeRegistry } from "./schemes.js";
6
6
  const TOKEN = /\{([^{}]*)\}/gu;
@@ -9,7 +9,7 @@ const CORE_TOKENS = ["major", "minor", "patch"];
9
9
  const VERSION = "\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?";
10
10
  const ALNUM = /[0-9A-Za-z]/u;
11
11
  const escape = (text) => text.replace(SPECIAL, "\\$&");
12
- const configError = (pattern, unit, problem) => new TallyError("E_CONFIG", `version file pattern "${pattern}" of unit "${unit}" ${problem}`, { unit, pattern });
12
+ const configError = (pattern, unit, problem) => coreCodes.error("E_CONFIG", `version file pattern "${pattern}" of unit "${unit}" ${problem}`, { unit, pattern });
13
13
  /**
14
14
  * Compiles a template pattern: literal parts escaped, `{id}` literal, version
15
15
  * tokens captured. An edge that is a token or an alphanumeric literal must not
@@ -110,7 +110,7 @@ const applyTemplate = (content, { pattern, unit, from, next, scheme, }) => {
110
110
  };
111
111
  /** Repo-relative template file; `E_CONFIG` for `.`, absolute or `..` paths. */
112
112
  const templateFile = (file, unit) => {
113
- const invalid = () => new TallyError("E_CONFIG", `version file "${file}" of unit "${unit}" must be a repo-relative file without ".." segments`, { unit, file });
113
+ const invalid = () => coreCodes.error("E_CONFIG", `version file "${file}" of unit "${unit}" must be a repo-relative file without ".." segments`, { unit, file });
114
114
  let path;
115
115
  try {
116
116
  path = normalizeRoot(file);
@@ -144,7 +144,7 @@ export const versionFileClaims = async (workspace) => {
144
144
  for (const { path } of list) {
145
145
  const prior = claims.get(path);
146
146
  if (prior !== undefined) {
147
- throw new TallyError("E_VERSION_FILE_CLAIM", `version file "${path}" is claimed by units "${prior}" and "${id}"`, { path, units: [prior, id] });
147
+ throw coreCodes.error("E_VERSION_FILE_CLAIM", `version file "${path}" is claimed by units "${prior}" and "${id}"`, { path, units: [prior, id] });
148
148
  }
149
149
  claims.set(path, id);
150
150
  }
@@ -154,7 +154,7 @@ export const versionFileClaims = async (workspace) => {
154
154
  const path = templateFile(file, id);
155
155
  const owner = claims.get(path);
156
156
  if (owner !== undefined) {
157
- throw new TallyError("E_VERSION_FILE_CLAIM", `version file template of unit "${id}" targets "${path}", a version file of unit "${owner}"`, { path, units: [owner, id] });
157
+ throw coreCodes.error("E_VERSION_FILE_CLAIM", `version file template of unit "${id}" targets "${path}", a version file of unit "${owner}"`, { path, units: [owner, id] });
158
158
  }
159
159
  }
160
160
  }
@@ -183,7 +183,7 @@ export const releaseFiles = async (workspace, plan, opts) => {
183
183
  const required = (path, unit) => {
184
184
  const content = read(path);
185
185
  if (content === undefined) {
186
- throw new TallyError("E_VERSION_FILE_MISSING", `version file "${path}" of unit "${unit}" does not exist`, { path, unit });
186
+ throw coreCodes.error("E_VERSION_FILE_MISSING", `version file "${path}" of unit "${unit}" does not exist`, { path, unit });
187
187
  }
188
188
  return content;
189
189
  };
@@ -219,11 +219,7 @@ export const releaseFiles = async (workspace, plan, opts) => {
219
219
  });
220
220
  files.set(file, result.content);
221
221
  if (!result.hit) {
222
- warnings.push({
223
- code: "W_VERSION_FILE_NO_MATCH",
224
- message: `version file pattern "${pattern}" of unit "${id}" matches nothing in "${file}"`,
225
- unit: id,
226
- });
222
+ warnings.push(coreCodes.warning("W_VERSION_FILE_NO_MATCH", `version file pattern "${pattern}" of unit "${id}" matches nothing in "${file}"`, { unit: id }));
227
223
  }
228
224
  }
229
225
  const section = changelogSection(workspace, plan, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tallyrelease/core",
3
- "version": "0.4.2-beta.6",
3
+ "version": "0.5.0-beta.14",
4
4
  "description": "Pure planning core of tallyrelease",
5
5
  "repository": {
6
6
  "type": "git",