@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 +21 -21
- package/dist/branches.js +7 -7
- package/dist/codes.d.ts +26 -0
- package/dist/codes.js +224 -0
- package/dist/discover.js +10 -13
- package/dist/explain.js +2 -2
- package/dist/facts.js +3 -7
- package/dist/floating.js +2 -2
- package/dist/graph.js +4 -7
- package/dist/groups.js +4 -7
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/plan.js +3 -8
- package/dist/plugin.js +3 -3
- package/dist/plugins.js +6 -6
- package/dist/release-items.js +4 -4
- package/dist/release.js +5 -20
- package/dist/released.js +3 -9
- package/dist/schemes.js +2 -2
- package/dist/semver.js +3 -3
- package/dist/tags.js +5 -9
- package/dist/validate.js +6 -6
- package/dist/version-files.js +7 -11
- package/package.json +1 -1
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/
|
|
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/
|
|
42
|
-
* **cli:** add tallyrelease run and fill released in plans ([#36](https://github.com/abinnovision/
|
|
43
|
-
* **core:** add changelog writer, release items and PR marker ([#30](https://github.com/abinnovision/
|
|
44
|
-
* tag merged release PRs with tallyrelease release ([#35](https://github.com/abinnovision/
|
|
45
|
-
* write version files and changelogs with tallyrelease version ([#33](https://github.com/abinnovision/
|
|
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/
|
|
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/
|
|
53
|
-
* **cli:** add plan and explain commands ([#20](https://github.com/abinnovision/
|
|
54
|
-
* **core:** absorb internal changes as patch by default ([#22](https://github.com/abinnovision/
|
|
55
|
-
* **core:** absorb unreleased dependencies and propagate releases ([#19](https://github.com/abinnovision/
|
|
56
|
-
* **core:** add semver scheme and conventional commit parser ([#16](https://github.com/abinnovision/
|
|
57
|
-
* **core:** compute release versions with computePlan ([#18](https://github.com/abinnovision/
|
|
58
|
-
* **core:** match unit tags and collect plan facts ([#17](https://github.com/abinnovision/
|
|
59
|
-
* enforce maintenance branch limits ([#24](https://github.com/abinnovision/
|
|
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/
|
|
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/
|
|
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/
|
|
74
|
-
* **cli:** align human-readable output and add a local build base ([#8](https://github.com/abinnovision/
|
|
75
|
-
* make units build-only unless opted into release ([#6](https://github.com/abinnovision/
|
|
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/
|
|
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 {
|
|
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) =>
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|
package/dist/codes.d.ts
ADDED
|
@@ -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 {
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
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
|
|
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 {
|
|
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
|
|
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 {
|
|
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
|
|
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 {
|
|
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
|
|
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
|
|
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 {
|
|
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
|
|
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
|
|
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 {
|
|
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
|
|
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
|
|
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 {
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
52
|
+
throw coreCodes.error("E_PLUGIN_HOOK", `plugin "${plugin}" failed in ${hook}: ${reason}`, { plugin, hook, error });
|
|
53
53
|
}
|
|
54
54
|
};
|
package/dist/release-items.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
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
|
|
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 {
|
|
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
|
|
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
|
|
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 {
|
|
1
|
+
import { coreCodes } from "./codes.js";
|
|
2
2
|
import { compare } from "./util.js";
|
|
3
3
|
const SPECIAL = /[\\^$.*+?()[\]{}|/]/gu;
|
|
4
|
-
const overlap = (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
|
|
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
|
|
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 {
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
});
|
package/dist/version-files.js
CHANGED
|
@@ -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) =>
|
|
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 = () =>
|
|
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
|
|
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
|
|
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
|
|
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, {
|