Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions actions/release-please/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,17 +99,25 @@ resolve to `false`:
- `draft-pull-request`
- `prerelease`

`include-v-in-tag` must resolve to `true` for every package.
`bump-minor-pre-major` and `include-v-in-tag` must resolve to `true` for every
package.

Together these settings give every repository the same versioning while it is
below 1.0.0: a fix bumps the patch version, and a feature or a breaking change
bumps the minor version. Moving to 1.0.0 is a deliberate `Release-As` decision,
never a side effect of a breaking change.

Package overrides take precedence over top-level settings. Validation checks
each package's effective value, so an override can satisfy the policy even
when the top-level value differs. Omitted settings use release-please's native
defaults: `false` for the settings above and `true` for `include-v-in-tag`.
defaults: `false` for every setting above except `include-v-in-tag`, which
defaults to `true`. Because `bump-minor-pre-major` defaults to `false`, it must
be set explicitly.
Explicit values must be booleans; `null` and strings are rejected. A top-level,
nonempty `signoff` is required.

Other boolean settings are checked for their types, without forcing a shared
value. Settings such as `bump-minor-pre-major`, tag formatting, `release-type`
value. Settings such as tag formatting, `release-type`
and plugins remain repository-specific. `versioning` must be a string when
provided, and its effective value must not be `always-bump-patch`, which would
override the required feature-bump behavior.
Expand Down
2 changes: 1 addition & 1 deletion actions/release-please/lib/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ export const CONFIG_FILE = ".github/release-please-config.json";

// Policy changes must not alter upstream's behavior for omitted settings.
const releaseRules = Object.freeze({
"bump-minor-pre-major": { required: true, fallback: false },
"bump-patch-for-minor-pre-major": { required: false, fallback: false },
draft: { required: false, fallback: false },
"draft-pull-request": { required: false, fallback: false },
prerelease: { required: false, fallback: false },
"include-v-in-tag": { required: true, fallback: true },
"bump-minor-pre-major": {},
"include-component-in-tag": {},
"separate-pull-requests": {},
});
Expand Down
9 changes: 6 additions & 3 deletions tests/node/release-please/cli.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ function config(overrides = {}) {
return {
signoff: "Release contributor",
"initial-version": "0.0.1",
"bump-minor-pre-major": true,
packages: { ".": {} },
...overrides,
};
Expand Down Expand Up @@ -49,8 +50,10 @@ test("CLI rejects forbidden root and package policy flags", (t) => {
rejected(run(t, config({ [flag]: true })), new RegExp(flag));
rejected(run(t, config({ packages: { ".": { [flag]: true } } })), new RegExp(flag));
}
rejected(run(t, config({ "include-v-in-tag": false })), /include-v-in-tag/);
rejected(run(t, config({ packages: { ".": { "include-v-in-tag": false } } })), /include-v-in-tag/);
for (const flag of ["bump-minor-pre-major", "include-v-in-tag"]) {
rejected(run(t, config({ [flag]: false })), new RegExp(flag));
rejected(run(t, config({ packages: { ".": { [flag]: false } } })), new RegExp(flag));
}
});

test("disabling initial-version validation preserves policy and signoff validation", (t) => {
Expand All @@ -73,7 +76,7 @@ test("CLI accepts native defaults and package release-type variation", (t) => {
});

test("initial-version opt-out accepts missing initial versions when mandatory guards pass", (t) => {
const result = run(t, { signoff: "Release contributor", packages: { ".": {} } }, "false");
const result = run(t, { signoff: "Release contributor", "bump-minor-pre-major": true, packages: { ".": {} } }, "false");
assert.equal(result.status, 0, result.stdout + result.stderr);
assert.match(result.stdout, /not checked/);
});
Expand Down
32 changes: 22 additions & 10 deletions tests/node/release-please/index.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ test("the repository's own configuration passes", () => {
});

const defaults = {
"bump-minor-pre-major": true,
"bump-patch-for-minor-pre-major": false,
draft: false,
"draft-pull-request": false,
Expand All @@ -143,17 +144,24 @@ const defaults = {
};

test("omitted policy settings use compatible native defaults", () => {
validateReleaseDefaults({ packages: { ".": {} } });
validateReleaseDefaults({ "bump-minor-pre-major": true, packages: { ".": {} } });
});

test("omitted bump-minor-pre-major is rejected because the native default releases 1.0.0 on a breaking change", () => {
const error = failure(() => validateReleaseDefaults({ packages: { ".": {} } }));
assert.ok(error.message.includes("bump-minor-pre-major: true"));
});

test("effective policy settings inherit root or native defaults", () => {
validateReleaseDefaults({ ...defaults, packages: { ".": {}, other: { ...defaults } } });
for (const [key, expected] of Object.entries(defaults)) {
validateReleaseDefaults({
...defaults,
[key]: !expected,
packages: { ".": { [key]: expected }, other: { [key]: expected } },
});
const error = failure(() => validateReleaseDefaults({
...defaults,
[key]: !expected,
packages: { compliant: { [key]: expected }, inherited: {} },
}));
Expand All @@ -179,30 +187,32 @@ test("explicit package null and strings are not rescued by root policy defaults"
for (const [key, expected] of Object.entries(defaults)) {
for (const value of [null, "false", 0, undefined, [], {}]) {
const error = failure(() => validateReleaseDefaults({
...defaults,
[key]: expected,
packages: { malformed: { [key]: value } },
}));
assert.match(error.message, /Package 'malformed'/);
assert.match(error.message, /to a boolean/);
}
validateReleaseDefaults({ [key]: expected, packages: { ".": { [key]: expected } } });
validateReleaseDefaults({ ...defaults, [key]: expected, packages: { ".": { [key]: expected } } });
}
});

test("strategy booleans may vary but must actually be booleans", () => {
for (const key of ["bump-minor-pre-major", "include-component-in-tag", "separate-pull-requests"]) {
for (const key of ["include-component-in-tag", "separate-pull-requests"]) {
for (const value of [true, false]) {
validateReleaseDefaults({ [key]: value, packages: { ".": {}, opposite: { [key]: !value } } });
validateReleaseDefaults({ ...defaults, [key]: value, packages: { ".": {}, opposite: { [key]: !value } } });
}
for (const value of [null, "true", 1]) {
failure(() => validateReleaseDefaults({ [key]: value, packages: { ".": {} } }));
failure(() => validateReleaseDefaults({ packages: { ".": { [key]: value } } }));
failure(() => validateReleaseDefaults({ ...defaults, [key]: value, packages: { ".": {} } }));
failure(() => validateReleaseDefaults({ ...defaults, packages: { ".": { [key]: value } } }));
}
}
});

test("release strategies and unknown extra settings remain caller-owned", () => {
validateReleaseDefaults({
"bump-minor-pre-major": true,
"release-type": "simple",
"tag-separator": "@",
plugins: [{ type: "cargo-workspace", "merge": false }],
Expand Down Expand Up @@ -244,23 +254,25 @@ test("the repository's own configuration satisfies mandatory release validation"
});

test("always-bump-patch versioning is rejected at its effective package value", () => {
failure(() => validateReleaseDefaults({ versioning: "always-bump-patch", packages: { ".": {} } }));
failure(() => validateReleaseDefaults({ packages: { ".": { versioning: "always-bump-patch" } } }));
failure(() => validateReleaseDefaults({ "bump-minor-pre-major": true, versioning: "always-bump-patch", packages: { ".": {} } }));
failure(() => validateReleaseDefaults({ "bump-minor-pre-major": true, packages: { ".": { versioning: "always-bump-patch" } } }));
const error = failure(() => validateReleaseDefaults({
"bump-minor-pre-major": true,
versioning: "always-bump-patch",
packages: { compliant: { versioning: "default" }, inherited: {} },
}));
assert.match(error.message, /Package 'inherited'.*versioning: always-bump-patch/);
validateReleaseDefaults({
"bump-minor-pre-major": true,
versioning: "always-bump-patch",
packages: { ".": { versioning: "default" } },
});
});

test("versioning preserves valid strings and own-property overrides", () => {
for (const versioning of ["default", "always-bump-minor", "always-bump-major", "service-pack", "prerelease", "custom-strategy", ""]) {
validateReleaseDefaults({ versioning, packages: { ".": {} } });
validateReleaseDefaults({ versioning: "always-bump-patch", packages: { ".": { versioning } } });
validateReleaseDefaults({ "bump-minor-pre-major": true, versioning, packages: { ".": {} } });
validateReleaseDefaults({ "bump-minor-pre-major": true, versioning: "always-bump-patch", packages: { ".": { versioning } } });
}
for (const versioning of [null, false, 1, undefined, [], {}]) {
failure(() => validateReleaseDefaults({ versioning, packages: { ".": { versioning: "default" } } }));
Expand Down
7 changes: 4 additions & 3 deletions tests/node/release-please/runtime-guard.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ test("upstream release execution loads the config response guard", () => {
const snapshot = JSON.stringify({
signoff: "Release contributor",
"initial-version": "0.0.1",
"bump-minor-pre-major": true,
packages: { ".": {} },
});
const jsonResponse = (value) => new Response(JSON.stringify(value), {
Expand Down Expand Up @@ -76,9 +77,9 @@ test("unrelated blobs, repositories, and API hosts pass through", async () => {
});

test("preload policy always checks signoff and supports initial-version opt-out", () => {
assert.throws(() => makeGuard({}, { snapshot: JSON.stringify({ signoff: "", packages: { ".": {} } }), requireInitialVersion: false }), /signoff/);
assert.throws(() => makeGuard({}, { snapshot: JSON.stringify({ signoff: "Release contributor", packages: { ".": {} } }) }), /initial-version/);
assert.doesNotThrow(() => makeGuard({}, { snapshot: JSON.stringify({ signoff: "Release contributor", packages: { ".": {} } }), requireInitialVersion: false }));
assert.throws(() => makeGuard({}, { snapshot: JSON.stringify({ signoff: "", "bump-minor-pre-major": true, packages: { ".": {} } }), requireInitialVersion: false }), /signoff/);
assert.throws(() => makeGuard({}, { snapshot: JSON.stringify({ signoff: "Release contributor", "bump-minor-pre-major": true, packages: { ".": {} } }) }), /initial-version/);
assert.doesNotThrow(() => makeGuard({}, { snapshot: JSON.stringify({ signoff: "Release contributor", "bump-minor-pre-major": true, packages: { ".": {} } }), requireInitialVersion: false }));
});

test("Node preload guards fetch before the action entry point executes", () => {
Expand Down
Loading