@daemux/store-automator 0.10.96 → 0.10.97

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.
@@ -5,14 +5,14 @@
5
5
  },
6
6
  "metadata": {
7
7
  "description": "App Store & Google Play automation for Flutter apps",
8
- "version": "0.10.96"
8
+ "version": "0.10.97"
9
9
  },
10
10
  "plugins": [
11
11
  {
12
12
  "name": "store-automator",
13
13
  "source": "./plugins/store-automator",
14
14
  "description": "3 agents for app store publishing: reviewer, meta-creator, media-designer",
15
- "version": "0.10.96",
15
+ "version": "0.10.97",
16
16
  "keywords": [
17
17
  "flutter",
18
18
  "app-store",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@daemux/store-automator",
3
- "version": "0.10.96",
3
+ "version": "0.10.97",
4
4
  "description": "Full App Store & Google Play automation for Flutter apps with Claude Code agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "store-automator",
3
- "version": "0.10.96",
3
+ "version": "0.10.97",
4
4
  "description": "App Store & Google Play automation agents for Flutter app publishing",
5
5
  "author": {
6
6
  "name": "Daemux"
@@ -43,7 +43,11 @@ Subsequent builds need neither — the ASC API key handles everything.
43
43
 
44
44
  ## Step 4 — MARKETING_VERSION
45
45
 
46
- Leave your Xcode project's `MARKETING_VERSION` at `1.0` initially. CI auto-rolls the marketing version forward (patch bump) whenever the prior version hits `READY_FOR_SALE` in App Store Connect. Until then, the pipeline keeps bumping the build number against the current marketing version.
46
+ Leave your Xcode project's `MARKETING_VERSION` at `1.0` initially.
47
+ CI auto-rolls the marketing version forward (rollover bump — patch
48
+ with carry at .9) whenever the prior version hits `READY_FOR_SALE` in
49
+ App Store Connect. Until then, the pipeline keeps bumping the build
50
+ number against the current marketing version.
47
51
 
48
52
  ### Auto-bumping MARKETING_VERSION
49
53
 
@@ -51,7 +55,33 @@ When the ASC combined floor (max of pending review, `preReleaseVersions`,
51
55
  or builds-via-`preReleaseVersion`) exceeds your project's
52
56
  `MARKETING_VERSION`, the action auto-bumps and commits the new value as
53
57
  part of the same bot commit that handles cert refresh / autoupdate.
54
- Default policy is `patch` — bumps the patch component.
58
+
59
+ Default policy is `rollover` — patch with carry: at `.9` it rolls into
60
+ the next minor (`1.0.9` → `1.1.0`), and at minor=9 it cascades into the
61
+ next major (`1.9.9` → `2.0.0`). Major has no upper limit (`9.9.9` →
62
+ `10.0.0` just keeps growing). This produces the more natural human
63
+ progression most projects want — patch numbers never silently grow past
64
+ 9.
65
+
66
+ **Four policies** are supported via the action input:
67
+
68
+ | Policy | Example bump | When to use |
69
+ |--------|--------------|-------------|
70
+ | `rollover` (default) | `1.0.5` → `1.0.6`; `1.0.9` → `1.1.0` | Natural progression, carry at .9. |
71
+ | `patch` | `1.0.5` → `1.0.6`; `1.0.9` → `1.0.10` | Legacy unbounded patch — pinned for backward compat. |
72
+ | `minor` | `1.0.5` → `1.1.0`; `1.0.9` → `1.1.0` | Projects that ship every release as a minor. |
73
+ | `none` | (fails the build) | Explicit semver control via human bump. |
74
+
75
+ Full rollover behaviour: `1.0.9` → `1.1.0` (patch overflow), `1.9.9`
76
+ → `2.0.0` (minor cascade), `9.9.9` → `10.0.0` (major has no upper
77
+ limit). The `patch` policy is preserved verbatim for consumers that
78
+ pinned it before `rollover` shipped.
79
+
80
+ **Backward compat:** existing consumers on `0.0.27` that explicitly pin
81
+ `marketing-version-auto-bump: 'patch'` keep their current unbounded
82
+ behavior — the `'patch'` policy is unchanged. The default change from
83
+ `'patch'` → `'rollover'` only affects new installs and consumers that
84
+ do not override the input.
55
85
 
56
86
  **Opt out** via the action input:
57
87
 
@@ -64,10 +94,6 @@ Default policy is `patch` — bumps the patch component.
64
94
  In `'none'` mode, the floor check fails the build and you must bump
65
95
  `MARKETING_VERSION` manually before retrying.
66
96
 
67
- **Policy `'minor'`** bumps the minor component (e.g. `1.0.5` → `1.1.0`)
68
- when floor exceeds project. Useful for projects where every release
69
- is a minor.
70
-
71
97
  **Side effect:** the bot commit subject reflects what was changed,
72
98
  e.g. `ci: refresh signing identity + bump MARKETING_VERSION [skip ci]`.
73
99
 
@@ -30,8 +30,11 @@ SEM_RE = re.compile(r"^(\d+)\.(\d+)(?:\.(\d+))?$")
30
30
 
31
31
  # DESIGN DECISION: auto-bump policy is read from the env var so the action
32
32
  # step can wire it from `inputs.marketing-version-auto-bump` without a
33
- # CLI-flag re-plumb of every helper. Default 'patch' matches the action
34
- # input default. ``'none'`` preserves the historical fail-the-build path.
33
+ # CLI-flag re-plumb of every helper. Default 'rollover' matches the action
34
+ # input default -- patch with carry at .9 (1.0.9 -> 1.1.0) is the more
35
+ # natural semver progression for unattended CI. ``'patch'`` is preserved
36
+ # unchanged for consumers pinning the historical (unbounded) behavior.
37
+ # ``'none'`` preserves the historical fail-the-build path.
35
38
  _AUTO_BUMP_ENV = "MARKETING_VERSION_AUTO_BUMP"
36
39
 
37
40
  # DESIGN DECISION: auto-bump must only fire when the resulting bumped
@@ -139,7 +142,7 @@ def _floor_error(target: str, floor: str, per_source: dict, *, strict: bool) ->
139
142
  raise SystemExit(2)
140
143
 
141
144
 
142
- def _persist_context_ok() -> bool:
145
+ def _persist_context_ok(policy: str) -> bool:
143
146
  """True when this run can persist a project-file bump back to the
144
147
  default branch via the action's commit-back step. The commit-back
145
148
  step is gated on ``event=push`` AND ``ref=refs/heads/<default>``;
@@ -149,7 +152,9 @@ def _persist_context_ok() -> bool:
149
152
 
150
153
  On refusal, emits the ``::warning::`` describing why this run
151
154
  cannot persist the bump (event/ref/default_branch values from the
152
- environment). Side-effecting the warning here -- rather than at
155
+ environment). The warning names the active policy (rollover /
156
+ patch / minor) so consumers reading CI logs can see which mode
157
+ was attempted. Side-effecting the warning here -- rather than at
153
158
  the caller -- keeps ``maybe_auto_bump`` under the per-function LOC
154
159
  cap without splitting the env-read + warning into two helpers
155
160
  (which would also push the file over the per-file functions cap)."""
@@ -164,7 +169,7 @@ def _persist_context_ok() -> bool:
164
169
  if ok:
165
170
  return True
166
171
  print(
167
- f"::warning::auto-bump=patch is enabled, but this run "
172
+ f"::warning::auto-bump={policy} is enabled, but this run "
168
173
  f"cannot persist the bump (event={event or '<unset>'}, "
169
174
  f"ref={ref or '<unset>'}, "
170
175
  f"default_branch={default_branch or '<unset>'}). The "
@@ -198,17 +203,17 @@ def maybe_auto_bump(target: str, floor: str) -> str | None:
198
203
  runs ``git add -f`` on the touched path so the auto-bump is staged
199
204
  in the index before any later step (e.g. prepare_signing) mutates
200
205
  the same file."""
201
- policy = (os.environ.get(_AUTO_BUMP_ENV) or "patch").strip().lower()
206
+ policy = (os.environ.get(_AUTO_BUMP_ENV) or "rollover").strip().lower()
202
207
  if policy == "none":
203
208
  return None
204
- if policy not in ("patch", "minor"):
209
+ if policy not in ("rollover", "patch", "minor"):
205
210
  print(
206
211
  f"::warning::Unknown {_AUTO_BUMP_ENV}={policy!r}; "
207
212
  f"falling back to fail-on-floor behavior",
208
213
  file=sys.stderr,
209
214
  )
210
215
  return None
211
- if not _persist_context_ok():
216
+ if not _persist_context_ok(policy):
212
217
  return None
213
218
  new_version = version_utils.compute_next_version(target, floor, policy)
214
219
  if not version_utils.write_marketing_version(new_version):
@@ -12,10 +12,14 @@ the project's 10-functions-per-file cap. Two concerns live here:
12
12
  result > floor (so ASC accepts the new row)
13
13
  result > current (so the bump is observable)
14
14
 
15
- Policy is ``'patch'`` (default) or ``'minor'``. Patch bumps the
16
- patch component of max(floor.major.minor, current.major.minor) and
17
- ensures it strictly exceeds both floor and current. Minor bumps the
18
- minor component and resets patch to 0.
15
+ Policy is ``'rollover'`` (default), ``'patch'``, or ``'minor'``.
16
+ Rollover bumps the patch component with carry: at .9 it rolls
17
+ into the next minor (1.0.9 -> 1.1.0), and at minor=9, patch=9 it
18
+ cascades into the next major (1.9.9 -> 2.0.0). Major has no upper
19
+ limit (9.9.9 -> 10.0.0). Patch (legacy) bumps the patch component
20
+ unbounded (1.0.9 -> 1.0.10) -- preserved for backward compat with
21
+ consumers pinning the historical default. Minor bumps the minor
22
+ component and resets patch to 0.
19
23
 
20
24
  2. ``write_marketing_version(new_version)`` -- I/O. Writes
21
25
  ``new_version`` into the project's source-of-truth. Resolution order:
@@ -100,32 +104,49 @@ def compute_next_version(current: str, floor: str, policy: str) -> str:
100
104
  """Compute the next MARKETING_VERSION that satisfies both:
101
105
  result > floor AND result > current.
102
106
 
103
- ``policy`` is ``'patch'`` or ``'minor'``. The ``'none'`` case is
104
- handled by the caller (it preserves the existing fail-the-build
105
- semantics) and never reaches this function."""
106
- if policy not in ("patch", "minor"):
107
+ ``policy`` is ``'rollover'`` (default), ``'patch'``, or ``'minor'``.
108
+ The ``'none'`` case is handled by the caller (it preserves the
109
+ existing fail-the-build semantics) and never reaches this function.
110
+
111
+ Cross-train semantics (shared by 'patch' and 'rollover'): stay on
112
+ the higher major.minor train of (current, floor) so we never
113
+ silently advance an unrelated minor or major; when trains match,
114
+ base patch is max(current.patch, floor.patch). 'rollover' adds
115
+ carry: patch>9 rolls to patch=0, minor+=1; resulting minor>9
116
+ rolls to minor=0, major+=1; major has no upper limit (9.9.9 ->
117
+ 10.0.0 just keeps growing). The ``>9`` (not ``==10``) check
118
+ handles inputs already past the rollover boundary -- e.g. a
119
+ project previously on the 'patch' policy that reached 1.0.10
120
+ before switching to 'rollover' must still cascade
121
+ (1.0.10 -> 1.1.0) rather than emit a malformed 1.0.11. 'patch'
122
+ (legacy) is unbounded (1.0.9 -> 1.0.10), preserved verbatim for
123
+ backward compat with consumers that pinned the historical default.
124
+ """
125
+ if policy not in ("rollover", "patch", "minor"):
107
126
  raise ValueError(f"unknown policy: {policy!r}")
108
- cur = _parse(current)
109
- fl = _parse(floor)
127
+ cur, fl = _parse(current), _parse(floor)
110
128
  if policy == "minor":
111
129
  major = max(cur[0], fl[0])
112
- minor = max(cur[1], fl[1]) + 1
113
- return f"{major}.{minor}.0"
114
- # patch: stay on the higher major.minor train of (current, floor) so we
115
- # never silently advance an unrelated minor or major. The patch number
116
- # is +1 above whichever of (current, floor) shares that train; patches
117
- # from the lower train are namespace-irrelevant and ignored.
130
+ return f"{major}.{max(cur[1], fl[1]) + 1}.0"
131
+ # patch / rollover share train resolution.
118
132
  if cur[:2] >= fl[:2]:
119
133
  major, minor, patch_base = cur[0], cur[1], cur[2]
120
- # If current and floor share a major.minor, lift the patch above
121
- # whichever is higher (floor by definition, since result must be
122
- # > floor; current can equal floor only when cur[:2] == fl[:2]).
123
134
  if cur[:2] == fl[:2]:
124
135
  patch_base = max(cur[2], fl[2])
125
136
  else:
126
- # Floor is on a higher train; jump to it and bump from its patch.
127
137
  major, minor, patch_base = fl
128
- return f"{major}.{minor}.{patch_base + 1}"
138
+ if policy == "patch":
139
+ return f"{major}.{minor}.{patch_base + 1}"
140
+ # rollover: patch+1 with carry into minor, then into major.
141
+ # ``>9`` (not ``==10``) so inputs already over the boundary
142
+ # (e.g. a 1.0.10 left over from the legacy 'patch' policy)
143
+ # still cascade cleanly into the next minor / major train.
144
+ new_patch = patch_base + 1
145
+ if new_patch > 9:
146
+ new_patch, minor = 0, minor + 1
147
+ if minor > 9:
148
+ minor, major = 0, major + 1
149
+ return f"{major}.{minor}.{new_patch}"
129
150
 
130
151
 
131
152
  def _xcodebuild_args() -> tuple[list[str], Path]: