feat(build): per-run size-budget override (#260)#263
Conversation
Let a single build override the soft size budget without editing launch.config.ts. A new optional `BuildRunOptions.sizeBudgetMB` is resolved at the upload gate by `resolveSizeBudgetMB(options, profile)` — one shared helper for all three `confirmUpload` sites (local iOS, local Android, EAS handoff) with the precedence: override > profile.sizeBudgetMB > DEFAULT_SIZE_BUDGET_MB (200). - `launch build`: add `--size-budget <MB>` (alias `--budget`), parsed and validated > 0 at the CLI boundary (rejects ≤ 0 / non-numeric). - wizard: after picking a profile in both the iOS and Android journeys, offer "Use profile budget (<n> MB)" (default) or "Enter a custom budget…" (numeric, validated > 0), threaded into the same gate. - Remembered-flow replay is unaffected: a one-off custom budget is per-run only and is not carried into `rememberLastFlow`. Tests cover the resolve order, CLI/wizard validation, and the profile-default fallback. Docs regenerated for the new flags. Closes #260 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
CodeAnt AI is reviewing your PR. |
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
Thanks for using CodeAnt! 🎉We're free for open-source projects. if you're enjoying it, help us grow by sharing. Share on X · |
|
📝 WalkthroughWalkthroughAdds a per-run size budget override: ChangesPer-run size budget override
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| /** | ||
| * Per-run soft size-budget override in MB (`--size-budget`, or the wizard's custom-budget prompt). When | ||
| * set it wins over the profile's `sizeBudgetMB` for this build only — `launch.config.ts` is untouched. | ||
| * See {@link resolveSizeBudgetMB}. | ||
| */ | ||
| sizeBudgetMB?: number; |
There was a problem hiding this comment.
Suggestion: Move this newly added run-option shape change into the appropriate src/core/types/*.ts module and consume it from pipeline.ts via import instead of extending the interface inline in the feature file. [custom_rule]
Severity Level: Minor
Why it matters? 🤔
The PR adds a new run-option field directly inside src/core/pipeline.ts, but the rule requires shape/interface changes to live in the matching src/core/types/*.ts module instead of a feature file. This is a real violation because BuildRunOptions is being extended inline here.
(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** src/core/pipeline.ts
**Line:** 102:107
**Comment:**
*Custom Rule: Move this newly added run-option shape change into the appropriate `src/core/types/*.ts` module and consume it from `pipeline.ts` via import instead of extending the interface inline in the feature file.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix|
CodeAnt AI finished reviewing your PR. |
…override # Conflicts: # docs/commands.md
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/cli/commands/build.ts`:
- Around line 104-117: The parseSizeBudget boundary in build.ts is too
permissive because Number.parseFloat accepts partial strings like “250mb” and
non-finite inputs like “Infinity”. Update parseSizeBudget to validate the full
input string as a numeric, finite MB value greater than 0, and keep the existing
error path for invalid values so the value passed into runBuild remains strictly
numeric.
In `@src/cli/commands/wizard.ts`:
- Around line 167-209: The wizard budget path is accepting non-strict numeric
input because validateCustomBudget and selectSizeBudget rely on
parseFloat/Number parsing, which lets values like “250mb” and “Infinity”
through. Tighten the validation in validateCustomBudget so it only accepts a
plain positive finite MB number, then make selectSizeBudget return that
validated numeric value without re-parsing loosely; use the existing
validateCustomBudget, selectSizeBudget, and profileBudgetMB symbols to update
the prompt flow consistently.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 45e81ecc-cb97-4940-b214-38a8d576d7d6
📒 Files selected for processing (18)
README.de.mdREADME.es.mdREADME.fr.mdREADME.ja.mdREADME.ko.mdREADME.mdREADME.pt-BR.mdREADME.ru.mdREADME.zh-CN.mddocs/commands.mdllms.txtsrc/cli/commands/build.test.tssrc/cli/commands/build.tssrc/cli/commands/wizard.test.tssrc/cli/commands/wizard.tssrc/core/easPipeline.tssrc/core/pipeline.test.tssrc/core/pipeline.ts
| /** | ||
| * Parse `--size-budget`/`--budget` into a positive MB number, rejecting `≤ 0` and non-numeric input at the | ||
| * CLI boundary. Returns undefined when omitted, so the pipeline falls back to the profile's `sizeBudgetMB` | ||
| * then the default — see {@link resolveSizeBudgetMB}. Exported so the same rejection is unit-tested directly. | ||
| */ | ||
| export function parseSizeBudget(sizeBudget: string | undefined): number | undefined { | ||
| if (sizeBudget === undefined) return undefined; | ||
| const value = Number.parseFloat(sizeBudget); | ||
| if (Number.isNaN(value) || value <= 0) { | ||
| throw new Error(`Invalid --size-budget "${sizeBudget}". Pass a size in MB greater than 0.`); | ||
| } | ||
| return value; | ||
| } | ||
|
|
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Reject partial numeric strings and non-finite values.
Number.parseFloat accepts inputs like "250mb" as 250 and "Infinity" as a valid budget, so this CLI boundary currently lets malformed values through instead of rejecting them. That breaks the PR's “numeric and greater than zero” contract before Line 179 threads the value into runBuild.
Suggested fix
export function parseSizeBudget(sizeBudget: string | undefined): number | undefined {
if (sizeBudget === undefined) return undefined;
- const value = Number.parseFloat(sizeBudget);
- if (Number.isNaN(value) || value <= 0) {
+ const value = Number(sizeBudget.trim());
+ if (!Number.isFinite(value) || value <= 0) {
throw new Error(`Invalid --size-budget "${sizeBudget}". Pass a size in MB greater than 0.`);
}
return value;
}Also applies to: 179-179
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/cli/commands/build.ts` around lines 104 - 117, The parseSizeBudget
boundary in build.ts is too permissive because Number.parseFloat accepts partial
strings like “250mb” and non-finite inputs like “Infinity”. Update
parseSizeBudget to validate the full input string as a numeric, finite MB value
greater than 0, and keep the existing error path for invalid values so the value
passed into runBuild remains strictly numeric.
| /** | ||
| * Validate a typed custom-budget string for the wizard prompt, returning a Clack error message when it's | ||
| * rejected (empty, non-numeric, or `≤ 0`) or undefined when it's a positive MB number. Pure → the | ||
| * rejection wording is unit-testable without driving a prompt. | ||
| */ | ||
| export function validateCustomBudget(input: string): string | undefined { | ||
| const value = Number.parseFloat(input); | ||
| if (Number.isNaN(value)) return 'Enter a number of megabytes.'; | ||
| if (value <= 0) return 'Enter a budget greater than 0 MB.'; | ||
| return undefined; | ||
| } | ||
|
|
||
| /** | ||
| * Step (both, after the profile): let the user keep the profile's size budget (default — just press | ||
| * Enter) or type a per-run override that wins at the upload gate for this build only. Returns the custom | ||
| * MB number, or undefined to leave the profile's budget in force. Reuses the same Clack `select`/`text` | ||
| * primitives the rest of the wizard uses, so it degrades and cancels identically. | ||
| */ | ||
| async function selectSizeBudget( | ||
| config: LaunchConfig, | ||
| profileName: string, | ||
| ): Promise<number | undefined> { | ||
| const budget = profileBudgetMB(config, profileName); | ||
| const choice = resolvePrompt( | ||
| await select<'profile' | 'custom'>({ | ||
| message: 'Size budget for this build?', | ||
| options: [ | ||
| { value: 'profile', label: `Use profile budget (${budget} MB)`, hint: 'no change' }, | ||
| { value: 'custom', label: 'Enter a custom budget…', hint: 'this build only' }, | ||
| ], | ||
| initialValue: 'profile', | ||
| }), | ||
| ); | ||
| if (choice === 'profile') return undefined; | ||
| const entered = resolvePrompt( | ||
| await text({ | ||
| message: 'Custom size budget (MB)', | ||
| placeholder: String(budget), | ||
| validate: (value) => validateCustomBudget(value ?? ''), | ||
| }), | ||
| ); | ||
| return Number.parseFloat(entered); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Tighten the wizard's numeric validation.
This prompt path has the same permissive parsing bug as the CLI: "250mb" and "Infinity" both pass validation and then become real overrides at Line 208. The wizard is supposed to reject anything that is not a positive numeric MB value.
Suggested fix
export function validateCustomBudget(input: string): string | undefined {
- const value = Number.parseFloat(input);
- if (Number.isNaN(value)) return 'Enter a number of megabytes.';
+ const value = Number(input.trim());
+ if (!Number.isFinite(value)) return 'Enter a number of megabytes.';
if (value <= 0) return 'Enter a budget greater than 0 MB.';
return undefined;
}
@@
- return Number.parseFloat(entered);
+ return Number(entered.trim());
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| /** | |
| * Validate a typed custom-budget string for the wizard prompt, returning a Clack error message when it's | |
| * rejected (empty, non-numeric, or `≤ 0`) or undefined when it's a positive MB number. Pure → the | |
| * rejection wording is unit-testable without driving a prompt. | |
| */ | |
| export function validateCustomBudget(input: string): string | undefined { | |
| const value = Number.parseFloat(input); | |
| if (Number.isNaN(value)) return 'Enter a number of megabytes.'; | |
| if (value <= 0) return 'Enter a budget greater than 0 MB.'; | |
| return undefined; | |
| } | |
| /** | |
| * Step (both, after the profile): let the user keep the profile's size budget (default — just press | |
| * Enter) or type a per-run override that wins at the upload gate for this build only. Returns the custom | |
| * MB number, or undefined to leave the profile's budget in force. Reuses the same Clack `select`/`text` | |
| * primitives the rest of the wizard uses, so it degrades and cancels identically. | |
| */ | |
| async function selectSizeBudget( | |
| config: LaunchConfig, | |
| profileName: string, | |
| ): Promise<number | undefined> { | |
| const budget = profileBudgetMB(config, profileName); | |
| const choice = resolvePrompt( | |
| await select<'profile' | 'custom'>({ | |
| message: 'Size budget for this build?', | |
| options: [ | |
| { value: 'profile', label: `Use profile budget (${budget} MB)`, hint: 'no change' }, | |
| { value: 'custom', label: 'Enter a custom budget…', hint: 'this build only' }, | |
| ], | |
| initialValue: 'profile', | |
| }), | |
| ); | |
| if (choice === 'profile') return undefined; | |
| const entered = resolvePrompt( | |
| await text({ | |
| message: 'Custom size budget (MB)', | |
| placeholder: String(budget), | |
| validate: (value) => validateCustomBudget(value ?? ''), | |
| }), | |
| ); | |
| return Number.parseFloat(entered); | |
| } | |
| /** | |
| * Validate a typed custom-budget string for the wizard prompt, returning a Clack error message when it's | |
| * rejected (empty, non-numeric, or `≤ 0`) or undefined when it's a positive MB number. Pure → the | |
| * rejection wording is unit-testable without driving a prompt. | |
| */ | |
| export function validateCustomBudget(input: string): string | undefined { | |
| const value = Number(input.trim()); | |
| if (!Number.isFinite(value)) return 'Enter a number of megabytes.'; | |
| if (value <= 0) return 'Enter a budget greater than 0 MB.'; | |
| return undefined; | |
| } | |
| /** | |
| * Step (both, after the profile): let the user keep the profile's size budget (default — just press | |
| * Enter) or type a per-run override that wins at the upload gate for this build only. Returns the custom | |
| * MB number, or undefined to leave the profile's budget in force. Reuses the same Clack `select`/`text` | |
| * primitives the rest of the wizard uses, so it degrades and cancels identically. | |
| */ | |
| async function selectSizeBudget( | |
| config: LaunchConfig, | |
| profileName: string, | |
| ): Promise<number | undefined> { | |
| const budget = profileBudgetMB(config, profileName); | |
| const choice = resolvePrompt( | |
| await select<'profile' | 'custom'>({ | |
| message: 'Size budget for this build?', | |
| options: [ | |
| { value: 'profile', label: `Use profile budget (${budget} MB)`, hint: 'no change' }, | |
| { value: 'custom', label: 'Enter a custom budget…', hint: 'this build only' }, | |
| ], | |
| initialValue: 'profile', | |
| }), | |
| ); | |
| if (choice === 'profile') return undefined; | |
| const entered = resolvePrompt( | |
| await text({ | |
| message: 'Custom size budget (MB)', | |
| placeholder: String(budget), | |
| validate: (value) => validateCustomBudget(value ?? ''), | |
| }), | |
| ); | |
| return Number(entered.trim()); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/cli/commands/wizard.ts` around lines 167 - 209, The wizard budget path is
accepting non-strict numeric input because validateCustomBudget and
selectSizeBudget rely on parseFloat/Number parsing, which lets values like
“250mb” and “Infinity” through. Tighten the validation in validateCustomBudget
so it only accepts a plain positive finite MB number, then make selectSizeBudget
return that validated numeric value without re-parsing loosely; use the existing
validateCustomBudget, selectSizeBudget, and profileBudgetMB symbols to update
the prompt flow consistently.
…ing safe slice (#267) Rollup since v0.30.0: - feat(build): per-run `--size-budget`/`--budget` override + wizard step (#260, #263) - fix(apple): multi-target iOS signing safe slice — pbxproj target discovery, stale-profile capability-diff regenerate, `creds setup --app`, build preflight (#261 safe slice, #264; archive-vector flip tracked in #262) - chore: finish the eslint+prettier → biome migration; repair the red CI gate (#265) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
User description
Summary
Lets a single build override the soft size budget without editing
launch.config.ts. Previously the budget was locked to the chosen profile'ssizeBudgetMB(or the 200 MB fallback), so a one-off threshold meant editing config and re-running.What changed
src/core/pipeline.ts— added an optionalsizeBudgetMB?: numbertoBuildRunOptionsand a single shared resolverresolveSizeBudgetMB(options, profile)(+ aDEFAULT_SIZE_BUDGET_MBconstant). Precedence:options.sizeBudgetMB ?? profile.sizeBudgetMB ?? 200. Both enforcement sites (runIosBuild,runAndroidBuild) now call it instead of duplicating the?? 200chain.src/core/easPipeline.ts— the EAS handoff'sconfirmUploaduses the same resolver, so the override applies on every build path (local iOS, local Android, EAS), not just two of three.src/cli/commands/build.ts— added--size-budget <MB>(alias--budget), parsed and validated> 0at the CLI boundary viaparseSizeBudget(rejects≤ 0and non-numeric with a clear message), threaded intorunBuildassizeBudgetMB.src/cli/commands/wizard.ts— afterselectProfile()in both the iOS and Android journeys, a newselectSizeBudgetstep offers "Use profile budget (<n>MB)" (default) vs "Enter a custom budget…" (numeric, validated> 0), reusing the same Clackselect/textprimitives. The chosen value flows into the sameconfirmUploadbudgetMB.rememberLastFlow, so a "Repeat last build?" replay keeps the profile budget (as the issue allows).Acceptance criteria
launch.config.tsis not written.confirmUploadover-budget warning/gate.--size-budget <MB>(alias--budget) provides the same override non-interactively; in CI/--yesthe prompt is skipped.≤ 0, non-numeric) is rejected with a clear message.Tests
resolveSizeBudgetMBprecedence (override > profile > default) —src/core/pipeline.test.ts.parseSizeBudgetvalidation +--size-budget/--budgetflag registration —src/cli/commands/build.test.ts(new).validateCustomBudgetrejection wording andprofileBudgetMBdefault fallback —src/cli/commands/wizard.test.ts.Full suite: 1917 passing.
typecheck,lint(biome), andbuildare green.Closes #260
🤖 Generated with Claude Code
Summary by cubic
Adds a per-run soft size budget override for builds, so you can bypass the profile budget without editing
launch.config.ts. Works across local iOS, local Android, and EAS builds.New Features
--size-budget <MB>(alias--budget) to set a positive MB budget for this run only; validated at the flag boundary.sizeBudgetMB> default 200 MB.Refactors
DEFAULT_SIZE_BUDGET_MBand a sharedresolveSizeBudgetMB(options, profile)used by allconfirmUploadsites (iOS, Android, EAS).Written for commit b5966f8. Summary will update on new commits.
CodeAnt-AI Description
Let each build use a custom size budget without changing config
What Changed
Impact
✅ One-off build budget overrides✅ Fewer config edits before uploads✅ Clearer size-limit choices in the build flow💡 Usage Guide
Checking Your Pull Request
Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.
Talking to CodeAnt AI
Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:
This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.
Example
Preserve Org Learnings with CodeAnt
You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:
This helps CodeAnt AI learn and adapt to your team's coding style and standards.
Example
Retrigger review
Ask CodeAnt AI to review the PR again, by typing:
Check Your Repository Health
To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.
Summary by CodeRabbit
New Features
Bug Fixes
Documentation
Tests