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
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ Config plugin to auto-configure [`react-native-code-push`][lib] when the native

### Add the package to your npm dependencies

> Prebuild is tested against Expo SDK 50 and SDK 57. Android is covered on both;
> on iOS the plugin writes `Info.plist` but does not yet touch the Swift
> `AppDelegate` that SDK 52 and up generate.
> Prebuild is tested against Expo SDK 50 and SDK 57. Android and iOS are both
> covered on both: on iOS the plugin writes `Info.plist` and, on SDK 53's
> Swift `AppDelegate`, redirects `ReactNativeDelegate.bundleURL()` to
> `CodePush.bundleURL()` (SDK 50-52's Objective-C `AppDelegate.mm` gets the
> same redirect).

```
yarn add react-native-code-push react-native-code-push-plugin
Expand Down
45 changes: 34 additions & 11 deletions tools/changelog-check.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
// including the `ci!:` case — `ci` is hidden and its breaking note still
// has to surface
// 3. the recommended bump respects `effect`, so a release of nothing but
// `build:` and `chore:` commits cannot come out a minor
// `build:` and `chore:` commits cannot come out a minor, and
// `build(deps):` specifically comes out a patch (the scope carve-out
// that lets dependency and security patches actually ship)
//
// Then it renders the same history through a sabotaged type list and fails if
// *that* passes. An assertion that cannot fail is worth nothing.
Expand All @@ -46,6 +48,10 @@ const cliPath = join(dirname(cliEntry), "cli", "index.js");
* What each type is for. `bump` can raise the version, `changelog` renders
* without raising it, `hidden` does neither.
*
* A key is `type` on its own, or `type:scope` for a scope-qualified entry
* (only `build:deps` needs one today: security and dependency patches land as
* `build(deps):`, and that's the one `build` subtype allowed to bump).
*
* @type {Record<string, "bump" | "changelog" | "hidden">}
*/
const POLICY = {
Expand All @@ -54,6 +60,7 @@ const POLICY = {
fix: "bump",
perf: "bump",
revert: "bump",
"build:deps": "bump",
build: "changelog",
refactor: "changelog",
chore: "changelog",
Expand All @@ -69,6 +76,7 @@ const COMMITS = [
"feat: add a thing",
"fix: correct a thing",
"build: shrink the tarball",
"build(deps): patch a vulnerable package",
"refactor: rewrite the internals",
"chore(deps): bump something",
"docs: update the readme",
Expand All @@ -80,13 +88,14 @@ const COMMITS = [
"ci!: require node 24\n\nBREAKING CHANGE: node 22 is no longer supported",
];

// Subject fragment -> the type it was committed under. Matching on subjects
// rather than section headings keeps the check working in a repo that renames
// its sections.
// Subject fragment -> the type (or `type:scope`) it was committed under.
// Matching on subjects rather than section headings keeps the check working
// in a repo that renames its sections.
const SUBJECTS = {
feat: "add a thing",
fix: "correct a thing",
build: "shrink the tarball",
"build:deps": "patch a vulnerable package",
refactor: "rewrite the internals",
chore: "bump something",
docs: "update the readme",
Expand Down Expand Up @@ -204,14 +213,23 @@ const expect = (label, condition) => {
if (!condition) failures.push(label);
};

// 1. The type list says what the policy says.
for (const [type, effect] of Object.entries(POLICY)) {
const entry = TYPES.find((candidate) => candidate.type === type);
expect(`${type} is in the type list`, entry !== undefined);
expect(`${type} is "${effect}"`, entry?.effect === effect);
// 1. The type list says what the policy says. A POLICY key is `type` or
// `type:scope`; TYPES entries are matched the same way conventional-changelog
// itself resolves them (findTypeEntry), so a scope-qualified entry has to
// come before its scope-less fallback or this can't tell them apart.
const keyFor = (entry) =>
entry.scope ? `${entry.type}:${entry.scope}` : entry.type;

for (const [key, effect] of Object.entries(POLICY)) {
const [type, scope] = key.split(":");
const entry = TYPES.find(
(candidate) => candidate.type === type && candidate.scope === scope,
);
expect(`${key} is in the type list`, entry !== undefined);
expect(`${key} is "${effect}"`, entry?.effect === effect);
}
for (const entry of TYPES) {
expect(`${entry.type} is covered by the policy`, entry.type in POLICY);
expect(`${keyFor(entry)} is covered by the policy`, keyFor(entry) in POLICY);
}

/**
Expand Down Expand Up @@ -258,6 +276,7 @@ const bumps = {
breaking: await bumpFor(["feat!: drop the old API"]),
feature: await bumpFor(["feat: add a thing"]),
fix: await bumpFor(["fix: correct a thing"]),
buildDeps: await bumpFor(["build(deps): patch a vulnerable package"]),
changelogOnly: await bumpFor([
"build: shrink the tarball",
"refactor: rewrite the internals",
Expand All @@ -270,6 +289,10 @@ const bumps = {
expect("a breaking feat is a major", bumps.breaking === "major");
expect("a feat is a minor", bumps.feature === "minor");
expect("a fix is a patch", bumps.fix === "patch");
expect(
`a build(deps) is a patch (got ${bumps.buildDeps})`,
bumps.buildDeps === "patch",
);
expect(
`build/refactor/chore/docs alone do not bump (got ${bumps.changelogOnly})`,
bumps.changelogOnly === null,
Expand Down Expand Up @@ -318,7 +341,7 @@ if (sabotagedFailures.length === 0) {
}

console.log(
`changelog preset check passed (${COMMITS.length} synthetic commits, 3 breaking; bumps: feat!=${bumps.breaking}, feat=${bumps.feature}, fix=${bumps.fix}, changelog-only=${bumps.changelogOnly}, hidden-only=${bumps.hiddenOnly})`,
`changelog preset check passed (${COMMITS.length} synthetic commits, 3 breaking; bumps: feat!=${bumps.breaking}, feat=${bumps.feature}, fix=${bumps.fix}, build(deps)=${bumps.buildDeps}, changelog-only=${bumps.changelogOnly}, hidden-only=${bumps.hiddenOnly})`,
);
console.log(
`negative control passed (hiding the visible types breaks ${sabotagedFailures.length} assertions: ${sabotagedFailures.join(", ")})`,
Expand Down
14 changes: 14 additions & 0 deletions tools/changelog-preset.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,19 @@
// touching nothing, and the release workflow's `increment` input is there to
// force a version out anyway when one is wanted.
//
// `build(deps)` is the one exception, and it's a deliberate carve-out, not a
// loosening of the rule above: three security patches (#37, #38, #43, #44,
// #45) landed as `build(deps):` and every one of them got the generic `build`
// treatment, so release-it correctly found nothing to release and none of
// them ever reached npm. `findTypeEntry` (in
// conventional-changelog-conventionalcommits) matches on type *and* scope
// before falling back to the scope-less entry, so a `build(deps)` commit
// bumps a patch while a scope-less `build:` (tooling, tsconfig, oxfmt) still
// doesn't. The entry has to sit before the generic `build` entry below,
// `.find()` takes the first match. `chore(deps)` stays changelog-only:
// history only ever uses it for tooling policy (release-age windows, allowing
// magic-* through), never for a version bump.
//
// Breaking changes are not configurable here and don't need to be. The
// preset's writer sets `discard = false` the moment a commit carries a note,
// so a `BREAKING CHANGE:` footer or a `!` renders its own section whatever
Expand All @@ -46,6 +59,7 @@ export const TYPES = [
{ type: "fix", section: "Bug Fixes", effect: "bump" },
{ type: "perf", section: "Performance", effect: "bump" },
{ type: "revert", section: "Reverts", effect: "bump" },
{ type: "build", scope: "deps", section: "Build System", effect: "bump" },
{ type: "build", section: "Build System", effect: "changelog" },
{ type: "refactor", section: "Code Refactoring", effect: "changelog" },
{ type: "chore", section: "Chores", effect: "changelog" },
Expand Down
Loading