Skip to content

Implement Android TV Play plan target#456

Open
Photon101 wants to merge 1 commit into
profullstack:masterfrom
Photon101:photon101/tv-androidtv-play-plan
Open

Implement Android TV Play plan target#456
Photon101 wants to merge 1 commit into
profullstack:masterfrom
Photon101:photon101/tv-androidtv-play-plan

Conversation

@Photon101
Copy link
Copy Markdown
Contributor

Summary

  • turn the Android TV target into an inspectable Google Play packaging plan
  • add manifest validation for Leanback feature and launcher requirements when a manifest path is provided
  • preserve safe dry-run shipping metadata and channel-to-track mapping

Tests

  • pnpm --filter @profullstack/sh1pt-target-tv-androidtv typecheck
  • pnpm test -- packages/targets/tv-androidtv/src/index.test.ts

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 28, 2026

Greptile Summary

This PR converts the Android TV target into an inspectable Google Play packaging plan, adding optional manifest validation (Leanback feature + LEANBACK_LAUNCHER), a configurable Gradle task, and store metadata in the real ship response.

  • Missing LAUNCHER validation: validateAndroidTvManifest checks leanback and LEANBACK_LAUNCHER but omits android.intent.category.LAUNCHER, which manifestChecks marks required: true — a manifest without it will silently pass and produce a contradictory plan.
  • Unnecessary buildPlan in non-dry-run ship(): buildPlan is always awaited in ship(), including the live-upload path where its return value is never read; since buildPlan may read the manifest from disk, this can throw ENOENT in ship environments that don't have the project source.
  • The positive-path manifest test fixture lacks the LAUNCHER intent filter and will fail once the validation gap is closed.

Confidence Score: 3/5

Not safe to merge as-is — manifest validation is incomplete and the ship path performs unnecessary disk I/O that will fail in typical CD environments.

The validation function promises three manifest requirements but only enforces two, so invalid TV manifests pass silently. The ship method unconditionally re-reads the manifest from disk on every real upload, where the project source is rarely present, turning a successful build into a broken deploy.

Both changed files need attention: index.ts for the validation gap and the unconditional buildPlan call in ship(); index.test.ts for the positive-path manifest fixture that will fail once the validation is fixed.

Important Files Changed

Filename Overview
packages/targets/tv-androidtv/src/index.ts Refactors build/ship to async, adds manifest validation and configurable Gradle task. Two issues: validateAndroidTvManifest omits the LAUNCHER category check while manifestChecks marks it required:true; and ship() unconditionally awaits buildPlan() (re-reading the manifest from disk) even in the non-dry-run path where the result is unused.
packages/targets/tv-androidtv/src/index.test.ts Adds manifest-validation tests and real-ship-mode coverage; the positive-path manifest fixture is missing the LAUNCHER intent filter and will break once the missing validation is added to the implementation.

Sequence Diagram

sequenceDiagram
    participant CI
    participant build as adapter.build()
    participant ship as adapter.ship()
    participant fs as File System
    participant play as Play Console API

    CI->>build: build(ctx, config)
    build->>fs: maybeValidateManifest (reads AndroidManifest.xml)
    fs-->>build: manifest content
    build->>build: validateAndroidTvManifest()
    build->>fs: writeFile(androidtv-package-plan.json)
    build-->>CI: "{ artifact, meta: { planFile, track, manifestChecks } }"

    CI->>ship: ship(ctx, config)
    ship->>build: buildPlan(ctx, config) [always called]
    build->>fs: maybeValidateManifest (re-reads manifest)
    fs-->>build: manifest / ENOENT error
    alt dryRun
        ship-->>CI: "{ id: dry-run, meta: { commands } }"
    else live upload
        ship->>play: [TODO] edits.insert / upload / commit
        ship-->>CI: "{ id, url, meta: { artifact, track } }"
    end
Loading

Reviews (1): Last reviewed commit: "Implement Android TV Play plan target" | Re-trigger Greptile

Comment on lines +39 to 42
if (!manifest.includes('android.intent.category.LEANBACK_LAUNCHER')) {
throw new Error('AndroidManifest must declare LEANBACK_LAUNCHER');
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 The validateAndroidTvManifest function checks for android.software.leanback and LEANBACK_LAUNCHER but silently skips the android.intent.category.LAUNCHER check that manifestChecks marks as required: true. Any manifest that lacks the LAUNCHER category will pass validation and produce a plan file that contradicts its own manifestChecks entries — the app would be unpublishable to the standard Play Store launcher.

Suggested change
if (!manifest.includes('android.intent.category.LEANBACK_LAUNCHER')) {
throw new Error('AndroidManifest must declare LEANBACK_LAUNCHER');
}
}
if (!manifest.includes('android.intent.category.LEANBACK_LAUNCHER')) {
throw new Error('AndroidManifest must declare LEANBACK_LAUNCHER');
}
if (!manifest.includes('android.intent.category.LAUNCHER')) {
throw new Error('AndroidManifest must declare android.intent.category.LAUNCHER');
}
}

Comment on lines 110 to 125
@@ -92,8 +125,12 @@ export default defineTarget<Config>({
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 In the non-dry-run branch of ship(), buildPlan() is awaited but its return value is never used — plan goes out of scope immediately after the if block. Because buildPlan calls maybeValidateManifest, which reads the manifest from disk, this means every real upload triggers a gratuitous file-system read that will throw ENOENT whenever the project source is not co-located with the ship environment (e.g., a separate CD runner that only has the artifact). Moving buildPlan inside the if (ctx.dryRun) guard keeps the dry-run behavior intact without the risk.

Suggested change
async ship(ctx, config) {
const track = releaseTrack(ctx.channel, config);
const packageName = requirePackageName(config);
ctx.log(`upload to Play Console package=${packageName} track=${track}`);
if (ctx.dryRun) {
const plan = await buildPlan(ctx, config);
return {
id: 'dry-run',
meta: {
packageName,
artifact: ctx.artifact,
track,
commands: plan.commands.slice(1),
},
};
}

Comment on lines +66 to +77
await writeFile(join(projectDir, 'app', 'src', 'main', 'AndroidManifest.xml'), `
<manifest package="com.acme.tv">
<uses-feature android:name="android.software.leanback" android:required="true" />
<application>
<activity android:name=".MainActivity">
<intent-filter>
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
`, 'utf-8');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 The "validates a provided manifest" test fixture does not include android.intent.category.LAUNCHER, yet the test expects the plan to be written successfully. Once the missing validation check in validateAndroidTvManifest is added, this test will start failing — the fixture needs a LAUNCHER intent filter to remain a valid positive-path test.

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