Skip to content

fix(target): harden native release preflight - #119

Merged
imjlk merged 4 commits into
mainfrom
codex/fix-native-release-review
Jul 26, 2026
Merged

imjlk merged 4 commits into
mainfrom
codex/fix-native-release-review

Conversation

@imjlk

@imjlk imjlk commented Jul 26, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • enforce native Android and iOS marketing versions to match the game APP_VERSION
  • reject Android release identity suffixes that would make static source validation differ from the packaged variant
  • inspect only the Capacitor App target Release configuration in Xcode projects
  • correct the generic versioning guide and include the required CLI Sampo changeset

Root cause

The previous preflight validated broad source text and did not bind native marketing versions to the game release version. It also passed a non-global regular expression to String.prototype.matchAll.

Validation

  • pnpm exec ttsc --noEmit -p tsconfig.tools.json
  • pnpm smoke:native-release-identity
  • pnpm graph:preflight
  • SAMPO_RELEASE_BRANCH=main sampo release --dry-run
  • ocr review --audience agent --commit fb87690

Known baseline check

pnpm check currently fails on two unrelated starter/template parity checks in postOperationStore.ts and redditSandbox.ts; this PR does not change either file.

Summary by CodeRabbit

  • Bug Fixes
    • Improved native Android and iOS release validation by ensuring release identities align with the game version and the effective release configuration.
    • Added stricter checks that reject unsupported Android release suffix settings.
    • Enhanced iOS verification to validate the App target’s release build settings precisely.
  • Documentation
    • Clarified multi-target release versioning to treat package.json version as the source of truth and added pre-build validation guidance.
    • Updated Android production build instructions with the current target command and options.
  • Tests
    • Expanded native release identity test coverage for both passing and failing scenarios.

@coderabbitai

coderabbitai Bot commented Jul 26, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@imjlk, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 52 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6a5589cf-9255-401b-a960-0939325dc386

📥 Commits

Reviewing files that changed from the base of the PR and between bab9a48 and fe89507.

📒 Files selected for processing (2)
  • tools/target/native-release-identity.test.ts
  • tools/target/native-release-identity.ts
📝 Walkthrough

Walkthrough

Native release identity validation now checks Android and iOS versions against APP_VERSION, rejects unsupported Android release suffixes, scopes iOS checks to the App Release configuration, updates tests and fixtures, and documents the versioning and Android build workflow.

Changes

Native release identity

Layer / File(s) Summary
Native identity validation
tools/target/native-release-identity.ts
assertNativeReleaseIdentity compares native versions with APP_VERSION, rejects Android release suffixes, parses the iOS App Release block, and improves setting extraction.
Identity validation tests
tools/target/native-release-identity.test.ts
Tests add APP_VERSION, cover version mismatches and unsupported Android suffixes, and expand the iOS project fixture.
Release guidance and metadata
docs/MULTI_TARGET_RELEASE_VERSIONING.md, .sampo/changesets/fix-native-release-identity.md
Documentation defines the game version source and Android production command, while the changeset records the CLI release.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Poem

A rabbit checks the versions twice,
Android suffixes pay the price.
iOS blocks now line up bright,
Game and native match just right.
Builds hop onward, clean and light!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: hardening native release preflight.
Description check ✅ Passed The description covers Summary and Validation well, with an extra root cause section; only Notes is missing from the template.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/fix-native-release-review

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/MULTI_TARGET_RELEASE_VERSIONING.md (1)

47-59: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Example is not copy-pasteable and omits the now-required APP_VERSION.

Two problems in this block:

  • \\ at the end of lines 56-57 is an escaped backslash in sh, not a line continuation; use a single \.
  • assertNativeReleaseIdentity now hard-fails unless APP_VERSION matches versionName/MARKETING_VERSION, but the example never exports it (and build-target.ts defaults it to 0.0.0).
📝 Proposed doc fix
 ```sh
+export APP_VERSION=1.4.0
+
 # Android
 export MPGD_TARGET_VERSION_NAME=1.4.0
 export MPGD_TARGET_VERSION_CODE=42
 
 # iOS
 export MPGD_TARGET_MARKETING_VERSION=1.4.0
 export MPGD_TARGET_BUILD_NUMBER=42
 
-mpgd target build android production \\
-  --targets-file ./mpgd.targets.json \\
+mpgd target build android production \
+  --targets-file ./mpgd.targets.json \
   --kit-path ../mpgd-kit
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/MULTI_TARGET_RELEASE_VERSIONING.md around lines 47 - 59, Update the
shell example in MULTI_TARGET_RELEASE_VERSIONING to export APP_VERSION=1.4.0
alongside the platform version variables, and replace the doubled trailing
backslashes in the mpgd target build command with single shell line-continuation
backslashes so the example is copy-pasteable.


</details>

<!-- cr-comment:v1:e98e2395be2d625db16ab0a7 -->

</blockquote></details>

</blockquote></details>
🧹 Nitpick comments (5)
tools/target/native-release-identity.test.ts (2)

72-83: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Suffix fixture permanently clobbers the shared shell fixture.

writeAndroidReleaseSuffix overwrites android/app/build.gradle in the shared shellRoot and never restores it, so this assertion must stay last forever; any test added after it inherits the suffix gradle and fails for the wrong reason. Use a dedicated temp shell (or re-run writeShellFiles(shellRoot) afterwards).

♻️ Suggested isolation
   writeAndroidReleaseSuffix(shellRoot);
   assert.throws(() => assertNativeReleaseIdentity({
@@
   }), /does not support applicationIdSuffix or versionNameSuffix/u);
+  writeShellFiles(shellRoot);

Also applies to: 103-108

🤖 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 `@tools/target/native-release-identity.test.ts` around lines 72 - 83, Isolate
the suffix fixture so it cannot permanently modify the shared shellRoot used by
later tests. Update the test around writeAndroidReleaseSuffix and the
corresponding case at lines 103–108 to use a dedicated temporary shell, or
restore writeShellFiles(shellRoot) immediately after each assertion, while
preserving the existing error expectations.

99-101: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Good fixture expansion — Debug and ShareExtension values now prove the Release/App scoping. Consider also adding an iOS marketingVersion vs APP_VERSION mismatch case to mirror the Android coverage at lines 60-70.

🤖 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 `@tools/target/native-release-identity.test.ts` around lines 99 - 101, Add a
fixture scenario in the native release identity tests that sets differing iOS
marketingVersion and APP_VERSION values, mirroring the Android mismatch coverage
near the existing version cases. Assert that the implementation uses the correct
iOS Release/App-scoped value and preserves the expected behavior when the values
differ.
docs/MULTI_TARGET_RELEASE_VERSIONING.md (1)

61-72: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the two new failure modes. The bullet list still describes only match-against-metadata; it omits that release builds are rejected when applicationIdSuffix/versionNameSuffix are present and that only the iOS App target's Release configuration is inspected.

🤖 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 `@docs/MULTI_TARGET_RELEASE_VERSIONING.md` around lines 61 - 72, The Android
validation documentation should also state that release builds fail when
applicationIdSuffix or versionNameSuffix is configured. Clarify that iOS
validation inspects only the App target’s Release configuration, while
preserving the existing metadata and release-ledger matching requirements.
tools/target/native-release-identity.ts (1)

160-171: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Suffix guard silently skips Kotlin DSL / non-literal release blocks.

readNamedGradleBlock only matches buildTypes { + release {. Common forms such as release { ... } declared via getByName("release") {, named("release") {, or buildTypes.release { ... } return undefined, so the guard passes and the mismatch it was meant to prevent can still ship. Since a missing block is indistinguishable from "no suffix", consider matching those forms too (or failing closed when buildTypes exists but the release block can't be parsed).

🤖 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 `@tools/target/native-release-identity.ts` around lines 160 - 171, The Android
release suffix guard in assertNoAndroidReleaseIdentitySuffix currently misses
Kotlin DSL and non-literal release block forms. Extend the release-block
detection used by readNamedGradleBlock to recognize getByName("release"),
named("release"), and buildTypes.release declarations, or fail closed when
buildTypes exists but its release block cannot be parsed, while preserving the
existing suffix checks.
.sampo/changesets/fix-native-release-identity.md (1)

1-6: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Changeset present and scoped correctly; consider whether patch undersells the behavior change. Builds that previously passed preflight (suffixed release identities, native version drift from APP_VERSION) now fail, so a minor bump plus a one-line note on the new APP_VERSION requirement would set clearer expectations. As per coding guidelines, "add a Sampo changeset when a publishable package's public API, runtime behavior, package metadata, generated release artifacts, or user-facing integration contract changes."

🤖 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 @.sampo/changesets/fix-native-release-identity.md around lines 1 - 6, Update
the changeset for npm/@mpgd/cli from a patch to a minor release, and add a
concise note stating that APP_VERSION must match the native Android and iOS
release identities before packaging.

Source: Coding guidelines

🤖 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 `@tools/target/native-release-identity.ts`:
- Around line 144-158: The assertNativeVersionMatchesGameVersion validation must
distinguish an absent APP_VERSION from the default 0.0.0 value. Update the
function to require and compare a non-default APP_VERSION when present, while
skipping App in Toss native validation only when APP_VERSION is absent; preserve
normal Android and non-App-in-Toss validation behavior.
- Around line 173-187: Update readIosAppReleaseBuildSettings so the App-target
match accepts both unquoted and quoted name values, including name = "App";.
Also avoid eagerly calling readPbxObject for every parsed object: filter or
search for PBXNativeTarget candidates first and read blocks lazily within the
lookup, while preserving the existing missing-target error.

---

Outside diff comments:
In `@docs/MULTI_TARGET_RELEASE_VERSIONING.md`:
- Around line 47-59: Update the shell example in MULTI_TARGET_RELEASE_VERSIONING
to export APP_VERSION=1.4.0 alongside the platform version variables, and
replace the doubled trailing backslashes in the mpgd target build command with
single shell line-continuation backslashes so the example is copy-pasteable.

---

Nitpick comments:
In @.sampo/changesets/fix-native-release-identity.md:
- Around line 1-6: Update the changeset for npm/@mpgd/cli from a patch to a
minor release, and add a concise note stating that APP_VERSION must match the
native Android and iOS release identities before packaging.

In `@docs/MULTI_TARGET_RELEASE_VERSIONING.md`:
- Around line 61-72: The Android validation documentation should also state that
release builds fail when applicationIdSuffix or versionNameSuffix is configured.
Clarify that iOS validation inspects only the App target’s Release
configuration, while preserving the existing metadata and release-ledger
matching requirements.

In `@tools/target/native-release-identity.test.ts`:
- Around line 72-83: Isolate the suffix fixture so it cannot permanently modify
the shared shellRoot used by later tests. Update the test around
writeAndroidReleaseSuffix and the corresponding case at lines 103–108 to use a
dedicated temporary shell, or restore writeShellFiles(shellRoot) immediately
after each assertion, while preserving the existing error expectations.
- Around line 99-101: Add a fixture scenario in the native release identity
tests that sets differing iOS marketingVersion and APP_VERSION values, mirroring
the Android mismatch coverage near the existing version cases. Assert that the
implementation uses the correct iOS Release/App-scoped value and preserves the
expected behavior when the values differ.

In `@tools/target/native-release-identity.ts`:
- Around line 160-171: The Android release suffix guard in
assertNoAndroidReleaseIdentitySuffix currently misses Kotlin DSL and non-literal
release block forms. Extend the release-block detection used by
readNamedGradleBlock to recognize getByName("release"), named("release"), and
buildTypes.release declarations, or fail closed when buildTypes exists but its
release block cannot be parsed, while preserving the existing suffix checks.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ac594776-5b61-4bb7-9390-b9dff34e5e26

📥 Commits

Reviewing files that changed from the base of the PR and between f613726 and fb87690.

📒 Files selected for processing (4)
  • .sampo/changesets/fix-native-release-identity.md
  • docs/MULTI_TARGET_RELEASE_VERSIONING.md
  • tools/target/native-release-identity.test.ts
  • tools/target/native-release-identity.ts

Comment thread tools/target/native-release-identity.ts Resolved
Comment thread tools/target/native-release-identity.ts Resolved

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fb876906f0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tools/target/native-release-identity.ts Outdated
}

function assertNoAndroidReleaseIdentitySuffix(source: string, file: string): void {
const releaseBlock = readNamedGradleBlock(source, 'buildTypes', 'release');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reject suffixes assigned outside the nested release block

When a valid Groovy build assigns a suffix through a qualified expression such as buildTypes.release.applicationIdSuffix = ".store", this helper returns only the contents of buildTypes { release { ... } } and therefore misses the assignment. The preflight then validates the unsuffixed defaultConfig values and passes, while the bundleRelease invocation in tools/target/build-target.ts packages a different application ID. Search the complete Gradle source for these release-affecting assignments, or reject unsupported qualified configuration forms as well.

Useful? React with 👍 / 👎.

Comment thread tools/target/native-release-identity.ts Outdated
Comment on lines +104 to +105
const source = readRequiredFile(file, 'iOS Xcode project configuration');
const appReleaseSettings = readIosAppReleaseBuildSettings(source, file);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Resolve inherited App Release settings

When the App target inherits an identity setting from the PBXProject Release configuration, or its target value is $(inherited), Xcode can resolve a valid effective bundle ID and version even though this extracted target buildSettings block has no matching literal. The subsequent assertions therefore reject a correctly configured archive before the build runs. Combine the target settings with their project/base configuration inheritance rather than requiring all three values directly in the target block.

Useful? React with 👍 / 👎.

Comment thread tools/target/native-release-identity.ts Outdated
Comment on lines +128 to +131
const globalExpression = expression.global
? expression
: new RegExp(expression.source, `${expression.flags}g`);
const values = [...source.matchAll(globalExpression)].map((match) => match[1]?.trim());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Exclude commented settings from identity matches

When build.gradle retains a commented-out old value such as // versionName "1.3.0", this raw-source matchAll includes it alongside the active versionName "1.4.0"; values.some(...) then reports an identity mismatch even though Gradle packages the expected release. Ignore comments and string contents, or parse only active Gradle declarations, before comparing all matches.

Useful? React with 👍 / 👎.

Comment thread docs/MULTI_TARGET_RELEASE_VERSIONING.md Outdated
Comment on lines +56 to +57
mpgd target build android production \\
--targets-file ./mpgd.targets.json \\

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use single shell continuation backslashes

When a reader copies this documented command, each \\ escapes the other backslash instead of the newline, so mpgd receives a literal backslash argument and the following --targets-file and --kit-path lines are executed as separate commands. Use one trailing backslash on each continued shell line so the documented native release invocation is runnable.

Useful? React with 👍 / 👎.

Comment on lines +281 to +282
if (character === '{') {
depth += 1;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Ignore braces inside quoted PBX values

When any Xcode object contains an unmatched brace character inside a quoted value—commonly a PBXShellScriptBuildPhase with a command such as echo '{'—this counter treats the string character as structural nesting. Because App-target discovery eagerly calls readPbxObject for every object, an otherwise valid project then throws an "unclosed Xcode object" error and cannot pass release preflight. Parse quoted strings and comments separately, or locate the target without brace-scanning unrelated objects.

Useful? React with 👍 / 👎.

@imjlk

imjlk commented Jul 26, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (4)
tools/target/native-release-identity.ts (4)

168-177: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Static-analysis ReDoS hints here are false positives.

expression is always an internal literal regex; reconstructing it with the g flag is a correct fix for matchAll. No action needed. One nit: new RegExp(expression.source, ...) drops nothing today, but a cached module-level WeakMap<RegExp, RegExp> would avoid recompiling per call if this ever runs in a loop over large sources.

🤖 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 `@tools/target/native-release-identity.ts` around lines 168 - 177, No code
changes are needed for readSettingValues; retain the current regex
reconstruction and matchAll handling. The caching suggestion is optional and
outside this comment’s required scope.

Source: Linters/SAST tools


208-228: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

/\brelease\s*\{/gu also matches non-buildType release blocks.

signingConfigs { release { ... } } (and any other DSL block literally named release) is collected as an "Android release Gradle block". Today that only widens the suffix check, so it is a false-positive risk rather than a miss — but the error message would then point at a block that has nothing to do with build types. Consider scoping block discovery to a buildTypes { ... } region before matching bare release {.

Also note buildTypes.release { } is matched by both the first and third expression, yielding duplicate blocks.

Also applies to: 340-361

🤖 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 `@tools/target/native-release-identity.ts` around lines 208 - 228, Update
release-block discovery used by assertNoAndroidReleaseIdentitySuffix to only
collect bare release blocks within a buildTypes region, excluding signingConfigs
and other unrelated DSL blocks. Also prevent buildTypes.release blocks from
being collected twice when already matched by the qualified buildTypes.release
expression, while preserving suffix validation for genuine Android release build
configurations.

384-460: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Shared scanner state duplicated between readBracedBlock and stripComments.

Both implement the same quote/line-comment/block-comment state machine with slightly different bodies. Extracting a small tokenizer (or a generator yielding { index, character, inCode }) would keep the two in sync when pbxproj/Gradle quirks are found later. Not blocking — current behavior looks correct, including quote-before-comment precedence.

One edge case worth knowing: an unquoted pbxproj value containing // (e.g. a bare URL) would be treated as a line comment by readBracedBlock. Xcode quotes such values, so this is theoretical.

Also applies to: 462-531

🤖 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 `@tools/target/native-release-identity.ts` around lines 384 - 460, Extract the
shared quote, line-comment, and block-comment scanning state machine from
readBracedBlock and stripComments into a reusable tokenizer or scanner helper
that exposes each character’s index and code/comment status. Refactor both
functions to consume this shared state while preserving their current brace
parsing and comment-stripping behavior, including quote-before-comment
precedence.

263-310: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Deduplicate the two full-project pbxproj scans.

findIosAppTargetBlock and findIosProjectBlock are identical except for the final predicate, and each one calls readBracedBlock for every object in the file — so a real project.pbxproj is walked (and brace-matched) twice end to end. A single pass that returns both blocks, with the cheap isa test applied before reading the full block, keeps preflight linear-ish and removes the copy/paste.

♻️ Suggested single-pass scan
-function findIosAppTargetBlock(source: string, file: string): string | undefined {
-  for (const match of source.matchAll(/\b([A-F0-9]+)\s*\/\*\s*[^*]+\s*\*\/\s*=\s*\{/gu)) {
+function findIosBlocks(source: string, file: string): {
+  appTarget: string | undefined;
+  project: string | undefined;
+} {
+  let appTarget: string | undefined;
+  let project: string | undefined;
+
+  for (const match of source.matchAll(/\b([A-F0-9]+)\s*\/\*\s*[^*]+\s*\*\/\s*=\s*\{/gu)) {
     const id = match[1];
 
     if (id === undefined || match.index === undefined) {
       continue;
     }
 
+    if (appTarget !== undefined && project !== undefined) {
+      break;
+    }
+
     const block = readBracedBlock(
       source,
       source.indexOf('{', match.index),
       `Xcode object ${id}`,
       file,
     );
 
-    if (
-      /\bisa\s*=\s*PBXNativeTarget;/u.test(block)
-      && /\bname\s*=\s*"?App"?\s*;/u.test(block)
-    ) {
-      return block;
-    }
+    if (
+      appTarget === undefined
+      && /\bisa\s*=\s*PBXNativeTarget;/u.test(block)
+      && /\bname\s*=\s*"?App"?\s*;/u.test(block)
+    ) {
+      appTarget = block;
+    } else if (project === undefined && /\bisa\s*=\s*PBXProject;/u.test(block)) {
+      project = block;
+    }
   }
 
-  return undefined;
-}
+  return { appTarget, project };
+}
🤖 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 `@tools/target/native-release-identity.ts` around lines 263 - 310, Replace the
separate scans in findIosAppTargetBlock and findIosProjectBlock with one shared
pass that examines each Xcode object once, checks the object’s cheap isa
indicators before calling readBracedBlock, and returns both the PBXNativeTarget
named App block and the PBXProject block together. Update callers to consume the
paired result while preserving undefined behavior when either block is absent.
🤖 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.

Nitpick comments:
In `@tools/target/native-release-identity.ts`:
- Around line 168-177: No code changes are needed for readSettingValues; retain
the current regex reconstruction and matchAll handling. The caching suggestion
is optional and outside this comment’s required scope.
- Around line 208-228: Update release-block discovery used by
assertNoAndroidReleaseIdentitySuffix to only collect bare release blocks within
a buildTypes region, excluding signingConfigs and other unrelated DSL blocks.
Also prevent buildTypes.release blocks from being collected twice when already
matched by the qualified buildTypes.release expression, while preserving suffix
validation for genuine Android release build configurations.
- Around line 384-460: Extract the shared quote, line-comment, and block-comment
scanning state machine from readBracedBlock and stripComments into a reusable
tokenizer or scanner helper that exposes each character’s index and code/comment
status. Refactor both functions to consume this shared state while preserving
their current brace parsing and comment-stripping behavior, including
quote-before-comment precedence.
- Around line 263-310: Replace the separate scans in findIosAppTargetBlock and
findIosProjectBlock with one shared pass that examines each Xcode object once,
checks the object’s cheap isa indicators before calling readBracedBlock, and
returns both the PBXNativeTarget named App block and the PBXProject block
together. Update callers to consume the paired result while preserving undefined
behavior when either block is absent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 17561229-cba1-4f59-a1ad-7824c21036cd

📥 Commits

Reviewing files that changed from the base of the PR and between fb87690 and bab9a48.

📒 Files selected for processing (4)
  • .sampo/changesets/fix-native-release-identity.md
  • docs/MULTI_TARGET_RELEASE_VERSIONING.md
  • tools/target/native-release-identity.test.ts
  • tools/target/native-release-identity.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • .sampo/changesets/fix-native-release-identity.md
  • tools/target/native-release-identity.test.ts

@imjlk

imjlk commented Jul 26, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fe895077b6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +142 to +146
const targetValues = readSettingValues(settings.target, expression)
.filter((value) => value !== '$(inherited)');
const values = targetValues.length > 0
? targetValues
: readSettingValues(settings.project, expression);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Inspect target xcconfigs before accepting project values

When the App target's Release configuration supplies an identity through its baseConfigurationReference, the inline target settings are absent or $(inherited), so this fallback validates only the project-level value. A target xcconfig can therefore override PRODUCT_BUNDLE_IDENTIFIER, MARKETING_VERSION, or CURRENT_PROJECT_VERSION with a different value while the preflight passes, allowing the archive identity to diverge from the release manifest.

Useful? React with 👍 / 👎.

Comment on lines +210 to +213
const qualifiedReleaseSuffixes = [
/\bbuildTypes\s*\.\s*release\s*\.\s*(?:applicationIdSuffix|versionNameSuffix)\b/u,
/\bbuildTypes\s*\.\s*(?:getByName|named)\s*\(\s*["']release["']\s*\)\s*\.\s*(?:applicationIdSuffix|versionNameSuffix)\b/u,
];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reject suffixes inherited from defaultConfig

When defaultConfig declares a valid versionNameSuffix or applicationIdSuffix, the release variant inherits that suffix, but these checks inspect only buildTypes.release forms. The ordinary identity regexes do not match the suffix property either, so preflight accepts the unsuffixed values and the generated bundleRelease can have an identity different from the recorded release metadata.

Useful? React with 👍 / 👎.

@imjlk
imjlk merged commit caab359 into main Jul 26, 2026
6 checks passed
@imjlk
imjlk deleted the codex/fix-native-release-review branch July 26, 2026 17:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant