diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index 37ddf42..bf834e6 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -222,3 +222,227 @@ jobs: # warning in these sources should block a release. test.yaml's iOS build catches it first, # on the PR that introduces it, so reaching this job with warnings should not happen. run: pod trunk push AppductCore.podspec + + publish-maven: + # Alongside publish-cocoapods rather than after it: the two are independent registries, and + # neither can be rolled back, so ordering them buys nothing. Both wait on the npm publishes for + # the same reason publish-cocoapods does -- Maven Central is immutable, a version can never be + # replaced or deleted, so the irreversible half of a release only runs once npm (which allows + # `npm unpublish` for 72 hours) is known good. + # + # Re-run safe, like publish-cocoapods: an already-published version is skipped, and a deployment + # uploaded by an earlier attempt of this run is picked back up instead of being uploaded twice. + needs: [publish-cli, publish-react-native] + runs-on: ubuntu-latest + timeout-minutes: 30 + # Holds CENTRAL_TOKEN_USERNAME/PASSWORD (Central Portal user token) and the GPG key pair. + # Central has no OIDC trusted-publishing equivalent either, so these are long-lived secrets in + # a protected environment. + environment: maven-central + steps: + - name: Check out source + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + # The release's tag: the Gradle build reads its version from + # packages/react-native/package.json, so the checkout must be the commit the release names. + ref: ${{ github.event.release.tag_name }} + persist-credentials: false + - name: Set up Java 17 + uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 + with: + distribution: temurin + java-version: 17 + - name: Set up Android SDK + uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699 # v4.0.1 + with: + # See test.yaml: the legacy standalone "tools" package no longer exists upstream. + packages: platform-tools + - name: Check whether this version is already on Maven Central + # Two independent sources, OR-ed. Each can only say "published" when that is genuinely true, + # so neither can cause a false skip -- the dangerous direction, since it would end a release + # green without anything ever reaching Central. + # + # repo1.maven.org is public and unambiguous, but trails a real publish by up to ~30 minutes. + # The Portal's `published` endpoint has no such lag, but its response body is not in + # Sonatype's published docs, so it only counts on an explicit `"published": true` and any + # other answer falls through. Checking `core` alone is enough: both artifacts go up in one + # bundle, so they are published together or not at all. + env: + CENTRAL_TOKEN_USERNAME: ${{ secrets.CENTRAL_TOKEN_USERNAME }} + CENTRAL_TOKEN_PASSWORD: ${{ secrets.CENTRAL_TOKEN_PASSWORD }} + run: | + version=$(node -p "require('./packages/react-native/package.json').version") + echo "APPDUCT_VERSION=$version" >> "$GITHUB_ENV" + published=false + repo1=$(curl -s -o /dev/null -I -w '%{http_code}' \ + "https://repo1.maven.org/maven2/com/callstack/appduct/core/$version/core-$version.pom") || repo1=000 + if [ "$repo1" = "200" ]; then + published=true + else + auth=$(printf '%s:%s' "$CENTRAL_TOKEN_USERNAME" "$CENTRAL_TOKEN_PASSWORD" | base64 | tr -d '\n') + if curl -sS -H "Authorization: Bearer $auth" \ + "https://central.sonatype.com/api/v1/publisher/published?namespace=com.callstack.appduct&name=core&version=$version" \ + | node -e 'let s="";process.stdin.on("data",c=>s+=c).on("end",()=>{ + try { process.exit(JSON.parse(s).published === true ? 0 : 1); } catch { process.exit(1); } + })'; then + published=true + fi + fi + echo "ALREADY_PUBLISHED=$published" >> "$GITHUB_ENV" + if [ "$published" = true ]; then + echo "::notice::com.callstack.appduct $version is already on Maven Central; skipping the upload." + fi + - name: Restore the deployment id from an earlier attempt + if: env.ALREADY_PUBLISHED == 'false' + # Covers what the check above cannot: an upload that succeeded, followed by a failure later + # in this job (most likely the status poll timing out), followed by "Re-run failed jobs". + # That deployment is VALIDATED but unpublished, so it is invisible to both published checks, + # and the Portal API has no endpoint to list deployments -- its id is the only handle on it. + # The upload step saves the id as an artifact of this run, which a re-run attempt can read. + # Absent on a first attempt, hence continue-on-error. + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: central-deployment-id + path: ${{ runner.temp }}/central-deployment + - name: Reuse the earlier deployment if it is still live + if: env.ALREADY_PUBLISHED == 'false' + # Only states that mean "this deployment will, or already did, carry the release" are reused. + # FAILED, a deployment dropped in the Portal UI, or any answer that cannot be parsed all + # fall through to a fresh upload -- reusing wrongly would leave the release with nothing to + # publish, while uploading again at worst leaves a duplicate for a human to drop. + env: + CENTRAL_TOKEN_USERNAME: ${{ secrets.CENTRAL_TOKEN_USERNAME }} + CENTRAL_TOKEN_PASSWORD: ${{ secrets.CENTRAL_TOKEN_PASSWORD }} + run: | + reuse=false + id_file="$RUNNER_TEMP/central-deployment/deployment-id" + if [ -f "$id_file" ]; then + id=$(tr -d '[:space:]' < "$id_file") + auth=$(printf '%s:%s' "$CENTRAL_TOKEN_USERNAME" "$CENTRAL_TOKEN_PASSWORD" | base64 | tr -d '\n') + state=$(curl -sS -X POST -H "Authorization: Bearer $auth" \ + "https://central.sonatype.com/api/v1/publisher/status?id=$id" \ + | node -e 'let s="";process.stdin.on("data",c=>s+=c).on("end",()=>{ + try { process.stdout.write(JSON.parse(s).deploymentState || "UNKNOWN"); } + catch { process.stdout.write("UNKNOWN"); } + })') || state=UNKNOWN + echo "Deployment $id from an earlier attempt is $state." + case "$state" in + PENDING|VALIDATING|VALIDATED|PUBLISHING|PUBLISHED) + reuse=true + echo "DEPLOYMENT_ID=$id" >> "$GITHUB_ENV" + echo "::notice::Reusing deployment $id ($state) instead of uploading $APPDUCT_VERSION again." + ;; + esac + fi + echo "REUSE_DEPLOYMENT=$reuse" >> "$GITHUB_ENV" + - name: Stage the signed bundle + if: env.ALREADY_PUBLISHED == 'false' && env.REUSE_DEPLOYMENT != 'true' + # Publishes both modules into one on-disk Maven repository layout, which is exactly the + # shape the Portal's bundle zip must have. Gradle writes the .md5/.sha1 checksums Central + # requires for every file, and the `signing` plugin writes a .asc beside each one -- the + # key is only ever in the environment, never on the runner's disk. + working-directory: packages/native/android + env: + APPDUCT_SIGNING_KEY: ${{ secrets.APPDUCT_SIGNING_KEY }} + APPDUCT_SIGNING_PASSWORD: ${{ secrets.APPDUCT_SIGNING_PASSWORD }} + run: | + ./gradlew --no-daemon \ + :core:publishAllPublicationsToCentralBundleRepository \ + :core-noop:publishAllPublicationsToCentralBundleRepository + - name: Verify every artifact is signed + if: env.ALREADY_PUBLISHED == 'false' && env.REUSE_DEPLOYMENT != 'true' + # A missing APPDUCT_SIGNING_KEY makes `signing` a silent no-op by design, so that + # `publishToMavenLocal` keeps working on an unkeyed machine (see publishing.gradle). That + # same leniency would otherwise let this job upload an unsigned bundle and fail deep inside + # Central's validator, so assert it here where the cause is obvious. + working-directory: packages/native/android + run: | + bundle=build/central-bundle + unsigned=$(find "$bundle" -type f \ + ! -name '*.asc' ! -name '*.md5' ! -name '*.sha1' ! -name '*.sha256' ! -name '*.sha512' \ + ! -name 'maven-metadata.xml' \ + -exec sh -c '[ -f "$1.asc" ] || echo "$1"' _ {} \;) + if [ -n "$unsigned" ]; then + echo "::error::Unsigned artifacts staged -- is APPDUCT_SIGNING_KEY set?" + echo "$unsigned" + exit 1 + fi + echo "All staged artifacts carry a .asc signature." + - name: Build the deployment bundle + if: env.ALREADY_PUBLISHED == 'false' && env.REUSE_DEPLOYMENT != 'true' + # maven-metadata.xml is excluded deliberately: Central generates its own repository + # metadata, and Gradle's copy describes only this one deployment, so shipping it is at best + # redundant and at worst a validation failure. + working-directory: packages/native/android/build/central-bundle + run: zip -r "$RUNNER_TEMP/central-bundle.zip" . -x '*maven-metadata.xml*' + - name: Upload to the Central Portal + if: env.ALREADY_PUBLISHED == 'false' && env.REUSE_DEPLOYMENT != 'true' + env: + CENTRAL_TOKEN_USERNAME: ${{ secrets.CENTRAL_TOKEN_USERNAME }} + CENTRAL_TOKEN_PASSWORD: ${{ secrets.CENTRAL_TOKEN_PASSWORD }} + # publishingType=USER_MANAGED, not AUTOMATIC. Central validates the bundle and then waits + # for a human to press Publish in the Portal UI. Given the deployment can never be undone + # once it goes out, the last step stays manual until this path has proven itself over a few + # releases; switching to AUTOMATIC later is a one-word change. + run: | + auth=$(printf '%s:%s' "$CENTRAL_TOKEN_USERNAME" "$CENTRAL_TOKEN_PASSWORD" | base64 | tr -d '\n') + response=$(curl --fail-with-body -sS -X POST \ + -H "Authorization: Bearer $auth" \ + -F "bundle=@$RUNNER_TEMP/central-bundle.zip" \ + "https://central.sonatype.com/api/v1/publisher/upload?name=appduct-v$APPDUCT_VERSION&publishingType=USER_MANAGED") + id=$(printf '%s' "$response" | tr -d '[:space:]') + # The body is the bare deployment id. Refuse anything else rather than saving junk that a + # re-run would then try to resume. + if ! printf '%s' "$id" | grep -Eq '^[0-9a-fA-F-]{36}$'; then + echo "::error::Unexpected upload response (expected a deployment id): $response" + exit 1 + fi + echo "Deployment id: $id" + echo "DEPLOYMENT_ID=$id" >> "$GITHUB_ENV" + mkdir -p "$RUNNER_TEMP/central-deployment" + printf '%s\n' "$id" > "$RUNNER_TEMP/central-deployment/deployment-id" + - name: Save the deployment id for a re-run + if: env.ALREADY_PUBLISHED == 'false' && env.REUSE_DEPLOYMENT != 'true' + # Deliberately before the status poll, so the id survives whatever fails after the upload. + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: central-deployment-id + path: ${{ runner.temp }}/central-deployment/deployment-id + # A later attempt that finds the saved deployment FAILED or dropped uploads a fresh one, + # and must replace this id rather than collide with it. + overwrite: true + # GitHub allows re-running a workflow run for up to 30 days. + retention-days: 30 + - name: Report validation status + if: env.ALREADY_PUBLISHED == 'false' + env: + CENTRAL_TOKEN_USERNAME: ${{ secrets.CENTRAL_TOKEN_USERNAME }} + CENTRAL_TOKEN_PASSWORD: ${{ secrets.CENTRAL_TOKEN_PASSWORD }} + # Validation is asynchronous, so poll until it leaves the pending states. FAILED fails the + # job; VALIDATED is the success case and means the release is staged and waiting for the + # manual Publish. Never publishes anything itself. + run: | + auth=$(printf '%s:%s' "$CENTRAL_TOKEN_USERNAME" "$CENTRAL_TOKEN_PASSWORD" | base64 | tr -d '\n') + for _ in $(seq 1 40); do + state=$(curl --fail-with-body -sS -X POST \ + -H "Authorization: Bearer $auth" \ + "https://central.sonatype.com/api/v1/publisher/status?id=$DEPLOYMENT_ID" \ + | node -e 'let s="";process.stdin.on("data",c=>s+=c).on("end",()=>{ + process.stdout.write(JSON.parse(s).deploymentState || "UNKNOWN"); + })') + echo "deploymentState=$state" + case "$state" in + VALIDATED|PUBLISHING|PUBLISHED) + echo "::notice::Deployment $DEPLOYMENT_ID is $state. Press Publish at https://central.sonatype.com/publishing/deployments to release it." + exit 0 + ;; + FAILED) + echo "::error::Central rejected deployment $DEPLOYMENT_ID. See https://central.sonatype.com/publishing/deployments" + exit 1 + ;; + esac + sleep 15 + done + echo "::error::Timed out waiting for deployment $DEPLOYMENT_ID to validate. Re-running this job resumes watching it rather than uploading again." + exit 1 diff --git a/docs/BUILD-VARIANTS.md b/docs/BUILD-VARIANTS.md index 447beb9..c86a886 100644 --- a/docs/BUILD-VARIANTS.md +++ b/docs/BUILD-VARIANTS.md @@ -309,14 +309,14 @@ those sources compile, so the pod never needs `Stub/`. **Android** (`packages/native/android`): a standalone Gradle project (own `settings.gradle`, not a workspace member) publishing two modules with the same public API — -`com.callstackincubator.appduct:core` (the real implementation) and `:core-noop` (every +`com.callstack.appduct:core` (the real implementation) and `:core-noop` (every method a no-op, no `okhttp` dependency, no marker class). `@appduct/react-native` vendors `core`/`core-noop` into `android/core`/`android/core-noop` and picks between them the same way described above — `AppductPackage`/`NativeAppductModule` (`android/src/main/java`) always compile, and `debug`/`release` add whichever vendored directory to `java.srcDirs`. **A plain Android app instead depends on `core`/`core-noop` as ordinary Maven coordinates** -(`debugImplementation("com.callstackincubator.appduct:core:")` / -`releaseImplementation("com.callstackincubator.appduct:core-noop:")`, +(`debugImplementation("com.callstack.appduct:core:")` / +`releaseImplementation("com.callstack.appduct:core-noop:")`, `packages/native/android/README.md`) — a real per-variant *dependency* decision, distinct from (and simpler than) the vendored copy's source-directory swap, since a plain app has no `PackageList.java`-style shared registration file forcing every variant onto the same diff --git a/docs/CI.md b/docs/CI.md index 2194c89..97c767a 100644 --- a/docs/CI.md +++ b/docs/CI.md @@ -60,7 +60,7 @@ docs/tasks/14-native-core-extraction.md) or the `RCTNativeAppduct` Objective-C c OR'd together so a stripped binary that dropped one doesn't read as absent; the plugin-authored `Info.plist` keys are reported alongside them but cannot flip the verdict on their own. On Android the verdict is decided by the `AppductNativeMarker` keep-rule -signal alone; the `com.callstackincubator.appduct` dex package and the +signal alone; the `com.callstack.appduct` dex package and the `AndroidManifest.xml` meta-data keys are reported alongside it but cannot flip it — the two platforms now follow the same "real-code-only symbol, corroborating signals only" rule (see [Android detection](#android-detection)). @@ -179,7 +179,7 @@ jobs each build the corresponding native playground (`playground-native/android` `playground-native/ios`) after their Expo-playground steps, for exactly this reason: - **Android**: `./gradlew :app:assembleDebug :app:assembleRelease` in `playground-native/android` - (which resolves `com.callstackincubator.appduct:core`/`:core-noop` to the local + (which resolves `com.callstack.appduct:core`/`:core-noop` to the local `packages/native/android` projects via `settings.gradle`'s `includeBuild` substitution, not a published artifact), then `appduct doctor --assert-present` on the debug APK and `--assert-absent` on the release APK — the same marker-only signal the Expo gate uses, proving @@ -209,15 +209,14 @@ than through a published `appduct` binary, matching how the playground's own ## Native publishing -`deploy.yaml` publishes three npm packages, and — since the CocoaPods wiring landed — the two iOS -distribution channels as well. All of it hangs off one GitHub release. +`deploy.yaml` publishes every channel Appduct ships on, all off one GitHub release. | Channel | Published by | Version comes from | | --- | --- | --- | | npm (`appduct`, `@appduct/shared`, `@appduct/react-native`) | `publish-*` jobs, OIDC trusted publishing | each `package.json` | | SwiftPM (`AppductCore`) | the release's **git tag** — nothing else to do | the tag; `Package.swift` has no version field | | CocoaPods trunk (`AppductCore`) | `publish-cocoapods` job | `packages/react-native/package.json`, read by the podspec | -| Maven Central (`…appduct:core`, `:core-noop`) | **nobody yet — ops task** | `ext.appductVersion`, read from the same `package.json` | +| Maven Central (`com.callstack.appduct:core`, `:core-noop`) | `publish-maven` job (staged; **the final Publish is manual**) | `ext.appductVersion`, read from the same `package.json` | The podspec lives at the **repo root**, next to `Package.swift`, for the same reason that manifest does: CocoaPods resolves a trunk pod's file patterns against the root of the cloned repository, not @@ -242,6 +241,75 @@ target macOS, where `#if canImport(UIKit)` is false and the UIKit-backed code is entirely. Note the iOS gate does not cover **tvOS**, which the podspec also declares and trunk also validates. +### Maven Central + +Coordinates are `com.callstack.appduct:core` and `:core-noop`, from the reversed `callstack.com` +domain that Central verifies the namespace against. + +**One namespace, everywhere.** The Maven group, the Kotlin package, the AGP namespace, the +`AndroidManifest.xml` meta-data keys, and the native playground's application id all read +`com.callstack.appduct*`. They are technically independent knobs, and for a while the Maven group +and the Kotlin package disagreed — that is precisely the state worth avoiding, because the one +place they are coupled is invisible: `appduct doctor`'s Android detection keys on the **dex package +name**, so a change to the Kotlin package silently changes what the release gate inspects. + +That coupling is why `artifact-inspect.ts` matches every Android marker against both +`com.callstack.*` and the legacy `com.callstackincubator.*` spelling that shipped up to 0.9.0. +`doctor` inspects artifacts it did not build, so an app compiled against an older Appduct must +still be detected — reporting `absent` for an app that genuinely bundles Appduct would rubber-stamp +shipping it to production, the one failure this tool must never have. The legacy entries are +load-bearing for as long as any pre-rename artifact can still be inspected, and a regression test +in `artifact-inspect.test.ts` fails if they are dropped. + +The GitHub organisation is still `callstackincubator`, so repository URLs are unchanged; only the +namespace moved. Task documents under `docs/tasks/` keep the old spelling as dated records. + +**No third-party publishing plugin.** Sonatype ships no official Gradle plugin for the Central +Portal, and the community alternatives mostly automate what `publishing.gradle` already does in +~40 lines: stage a signed, checksummed repository layout on disk. `publish-maven` zips that +directory and POSTs it to the Portal API. Keeping it first-party keeps the Android build classpath +to AGP and Kotlin. + +Publishing to a *file* repository rather than a remote one is the trick that makes this work: the +Portal takes a single zipped bundle, not Maven-protocol uploads, and a file repository is what +produces the exact layout that bundle needs — full group path, plus the `.md5`/`.sha1` checksums +Central requires on every file, written by Gradle rather than by hand. Both modules stage into one +`rootProject` directory so a single zip covers the release. `maven-metadata.xml` is excluded from +the zip: Central generates its own. + +**Signing is conditional by design.** `publishing.gradle` wires the `signing` plugin only when +`APPDUCT_SIGNING_KEY` is present, so `test.yaml`'s `publishToMavenLocal` keeps working on an +unkeyed machine. Central rejects any unsigned file, so that same leniency could let the job upload +an unsigned bundle and fail deep inside Central's validator — `publish-maven` therefore asserts +every staged artifact has a `.asc` beside it before zipping. + +**The final Publish is manual.** The job uploads with `publishingType=USER_MANAGED`, so Central +validates the bundle and then waits for a human to press Publish in the Portal UI. A Central +version can never be replaced or deleted, so the last irreversible step stays deliberate until this +path has proven itself; switching to `AUTOMATIC` later is a one-word change. The job polls the +status endpoint and fails on `FAILED`, so a rejected bundle still breaks the release loudly. + +**Re-run safe, in two layers.** Before staging anything, `publish-maven` checks whether the +version is already on Central (repo1.maven.org, OR the Portal's `published` endpoint on an explicit +`"published": true`) and skips if so. That alone does not cover the likeliest re-run, though: an +upload that succeeded, followed by the status poll timing out. That deployment is validated but +*unpublished*, so every "is it published?" check says no — and the Portal API has no endpoint to +list deployments, so its id is the only handle on it. The job therefore saves the id as a workflow +artifact **immediately after uploading, before polling**, and a re-run attempt restores it and +resumes watching that deployment instead of uploading a duplicate. Only live states (`PENDING`, +`VALIDATING`, `VALIDATED`, `PUBLISHING`, `PUBLISHED`) are resumed; a `FAILED` or dropped deployment, +or any unparseable answer, falls through to a fresh upload. Both guards lean the same way: a wrong +"skip" would end a release green with nothing on Central, while a wrong "upload" at worst leaves a +duplicate for a human to drop. + +The artifact is scoped to one workflow run, so this covers **Re-run failed jobs**, not a brand-new +run for the same version; that case still hits the published check, or at worst a duplicate +deployment Central refuses to publish. + +Secrets, in a `maven-central` environment: `CENTRAL_TOKEN_USERNAME` / `CENTRAL_TOKEN_PASSWORD` +(a Portal user token) and `APPDUCT_SIGNING_KEY` / `APPDUCT_SIGNING_PASSWORD` (an armored private +key, used in memory — never written to the runner's disk). + ## Release policy Publish production releases only for now. The deployment workflow must reject prerelease versions and publish every diff --git a/docs/SECURITY.md b/docs/SECURITY.md index 5d592e5..7e7dad3 100644 --- a/docs/SECURITY.md +++ b/docs/SECURITY.md @@ -182,9 +182,9 @@ Android `` meta-data: | Name | Purpose | | --- | ------- | -| `com.callstackincubator.appduct.CLI_PINS` | JSON array string of pin values | -| `com.callstackincubator.appduct.TRUST` | `"link"` \| `"pin"` — any other value is a hard error at connect time | -| `com.callstackincubator.appduct.ALLOW_PRIVATE_LAN_ONLY` | Boolean meta-data value (a `"true"`/`"false"` String is also accepted); defaults to `true` (fail-closed) when absent | +| `com.callstack.appduct.CLI_PINS` | JSON array string of pin values | +| `com.callstack.appduct.TRUST` | `"link"` \| `"pin"` — any other value is a hard error at connect time | +| `com.callstack.appduct.ALLOW_PRIVATE_LAN_ONLY` | Boolean meta-data value (a `"true"`/`"false"` String is also accepted); defaults to `true` (fail-closed) when absent | Wire **deep links** so the OS can open your app with the host's bootstrap URL, and make sure the app scheme matches the one `appduct link` (or the `deepLinkScheme` plugin @@ -201,7 +201,7 @@ worked example and `Appduct.shared.buildConfig`, the plain-app equivalent of `getAppductBuildConfig()`. A plain Android app consuming `packages/native` directly (the `Appduct` object) sets the same -`` meta-data keys — `com.callstackincubator.appduct.CLI_PINS`, `.TRUST`, +`` meta-data keys — `com.callstack.appduct.CLI_PINS`, `.TRUST`, `.ALLOW_PRIVATE_LAN_ONLY` — from the table above, read by the same `resolveTrustedPins`-equivalent logic the RN bridge's `connect()` uses. See [`packages/native/android/README.md`](../packages/native/android/README.md#5-hardened-builds) for diff --git a/docs/internal/native-core.md b/docs/internal/native-core.md index 0b319f0..3b51c20 100644 --- a/docs/internal/native-core.md +++ b/docs/internal/native-core.md @@ -18,13 +18,14 @@ over resolved pnpm workspace packages, never sees it either. | --- | --- | --- | | `@appduct/react-native` | **Vendored by copy**, not a dependency — `packages/react-native/scripts/sync-native-core.mjs` copies source files in at build/publish time (see below) | [`../../packages/react-native/README.md`](../../packages/react-native/README.md) | | A plain iOS app | SwiftPM (`.package(url:)` against the repo-root `Package.swift`) or CocoaPods (the repo-root `AppductCore.podspec`) | [`../../packages/native/ios/README.md`](../../packages/native/ios/README.md) | -| A plain Android app | Maven (`com.callstackincubator.appduct:core`/`:core-noop`) | [`../../packages/native/android/README.md`](../../packages/native/android/README.md) | +| A plain Android app | Maven (`com.callstack.appduct:core`/`:core-noop`) | [`../../packages/native/android/README.md`](../../packages/native/android/README.md) | CocoaPods trunk and the SwiftPM tag are published by `deploy.yaml`'s `publish-cocoapods` job, which runs after the npm publishes on every GitHub release (`docs/CI.md`); the SwiftPM "publish" is the release's git tag itself, since `Package.swift` carries no version of its own. **Maven Central -is still an ops task** — `core`/`core-noop` have `maven-publish` wiring but no repository, signing, -or credentials — see [`../tasks/21-native-core-integration.md`](../tasks/21-native-core-integration.md). +is published by `deploy.yaml`'s `publish-maven` job**, which stages a signed bundle and uploads it +to the Central Portal for validation; the final Publish is a deliberate manual step in the Portal +UI. See [`../CI.md`](../CI.md#maven-central). `playground-native/android`'s `settings.gradle` and `playground-native/ios`'s `project.yml` both build against this worktree's own sources directly (Gradle `includeBuild` substitution, a local SwiftPM package path respectively) — no publish-then-consume round trip needed for local @@ -59,7 +60,7 @@ decisions (why `Appduct.shared` starts `restoreSession()` on first access, the A `AppductClient` instance and the one process-memory resume lease that comes with it; the RN bridge (`AppductTurboBridge.swift`/`NativeAppductModule.kt`) owns a *separate* `AppductClient` of its own. A React Native app that imported `AppductCore`/ -`com.callstackincubator.appduct:core` directly and called the facade alongside the RN bridge +`com.callstack.appduct:core` directly and called the facade alongside the RN bridge would end up with two independent clients racing for the same lease and the same deep link — which is also why the facade files (`AppductAPI.swift` on iOS; `Appduct.kt`, `AppductInitProvider.kt`, `AppductLinkActivity.kt` on Android) are excluded from vendoring @@ -94,7 +95,7 @@ dependencies (and therefore only lets another project depend on this repo via ## How `@appduct/react-native` vendors this -The RN package does **not** depend on `AppductCore`/`com.callstackincubator.appduct:core` as +The RN package does **not** depend on `AppductCore`/`com.callstack.appduct:core` as a CocoaPods/Gradle dependency. Publishing this core independently to CocoaPods trunk and Maven Central is deferred (see the table above); until then the RN package's own releases would be blocked on unrelated native-core publishing infrastructure if it depended on published core diff --git a/docs/tasks/00-overview.md b/docs/tasks/00-overview.md index c63c29d..a435e1d 100644 --- a/docs/tasks/00-overview.md +++ b/docs/tasks/00-overview.md @@ -100,9 +100,9 @@ hand-adding the pod is a combination only a maintainer would use. Task 09 should | iOS `Info.plist` | `AppductCliPins` | array of `sha256/...` | | iOS `Info.plist` | `AppductTrust` | `"link"` \| `"pin"` | | iOS `Info.plist` | `AppductAllowPrivateLanOnly` | Boolean | -| Android meta-data | `com.callstackincubator.appduct.CLI_PINS` | JSON array string | -| Android meta-data | `com.callstackincubator.appduct.TRUST` | `"link"` \| `"pin"` | -| Android meta-data | `com.callstackincubator.appduct.ALLOW_PRIVATE_LAN_ONLY` | Boolean | +| Android meta-data | `com.callstack.appduct.CLI_PINS` | JSON array string | +| Android meta-data | `com.callstack.appduct.TRUST` | `"link"` \| `"pin"` | +| Android meta-data | `com.callstack.appduct.ALLOW_PRIVATE_LAN_ONLY` | Boolean | ### Deleted outright diff --git a/docs/tasks/05-explicit-trust-mode.md b/docs/tasks/05-explicit-trust-mode.md index ed0d114..2e05b7e 100644 --- a/docs/tasks/05-explicit-trust-mode.md +++ b/docs/tasks/05-explicit-trust-mode.md @@ -23,7 +23,7 @@ moves from "we guessed from the build type" to "someone wrote it down". ## Scope -Read `AppductTrust` (iOS `Info.plist`) / `com.callstackincubator.appduct.TRUST` +Read `AppductTrust` (iOS `Info.plist`) / `com.callstack.appduct.TRUST` (Android meta-data) alongside the existing pins keys. Values: `"link"` | `"pin"`. Rewrite `resolveTrustedPins` on both platforms to take `(trust, embeddedPins, linkPin)`: diff --git a/docs/tasks/08-appduct-doctor.md b/docs/tasks/08-appduct-doctor.md index 54b3632..59779c9 100644 --- a/docs/tasks/08-appduct-doctor.md +++ b/docs/tasks/08-appduct-doctor.md @@ -29,7 +29,7 @@ appduct doctor [--assert-absent | --assert-present] [--json] present/absent, and — when present and cheaply readable — the configured trust mode from `Info.plist` / the manifest. - Detection: the Objective-C class name (`RCTNativeAppduct`) and the Swift/pod symbols on - iOS; the `com.callstackincubator.appduct` package/classes in the dex on Android. Prefer + iOS; the `com.callstack.appduct` package/classes in the dex on Android. Prefer tools already available where the artifact is built (`unzip`, `strings`, `nm`, `aapt2`/`dexdump`) and degrade with a clear message rather than a wrong answer when a tool is missing. **Never report "absent" because a tool was unavailable** — that failure mode diff --git a/docs/tasks/10-android-detection-keep-rule.md b/docs/tasks/10-android-detection-keep-rule.md index 14611ba..131f240 100644 --- a/docs/tasks/10-android-detection-keep-rule.md +++ b/docs/tasks/10-android-detection-keep-rule.md @@ -9,7 +9,7 @@ the Android half of `packages/appduct/src/artifact-inspect.ts`.** ## Why -Task 08 shipped two Android signals: the `com.callstackincubator.appduct` package string in +Task 08 shipped two Android signals: the `com.callstack.appduct` package string in the dex, and the plugin-authored manifest meta-data. Its reviewer found a combination that evades both — **bare RN + R8 with no keep rule and no config plugin** — because R8 can rename the dex package and there are no plugin-written manifest keys to fall back on. diff --git a/docs/tasks/14-native-core-extraction.md b/docs/tasks/14-native-core-extraction.md index 20350bc..df89f4a 100644 --- a/docs/tasks/14-native-core-extraction.md +++ b/docs/tasks/14-native-core-extraction.md @@ -54,7 +54,7 @@ Package.swift # repo root -- SwiftPM requires it there ## Decisions and why **Vendoring, not a dependency.** `@appduct/react-native` does not depend on -`AppductCore`/`com.callstackincubator.appduct:core` as a CocoaPods/Gradle dependency. +`AppductCore`/`com.callstack.appduct:core` as a CocoaPods/Gradle dependency. `packages/react-native/scripts/sync-native-core.mjs`, wired into this package's `build` and `prepack` scripts, copies the sources in instead. Publishing `packages/native` independently to CocoaPods trunk and Maven Central is deferred to Phase 3 of issue #48 — until that exists, @@ -100,13 +100,13 @@ the unchanged `AppductConnectionManagerTest.kt` test suite (which never called ` directly, only the pure functions) continuing to pass unmodified. **Android package name unchanged; AAR namespace changed.** The issue allows either keeping the -existing Kotlin package (`com.callstackincubator.appduct`) for the moved classes or moving +existing Kotlin package (`com.callstack.appduct`) for the moved classes or moving them under a `.core` subpackage, calling the former "acceptable and lower-risk." Taken: touching every import across the moved files and the still-RN-package bridge that references them by simple name is unnecessary risk for a mechanical move. The AAR **namespace** (a separate AGP concept — where a module's own generated `R`/`BuildConfig` land, not the Kotlin package its -classes declare) is `com.callstackincubator.appduct.core` for both `core` and `core-noop`, -distinct from `@appduct/react-native`'s own `com.callstackincubator.appduct` namespace — +classes declare) is `com.callstack.appduct.core` for both `core` and `core-noop`, +distinct from `@appduct/react-native`'s own `com.callstack.appduct` namespace — required since a consumer app links the RN module and one of `core`/`core-noop` into the same build. `core` and `core-noop` share a namespace with each other safely, since exactly one is ever on a given variant's classpath (`debugImplementation`-equivalent vs. diff --git a/docs/tasks/17-conformance-fixtures.md b/docs/tasks/17-conformance-fixtures.md index c1016a4..2a7517c 100644 --- a/docs/tasks/17-conformance-fixtures.md +++ b/docs/tasks/17-conformance-fixtures.md @@ -52,7 +52,7 @@ Consumers, one per language: | vitest (`@appduct/shared`) | `packages/shared/src/__tests__/fixtures-conformance.test.ts` | | vitest (`@appduct/appduct` CLI, SPKI only) | `packages/appduct/src/__tests__/spki-pin.test.ts` (extended) | | XCTest (`AppductCore`) | `packages/native/ios/Tests/AppductCoreTests/FixturesConformanceTests.swift` | -| JUnit (`packages/native/android/core`) | `packages/native/android/core/src/test/java/com/callstackincubator/appduct/FixturesConformanceTest.kt` | +| JUnit (`packages/native/android/core`) | `packages/native/android/core/src/test/java/com/callstack/appduct/FixturesConformanceTest.kt` | Where a hand-written test already duplicated a vector now covered by a fixture (the SPKI certificate/pin pair in the Swift and Kotlin suites), the duplicate assertion was removed in favor diff --git a/docs/tasks/19-android-entry-points.md b/docs/tasks/19-android-entry-points.md index 98127bd..ac4598b 100644 --- a/docs/tasks/19-android-entry-points.md +++ b/docs/tasks/19-android-entry-points.md @@ -176,15 +176,15 @@ Kotlin/Java sources, no hand-wired `Jar` task needed): ./gradlew :core:publishToMavenLocal :core-noop:publishToMavenLocal ``` -Verified against `~/.m2/repository/com/callstackincubator/appduct/`: both `core/0.8.0/` and +Verified against `~/.m2/repository/com/callstack/appduct/`: both `core/0.8.0/` and `core-noop/0.8.0/` contain `.aar`, `-sources.jar`, `.module`, and `.pom`. No signing, no Central upload -- deferred as an ops task per the brief. ## 7. Native playground (`playground-native/android`) -A single-module Compose app, `com.callstackincubator.appduct.playground`. `settings.gradle` +A single-module Compose app, `com.callstack.appduct.playground`. `settings.gradle` uses `includeBuild("../../packages/native/android")` with dependency substitution for -`com.callstackincubator.appduct:core`/`:core-noop`, so the playground always builds against +`com.callstack.appduct:core`/`:core-noop`, so the playground always builds against this worktree's `packages/native/android`, never a published artifact, with no publish-then-consume round trip during development. `app/build.gradle` pairs `debugImplementation(core)` / `releaseImplementation(core-noop)`, matching the issue's sketch exactly, and sets @@ -225,7 +225,7 @@ key output, run from the repo root unless noted: ```bash export APPDUCT_STATE_DIR=/tmp/appduct-3b-state # config.json: {"wssPort": 8456} adb -s emulator-5554 install -r playground-native/android/app/build/outputs/apk/debug/app-debug.apk -adb -s emulator-5554 shell am start -n com.callstackincubator.appduct.playground/.MainActivity +adb -s emulator-5554 shell am start -n com.callstack.appduct.playground/.MainActivity node packages/appduct/dist/bin.js link --open android --scheme appduct-native --device emulator-5554 # -> Link Created, Delivered yes (android) diff --git a/packages/appduct/src/__tests__/artifact-inspect.test.ts b/packages/appduct/src/__tests__/artifact-inspect.test.ts index a5efea1..222c678 100644 --- a/packages/appduct/src/__tests__/artifact-inspect.test.ts +++ b/packages/appduct/src/__tests__/artifact-inspect.test.ts @@ -142,7 +142,7 @@ describe("artifact-inspect: Android .apk (synthetic zip fixture)", () => { await buildZipFixture(apkPath, { "classes.dex": Buffer.concat([ Buffer.from("dex\n035\0"), - Buffer.from("Lcom/callstackincubator/appduct/AppductPackage;"), + Buffer.from("Lcom/callstack/appduct/AppductPackage;"), ]), "AndroidManifest.xml": "binary-axml-placeholder-no-appduct-keys", }); @@ -162,8 +162,8 @@ describe("artifact-inspect: Android .apk (synthetic zip fixture)", () => { await buildZipFixture(apkPath, { "classes.dex": Buffer.concat([ Buffer.from("dex\n035\0"), - Buffer.from("Lcom/callstackincubator/appduct/AppductPackage;"), - Buffer.from("Lcom/callstackincubator/appduct/AppductNativeMarker;"), + Buffer.from("Lcom/callstack/appduct/AppductPackage;"), + Buffer.from("Lcom/callstack/appduct/AppductNativeMarker;"), ]), "AndroidManifest.xml": "binary-axml-placeholder-no-appduct-keys", }); @@ -187,7 +187,7 @@ describe("artifact-inspect: Android .apk (synthetic zip fixture)", () => { // for AppductNativeMarker held: its fully-qualified dex type descriptor survives verbatim. "classes.dex": Buffer.concat([ Buffer.from("dex\n035\0"), - Buffer.from("Lcom/callstackincubator/appduct/AppductNativeMarker;"), + Buffer.from("Lcom/callstack/appduct/AppductNativeMarker;"), ]), "AndroidManifest.xml": "no appduct keys in here (bare-RN app, no config plugin)", }); @@ -203,11 +203,11 @@ describe("artifact-inspect: Android .apk (synthetic zip fixture)", () => { const apkPath = path.join(root, "manifest-only.apk"); await buildZipFixture(apkPath, { - // Simulates a release build where R8 renamed the dex package (no "callstackincubator" string + // Simulates a release build where R8 renamed the dex package (no "callstack" string // left in classes.dex) but the config-plugin-authored manifest meta-data keys survive, since // they're XML attribute string data, not a code symbol R8 can rename. "classes.dex": Buffer.from("dex\n035\0La/b/c;"), - "AndroidManifest.xml": Buffer.from("com.callstackincubator.appduct.CLI_PINS", "utf16le"), + "AndroidManifest.xml": Buffer.from("com.callstack.appduct.CLI_PINS", "utf16le"), }); const result = await inspectArtifact(apkPath); @@ -217,6 +217,26 @@ describe("artifact-inspect: Android .apk (synthetic zip fixture)", () => { expect(result.signals).not.toContain("android-dex-package-symbol"); }); + test("an artifact built before the com.callstackincubator -> com.callstack rename is still detected -- a release gate must never report a bundled Appduct as absent", async () => { + const root = await withFixtureRoot(); + const apkPath = path.join(root, "legacy-namespace.apk"); + + await buildZipFixture(apkPath, { + // Exactly what Appduct <= 0.9.0 shipped: the marker class under the pre-rename package. + "classes.dex": Buffer.concat([ + Buffer.from("dex\n035\0"), + Buffer.from("Lcom/callstackincubator/appduct/AppductNativeMarker;"), + ]), + "AndroidManifest.xml": Buffer.from("com.callstackincubator.appduct.CLI_PINS", "utf16le"), + }); + + const result = await inspectArtifact(apkPath); + + expect(result.present).toBe(true); + expect(result.signals).toContain("android-keep-rule-marker"); + expect(result.signals).toContain("android-dex-package-symbol"); + expect(result.signals).toContain("android-manifest-meta-data-keys"); + }); test("excluded: reports absent with no signals", async () => { const root = await withFixtureRoot(); const apkPath = path.join(root, "excluded.apk"); @@ -241,7 +261,7 @@ describe("artifact-inspect: Android .aab (bonus format coverage)", () => { await buildZipFixture(aabPath, { "base/dex/classes.dex": Buffer.from( - "Lcom/callstackincubator/appduct/AppductPackage;Lcom/callstackincubator/appduct/AppductNativeMarker;", + "Lcom/callstack/appduct/AppductPackage;Lcom/callstack/appduct/AppductNativeMarker;", ), "base/manifest/AndroidManifest.xml": "no keys here", }); diff --git a/packages/appduct/src/__tests__/doctor-cli.integration.test.ts b/packages/appduct/src/__tests__/doctor-cli.integration.test.ts index 6c8dbf5..6f236f2 100644 --- a/packages/appduct/src/__tests__/doctor-cli.integration.test.ts +++ b/packages/appduct/src/__tests__/doctor-cli.integration.test.ts @@ -31,7 +31,7 @@ describe("appduct doctor: CLI wiring", () => { const root = await withFixtureRoot(); const apkPath = path.join(root, "included.apk"); await buildZipFixture(apkPath, { - "classes.dex": "Lcom/callstackincubator/appduct/X;Lcom/callstackincubator/appduct/AppductNativeMarker;", + "classes.dex": "Lcom/callstack/appduct/X;Lcom/callstack/appduct/AppductNativeMarker;", "AndroidManifest.xml": "placeholder manifest", }); @@ -46,7 +46,7 @@ describe("appduct doctor: CLI wiring", () => { const root = await withFixtureRoot(); const apkPath = path.join(root, "included.apk"); await buildZipFixture(apkPath, { - "classes.dex": "Lcom/callstackincubator/appduct/X;Lcom/callstackincubator/appduct/AppductNativeMarker;", + "classes.dex": "Lcom/callstack/appduct/X;Lcom/callstack/appduct/AppductNativeMarker;", "AndroidManifest.xml": "placeholder manifest", }); @@ -59,7 +59,7 @@ describe("appduct doctor: CLI wiring", () => { const root = await withFixtureRoot(); const apkPath = path.join(root, "included.apk"); await buildZipFixture(apkPath, { - "classes.dex": "Lcom/callstackincubator/appduct/X;Lcom/callstackincubator/appduct/AppductNativeMarker;", + "classes.dex": "Lcom/callstack/appduct/X;Lcom/callstack/appduct/AppductNativeMarker;", "AndroidManifest.xml": "placeholder manifest", }); @@ -125,7 +125,7 @@ describe("appduct doctor: CLI wiring", () => { const root = await withFixtureRoot(); const apkPath = path.join(root, "included.apk"); await buildZipFixture(apkPath, { - "classes.dex": "Lcom/callstackincubator/appduct/X;Lcom/callstackincubator/appduct/AppductNativeMarker;", + "classes.dex": "Lcom/callstack/appduct/X;Lcom/callstack/appduct/AppductNativeMarker;", "AndroidManifest.xml": "placeholder manifest", }); diff --git a/packages/appduct/src/__tests__/doctor.test.ts b/packages/appduct/src/__tests__/doctor.test.ts index b51cd50..bfcbc5f 100644 --- a/packages/appduct/src/__tests__/doctor.test.ts +++ b/packages/appduct/src/__tests__/doctor.test.ts @@ -26,7 +26,7 @@ describe("doctor command", () => { const root = await withFixtureRoot(); const apkPath = path.join(root, "included.apk"); await buildZipFixture(apkPath, { - "classes.dex": "Lcom/callstackincubator/appduct/X;Lcom/callstackincubator/appduct/AppductNativeMarker;", + "classes.dex": "Lcom/callstack/appduct/X;Lcom/callstack/appduct/AppductNativeMarker;", "AndroidManifest.xml": "placeholder manifest", }); @@ -43,7 +43,7 @@ describe("doctor command", () => { const root = await withFixtureRoot(); const apkPath = path.join(root, "included.apk"); await buildZipFixture(apkPath, { - "classes.dex": "Lcom/callstackincubator/appduct/X;Lcom/callstackincubator/appduct/AppductNativeMarker;", + "classes.dex": "Lcom/callstack/appduct/X;Lcom/callstack/appduct/AppductNativeMarker;", "AndroidManifest.xml": "placeholder manifest", }); @@ -85,7 +85,7 @@ describe("doctor command", () => { const root = await withFixtureRoot(); const apkPath = path.join(root, "included.apk"); await buildZipFixture(apkPath, { - "classes.dex": "Lcom/callstackincubator/appduct/X;Lcom/callstackincubator/appduct/AppductNativeMarker;", + "classes.dex": "Lcom/callstack/appduct/X;Lcom/callstack/appduct/AppductNativeMarker;", "AndroidManifest.xml": "placeholder manifest", }); diff --git a/packages/appduct/src/artifact-inspect.ts b/packages/appduct/src/artifact-inspect.ts index 5c2c072..1836ba0 100644 --- a/packages/appduct/src/artifact-inspect.ts +++ b/packages/appduct/src/artifact-inspect.ts @@ -31,7 +31,7 @@ * future stub could ship them) without the real implementation being present, exactly the reason * the Android signals below aren't all treated as equally authoritative either. * - Android: the primary signal is `AppductNativeMarker` - * (`packages/native/android/core/src/main/java/com/callstackincubator/appduct/AppductNativeMarker.kt`, + * (`packages/native/android/core/src/main/java/com/callstack/appduct/AppductNativeMarker.kt`, * vendored into `@appduct/react-native` at `android/core/src/main/java/...`), a marker class with no * other purpose. Its fully-qualified name is kept unminified and unremoved by a `-keep` rule in * `consumer-rules.pro` (same vendoring path), shipped to every consuming app via `consumerProguardFiles` @@ -41,10 +41,12 @@ * minification in every supported consumer setup. * * Two more signals are kept as fallbacks for artifacts built before this marker existed: the - * `com.callstackincubator.appduct` package string in the dex string pool (survives as long as + * `com.callstack.appduct` package string in the dex string pool (and its pre-rename spelling, + * `com.callstackincubator.appduct` — every Android marker below is matched against both, so an + * app built against Appduct 0.9.0 or earlier is still detected) (survives as long as * R8/ProGuard minification+obfuscation isn't applied to it — an aggressive release config with no keep * rule for this package can rename it away) and the plugin-authored meta-data key names in - * `AndroidManifest.xml` (`com.callstackincubator.appduct.CLI_PINS`/`TRUST`/`ALLOW_PRIVATE_LAN_ONLY`): + * `AndroidManifest.xml` (`com.callstack.appduct.CLI_PINS`/`TRUST`/`ALLOW_PRIVATE_LAN_ONLY`): * those are XML attribute string values written by the config plugin at prebuild time, not compiled * identifiers, so R8 never touches them, but they only exist at all if the config plugin ran. Both * encodings AAPT2 can choose for the manifest string pool (UTF-8 or UTF-16LE) are checked. @@ -113,16 +115,34 @@ const IOS_OBJC_CLASS_MARKER = "RCTNativeAppduct"; const IOS_INFO_PLIST_KEY_MARKERS = ["AppductCliPins", "AppductTrust", "AppductAllowPrivateLanOnly"]; // Fully-qualified name of `AppductNativeMarker` -// (packages/native/android/core/src/main/java/com/callstackincubator/appduct/AppductNativeMarker.kt, -// vendored into packages/react-native/android/core/src/main/java/com/callstackincubator/appduct/AppductNativeMarker.kt +// (packages/native/android/core/src/main/java/com/callstack/appduct/AppductNativeMarker.kt, +// vendored into packages/react-native/android/core/src/main/java/com/callstack/appduct/AppductNativeMarker.kt // by scripts/sync-native-core.mjs), kept unminified by packages/native/android/core/consumer-rules.pro // (vendored to packages/react-native/android/core/consumer-rules.pro the same way). Checked as a dex // type descriptor (`Lcom/.../AppductNativeMarker;`) — see detectAndroidSignals — which is how the // class's fully-qualified name is actually encoded in classes.dex. -const ANDROID_KEEP_RULE_MARKER_CLASS = "com/callstackincubator/appduct/AppductNativeMarker"; - -const ANDROID_DEX_PACKAGE_MARKERS = ["com/callstackincubator/appduct", "com.callstackincubator.appduct"]; -const ANDROID_MANIFEST_KEY_MARKER = "com.callstackincubator.appduct."; +// +// Each of the three Android markers is matched against BOTH the current `com.callstack.*` namespace +// and the legacy `com.callstackincubator.*` one that every release up to and including 0.9.0 +// shipped. `doctor` inspects artifacts it did not build -- including an app built against an older +// Appduct, which is exactly the case a release gate runs into -- and reporting `absent` for an app +// that genuinely bundles Appduct is the one failure mode this tool must never have: it would +// rubber-stamp shipping Appduct to production. docs/CI.md states the rule ("a broken check must +// fail loudly, not rubber-stamp a release"); the cost of honouring it here is one extra buffer +// scan per signal. The legacy entries are load-bearing and must not be dropped while any artifact +// built before the rename can still be inspected. +const ANDROID_KEEP_RULE_MARKER_CLASSES = [ + "com/callstack/appduct/AppductNativeMarker", + "com/callstackincubator/appduct/AppductNativeMarker", +]; + +const ANDROID_DEX_PACKAGE_MARKERS = [ + "com/callstack/appduct", + "com.callstack.appduct", + "com/callstackincubator/appduct", + "com.callstackincubator.appduct", +]; +const ANDROID_MANIFEST_KEY_MARKERS = ["com.callstack.appduct.", "com.callstackincubator.appduct."]; const bufferIncludesAscii = (haystack: Buffer, needle: string): boolean => { return haystack.includes(Buffer.from(needle, "utf8")); @@ -366,7 +386,9 @@ const detectAndroidSignals = (bytes: Buffer): DetectionSignal[] => { // in every supported consumer setup (see the file-level doc comment). Matched as a dex type // descriptor (`L` + fully-qualified-name-with-slashes + `;`) since that's the actual encoding of // a class name inside classes.dex, not just a loose substring check. - if (bufferIncludesAscii(bytes, `L${ANDROID_KEEP_RULE_MARKER_CLASS};`)) { + if ( + ANDROID_KEEP_RULE_MARKER_CLASSES.some((klass) => bufferIncludesAscii(bytes, `L${klass};`)) + ) { signals.push("android-keep-rule-marker"); } @@ -375,8 +397,9 @@ const detectAndroidSignals = (bytes: Buffer): DetectionSignal[] => { } if ( - bufferIncludesAscii(bytes, ANDROID_MANIFEST_KEY_MARKER) || - bufferIncludesUtf16le(bytes, ANDROID_MANIFEST_KEY_MARKER) + ANDROID_MANIFEST_KEY_MARKERS.some( + (marker) => bufferIncludesAscii(bytes, marker) || bufferIncludesUtf16le(bytes, marker), + ) ) { signals.push("android-manifest-meta-data-keys"); } diff --git a/packages/native/android/README.md b/packages/native/android/README.md index cf6223b..6cf1abc 100644 --- a/packages/native/android/README.md +++ b/packages/native/android/README.md @@ -13,8 +13,8 @@ Two artifacts, same public API, one real, one inert: ```kotlin dependencies { - debugImplementation("com.callstackincubator.appduct:core:") - releaseImplementation("com.callstackincubator.appduct:core-noop:") + debugImplementation("com.callstack.appduct:core:") + releaseImplementation("com.callstack.appduct:core-noop:") } ``` @@ -56,7 +56,7 @@ natural place, since [initialization](#4-initialization-the-init-provider) alrea the time it runs: ```kotlin -import com.callstackincubator.appduct.Appduct +import com.callstack.appduct.Appduct import org.json.JSONObject class MyApp : Application() { @@ -158,10 +158,10 @@ app's own manifest, matching the component by its fully qualified name: @@ -183,9 +183,9 @@ the keys themselves, set as `` on your app's `` tag: | Name | Purpose | | --- | ------- | -| `com.callstackincubator.appduct.CLI_PINS` | JSON array string of `sha256/...` SPKI pins | -| `com.callstackincubator.appduct.TRUST` | `"link"` \| `"pin"` -- any other value is a hard error at connect time | -| `com.callstackincubator.appduct.ALLOW_PRIVATE_LAN_ONLY` | Boolean (a `"true"`/`"false"` string is also accepted); defaults to `true` (fail-closed) when absent | +| `com.callstack.appduct.CLI_PINS` | JSON array string of `sha256/...` SPKI pins | +| `com.callstack.appduct.TRUST` | `"link"` \| `"pin"` -- any other value is a hard error at connect time | +| `com.callstack.appduct.ALLOW_PRIVATE_LAN_ONLY` | Boolean (a `"true"`/`"false"` string is also accepted); defaults to `true` (fail-closed) when absent | With no keys set, `trust: "link"` is the default: the app trusts whichever SPKI pin a delivered bootstrap link carries, for that one session only -- zero setup, appropriate for local @@ -212,7 +212,7 @@ appduct doctor path/to/app-debug.apk --assert-present `doctor`'s Android detection trusts only a keep-rule-protected marker class (`AppductNativeMarker`, compiled only into `core`, never `core-noop`) to decide presence -- -not just "is the `com.callstackincubator.appduct` package name anywhere in the dex", since +not just "is the `com.callstack.appduct` package name anywhere in the dex", since `core-noop`'s classes share that same Kotlin package and would otherwise look present to a naive scan. See [`docs/BUILD-VARIANTS.md`](../../../docs/BUILD-VARIANTS.md) and [`docs/CI.md`](../../../docs/CI.md#release-gate-appduct-doctor) for the full mechanism. diff --git a/packages/native/android/core-noop/build.gradle b/packages/native/android/core-noop/build.gradle index 7dd1293..5b17736 100644 --- a/packages/native/android/core-noop/build.gradle +++ b/packages/native/android/core-noop/build.gradle @@ -1,14 +1,13 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' -apply plugin: 'maven-publish' -group = 'com.callstackincubator.appduct' +group = 'com.callstack.appduct' version = rootProject.ext.appductVersion android { // Same namespace as `core` -- see that module's build.gradle for why this is safe: the two are // never linked into the same build variant at once. - namespace 'com.callstackincubator.appduct.core' + namespace 'com.callstack.appduct.core' compileSdk rootProject.ext.compileSdkVersion defaultConfig { @@ -36,6 +35,8 @@ android { publishing { singleVariant('release') { withSourcesJar() + // Required by Central, same as `core`. + withJavadocJar() } } } @@ -45,15 +46,9 @@ android { // artifact must not pull in the real transport dependency graph. dependencies {} -afterEvaluate { - publishing { - publications { - release(MavenPublication) { - from components.release - groupId = group - artifactId = 'core-noop' - version = version - } - } - } -} +// See core/build.gradle -- publishing.gradle holds the shared POM/signing/bundle wiring. +ext.appductArtifactId = 'core-noop' +ext.appductArtifactDescription = 'Inert build-variant counterpart to com.callstack.appduct:core: ' + + 'the same public API with every method a no-op, so a release build carries no Appduct ' + + 'implementation and none of its dependencies.' +apply from: "$rootDir/publishing.gradle" diff --git a/packages/native/android/core-noop/src/main/java/com/callstackincubator/appduct/Appduct.kt b/packages/native/android/core-noop/src/main/java/com/callstack/appduct/Appduct.kt similarity index 99% rename from packages/native/android/core-noop/src/main/java/com/callstackincubator/appduct/Appduct.kt rename to packages/native/android/core-noop/src/main/java/com/callstack/appduct/Appduct.kt index f6d033e..eced896 100644 --- a/packages/native/android/core-noop/src/main/java/com/callstackincubator/appduct/Appduct.kt +++ b/packages/native/android/core-noop/src/main/java/com/callstack/appduct/Appduct.kt @@ -1,4 +1,4 @@ -package com.callstackincubator.appduct +package com.callstack.appduct import android.content.Intent import android.net.Uri diff --git a/packages/native/android/core-noop/src/main/java/com/callstackincubator/appduct/AppductClient.kt b/packages/native/android/core-noop/src/main/java/com/callstack/appduct/AppductClient.kt similarity index 98% rename from packages/native/android/core-noop/src/main/java/com/callstackincubator/appduct/AppductClient.kt rename to packages/native/android/core-noop/src/main/java/com/callstack/appduct/AppductClient.kt index 703a45a..ece8fda 100644 --- a/packages/native/android/core-noop/src/main/java/com/callstackincubator/appduct/AppductClient.kt +++ b/packages/native/android/core-noop/src/main/java/com/callstack/appduct/AppductClient.kt @@ -1,4 +1,4 @@ -package com.callstackincubator.appduct +package com.callstack.appduct import android.content.Context diff --git a/packages/native/android/core-noop/src/main/java/com/callstackincubator/appduct/AppductClientTypes.kt b/packages/native/android/core-noop/src/main/java/com/callstack/appduct/AppductClientTypes.kt similarity index 99% rename from packages/native/android/core-noop/src/main/java/com/callstackincubator/appduct/AppductClientTypes.kt rename to packages/native/android/core-noop/src/main/java/com/callstack/appduct/AppductClientTypes.kt index e366bae..77266f8 100644 --- a/packages/native/android/core-noop/src/main/java/com/callstackincubator/appduct/AppductClientTypes.kt +++ b/packages/native/android/core-noop/src/main/java/com/callstack/appduct/AppductClientTypes.kt @@ -1,4 +1,4 @@ -package com.callstackincubator.appduct +package com.callstack.appduct import org.json.JSONObject diff --git a/packages/native/android/core-noop/src/main/java/com/callstackincubator/appduct/AppductConnectionManager.kt b/packages/native/android/core-noop/src/main/java/com/callstack/appduct/AppductConnectionManager.kt similarity index 98% rename from packages/native/android/core-noop/src/main/java/com/callstackincubator/appduct/AppductConnectionManager.kt rename to packages/native/android/core-noop/src/main/java/com/callstack/appduct/AppductConnectionManager.kt index 5f5ed08..b5e7747 100644 --- a/packages/native/android/core-noop/src/main/java/com/callstackincubator/appduct/AppductConnectionManager.kt +++ b/packages/native/android/core-noop/src/main/java/com/callstack/appduct/AppductConnectionManager.kt @@ -1,4 +1,4 @@ -package com.callstackincubator.appduct +package com.callstack.appduct import android.content.Context diff --git a/packages/native/android/core-noop/src/main/java/com/callstackincubator/appduct/AppductTransport.kt b/packages/native/android/core-noop/src/main/java/com/callstack/appduct/AppductTransport.kt similarity index 93% rename from packages/native/android/core-noop/src/main/java/com/callstackincubator/appduct/AppductTransport.kt rename to packages/native/android/core-noop/src/main/java/com/callstack/appduct/AppductTransport.kt index 104c9a4..9afd5b5 100644 --- a/packages/native/android/core-noop/src/main/java/com/callstackincubator/appduct/AppductTransport.kt +++ b/packages/native/android/core-noop/src/main/java/com/callstack/appduct/AppductTransport.kt @@ -1,4 +1,4 @@ -package com.callstackincubator.appduct +package com.callstack.appduct /** No-op mirror of `core`'s `AppductTransport` -- see that module for the real contract. */ internal interface AppductTransport { diff --git a/packages/native/android/core/build.gradle b/packages/native/android/core/build.gradle index e9b911e..338a130 100644 --- a/packages/native/android/core/build.gradle +++ b/packages/native/android/core/build.gradle @@ -1,16 +1,13 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' -apply plugin: 'maven-publish' -group = 'com.callstackincubator.appduct' +group = 'com.callstack.appduct' version = rootProject.ext.appductVersion android { - // Distinct from @appduct/react-native's own AAR namespace ("com.callstackincubator.appduct", - // packages/react-native/android/build.gradle) even though the Kotlin *package* the classes in this - // module live in is left unchanged (docs/tasks/14-native-core-extraction.md: "keeping the existing - // package is acceptable and lower-risk" -- it avoids touching every import across the moved files - // and the RN bridge that references them). The AAR namespace and the Kotlin package are unrelated + // Distinct from @appduct/react-native's own AAR namespace ("com.callstack.appduct", + // packages/react-native/android/build.gradle) despite sharing the `com.callstack.appduct` prefix + // with it and with the Kotlin package. The AAR namespace and the Kotlin package are unrelated // concepts in AGP: this only controls where this module's own generated `R`/`BuildConfig` classes // live, and it must differ from the RN module's own namespace so a debug build that links both // `@appduct/react-native`'s android module *and* this `core` module (which is exactly what @@ -18,7 +15,7 @@ android { // same namespace as `core` -- the two are never linked into the same build variant at once (one // is `debugImplementation`, the other `releaseImplementation`), so they cannot collide with each // other, only with the RN module they're paired with. - namespace 'com.callstackincubator.appduct.core' + namespace 'com.callstack.appduct.core' compileSdk rootProject.ext.compileSdkVersion defaultConfig { @@ -55,6 +52,9 @@ android { publishing { singleVariant('release') { withSourcesJar() + // Central rejects a deployment with no javadoc jar. AGP generates one from the Kotlin/Java + // sources here; for a Kotlin module it is largely a stub, which Central explicitly permits. + withJavadocJar() } } } @@ -99,21 +99,11 @@ androidComponents { } } -// Minimal `maven-publish` wiring for Phase 3 (docs/tasks/14-native-core-extraction.md), when this -// module is actually published to Maven Central rather than only vendored into -// @appduct/react-native by scripts/sync-native-core.mjs. Not exercised by CI yet -- no -// `publishing.repositories` target is configured -- but the coordinates are fixed now so a -// consumer's `debugImplementation("com.callstackincubator.appduct:core:")` (see the -// issue's Phase 3 sketch) is stable ahead of the actual publish. -afterEvaluate { - publishing { - publications { - release(MavenPublication) { - from components.release - groupId = group - artifactId = 'core' - version = version - } - } - } -} +// Maven Central publishing. The POM metadata, signing, and the staged bundle layout all live in +// publishing.gradle, shared with core-noop; these two properties are the only per-module part. +// A consumer declares `debugImplementation("com.callstack.appduct:core:")` -- see +// packages/native/android/README.md. +ext.appductArtifactId = 'core' +ext.appductArtifactDescription = 'Framework-free Android core for Appduct: TLS-pinned session ' + + 'transport, session lifecycle, and tool registry for plain Android apps.' +apply from: "$rootDir/publishing.gradle" diff --git a/packages/native/android/core/consumer-rules.pro b/packages/native/android/core/consumer-rules.pro index 028d814..6957189 100644 --- a/packages/native/android/core/consumer-rules.pro +++ b/packages/native/android/core/consumer-rules.pro @@ -7,6 +7,6 @@ # `appduct doctor` (packages/appduct/src/artifact-inspect.ts, docs/tasks/10-android-detection-keep-rule.md) # has a detection anchor that survives aggressive release minification even in a bare-RN app with # no config plugin and no other Android keep rules. Deliberately scoped to this one marker class, -# not the whole `com.callstackincubator.appduct` package: everything else in this library +# not the whole `com.callstack.appduct` package: everything else in this library # remains free to be renamed and shrunk by R8 as normal. --keep class com.callstackincubator.appduct.AppductNativeMarker { *; } +-keep class com.callstack.appduct.AppductNativeMarker { *; } diff --git a/packages/native/android/core/src/main/AndroidManifest.xml b/packages/native/android/core/src/main/AndroidManifest.xml index ccf6b99..40de908 100644 --- a/packages/native/android/core/src/main/AndroidManifest.xml +++ b/packages/native/android/core/src/main/AndroidManifest.xml @@ -8,19 +8,19 @@ (docs/tasks/19-android-entry-points.md). `${applicationId}` is filled in automatically by AGP: every consumer app gets a uniquely named authority, as the platform requires for a ContentProvider. The name below is fully qualified, not the relative `.AppductInitProvider` - shorthand, because this module's Kotlin package (`com.callstackincubator.appduct`) - differs from its AGP `namespace` (`com.callstackincubator.appduct.core`, see + shorthand, because this module's Kotlin package (`com.callstack.appduct`) + differs from its AGP `namespace` (`com.callstack.appduct.core`, see build.gradle) that relative names resolve against; a relative name here would resolve to a class that doesn't exist and crash at provider-install time. Opt out (an app that wants to drive initialization itself instead) with, in the app's own manifest: - See packages/native/android/README.md, "Opting out of the init provider and trampoline". --> @@ -34,11 +34,11 @@ the same scheme for your own deep links too). Opt out the same way: - --> { const metaData = metaDataOf(manifest); const pins = metaData.find( (item) => - item.$["android:name"] === "com.callstackincubator.appduct.CLI_PINS", + item.$["android:name"] === "com.callstack.appduct.CLI_PINS", ); const privateLan = metaData.find( (item) => item.$["android:name"] === - "com.callstackincubator.appduct.ALLOW_PRIVATE_LAN_ONLY", + "com.callstack.appduct.ALLOW_PRIVATE_LAN_ONLY", ); const trust = metaData.find( (item) => - item.$["android:name"] === "com.callstackincubator.appduct.TRUST", + item.$["android:name"] === "com.callstack.appduct.TRUST", ); expect(pins?.$["android:value"]).toBe(JSON.stringify([VALID_PIN])); @@ -401,7 +401,7 @@ describe("app.plugin.js: applyAndroidManifestChanges", () => { const pins = metaDataOf(manifest).find( (item) => - item.$["android:name"] === "com.callstackincubator.appduct.CLI_PINS", + item.$["android:name"] === "com.callstack.appduct.CLI_PINS", ); expect(pins?.$["android:value"]).toBe(JSON.stringify([])); }); diff --git a/playground-native/android/README.md b/playground-native/android/README.md index 250d95a..37fb993 100644 --- a/playground-native/android/README.md +++ b/playground-native/android/README.md @@ -17,14 +17,14 @@ playground-native/android/ build.gradle debugImplementation(core) / releaseImplementation(core-noop) src/main/ AndroidManifest.xml - java/com/callstackincubator/appduct/playground/ + java/com/callstack/appduct/playground/ PlaygroundApplication.kt registers tools in Application.onCreate() PlaygroundState.kt Compose-observable connection state / event log MainActivity.kt the one screen ``` This is **not** a published-artifact consumer: `settings.gradle`'s `includeBuild` substitutes -`com.callstackincubator.appduct:core`/`:core-noop` with the local Gradle projects from +`com.callstack.appduct:core`/`:core-noop` with the local Gradle projects from `packages/native/android`, so the playground always builds against whatever is in this worktree, with no publish-then-consume round trip. A real consumer app instead depends on the published Maven coordinates -- see `packages/native/android/README.md`. @@ -60,7 +60,7 @@ Install and run on a booted emulator or device: ```bash adb install -r app/build/outputs/apk/debug/app-debug.apk -adb shell am start -n com.callstackincubator.appduct.playground/.MainActivity +adb shell am start -n com.callstack.appduct.playground/.MainActivity ``` Then drive it from the `appduct` CLI, pointed at a daemon whose state directory this build's diff --git a/playground-native/android/app/build.gradle b/playground-native/android/app/build.gradle index 1a495ac..3506be1 100644 --- a/playground-native/android/app/build.gradle +++ b/playground-native/android/app/build.gradle @@ -12,11 +12,11 @@ apply plugin: 'org.jetbrains.kotlin.plugin.compose' def appductVersion = new JsonSlurper().parse(file("$rootDir/../../packages/react-native/package.json")).version android { - namespace 'com.callstackincubator.appduct.playground' + namespace 'com.callstack.appduct.playground' compileSdk rootProject.ext.compileSdkVersion defaultConfig { - applicationId 'com.callstackincubator.appduct.playground' + applicationId 'com.callstack.appduct.playground' minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode 1 @@ -57,8 +57,8 @@ dependencies { // build gets the inert no-op -- exactly the pairing issue #48 phase 3 asks a plain Android app // to declare, resolved here to the local project via settings.gradle's `includeBuild` instead of // a published Maven artifact. - debugImplementation "com.callstackincubator.appduct:core:${appductVersion}" - releaseImplementation "com.callstackincubator.appduct:core-noop:${appductVersion}" + debugImplementation "com.callstack.appduct:core:${appductVersion}" + releaseImplementation "com.callstack.appduct:core-noop:${appductVersion}" implementation platform('androidx.compose:compose-bom:2024.09.00') implementation 'androidx.compose.ui:ui' diff --git a/playground-native/android/app/src/main/java/com/callstackincubator/appduct/playground/MainActivity.kt b/playground-native/android/app/src/main/java/com/callstack/appduct/playground/MainActivity.kt similarity index 97% rename from playground-native/android/app/src/main/java/com/callstackincubator/appduct/playground/MainActivity.kt rename to playground-native/android/app/src/main/java/com/callstack/appduct/playground/MainActivity.kt index 2df76ac..806caa3 100644 --- a/playground-native/android/app/src/main/java/com/callstackincubator/appduct/playground/MainActivity.kt +++ b/playground-native/android/app/src/main/java/com/callstack/appduct/playground/MainActivity.kt @@ -1,4 +1,4 @@ -package com.callstackincubator.appduct.playground +package com.callstack.appduct.playground import android.os.Bundle import androidx.activity.ComponentActivity @@ -20,7 +20,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import com.callstackincubator.appduct.Appduct +import com.callstack.appduct.Appduct import kotlinx.coroutines.launch /** diff --git a/playground-native/android/app/src/main/java/com/callstackincubator/appduct/playground/PlaygroundApplication.kt b/playground-native/android/app/src/main/java/com/callstack/appduct/playground/PlaygroundApplication.kt similarity index 91% rename from playground-native/android/app/src/main/java/com/callstackincubator/appduct/playground/PlaygroundApplication.kt rename to playground-native/android/app/src/main/java/com/callstack/appduct/playground/PlaygroundApplication.kt index 3a8c507..3f10ded 100644 --- a/playground-native/android/app/src/main/java/com/callstackincubator/appduct/playground/PlaygroundApplication.kt +++ b/playground-native/android/app/src/main/java/com/callstack/appduct/playground/PlaygroundApplication.kt @@ -1,22 +1,22 @@ -package com.callstackincubator.appduct.playground +package com.callstack.appduct.playground import android.app.Application -import com.callstackincubator.appduct.Appduct -import com.callstackincubator.appduct.AppductEvent -import com.callstackincubator.appduct.ToolAnnotations +import com.callstack.appduct.Appduct +import com.callstack.appduct.AppductEvent +import com.callstack.appduct.ToolAnnotations import kotlinx.coroutines.delay import org.json.JSONObject /** * Registers the same tool set as the Expo playground (`playground/app/(tabs)/index.tsx`) so the * two can be driven identically from `appduct tools`/`invoke` (docs/tasks/19-android-entry-points.md). - * By the time [onCreate] runs, [com.callstackincubator.appduct.AppductInitProvider] has + * By the time [onCreate] runs, [com.callstack.appduct.AppductInitProvider] has * already captured this process's application `Context` and started lease recovery -- nothing * else needs to happen before [Appduct.register] works. * * On a release build (`releaseImplementation` resolves `core-noop`, see `app/build.gradle`), * every call below still compiles and runs, but does nothing: `Appduct.register` returns an - * inert [com.callstackincubator.appduct.ToolRegistration] and the listener below is never + * inert [com.callstack.appduct.ToolRegistration] and the listener below is never * invoked, matching every other Appduct consumer's compiled-out release behavior * (`docs/BUILD-VARIANTS.md`). */ diff --git a/playground-native/android/app/src/main/java/com/callstackincubator/appduct/playground/PlaygroundState.kt b/playground-native/android/app/src/main/java/com/callstack/appduct/playground/PlaygroundState.kt similarity index 84% rename from playground-native/android/app/src/main/java/com/callstackincubator/appduct/playground/PlaygroundState.kt rename to playground-native/android/app/src/main/java/com/callstack/appduct/playground/PlaygroundState.kt index e769d9e..e5616a9 100644 --- a/playground-native/android/app/src/main/java/com/callstackincubator/appduct/playground/PlaygroundState.kt +++ b/playground-native/android/app/src/main/java/com/callstack/appduct/playground/PlaygroundState.kt @@ -1,17 +1,17 @@ -package com.callstackincubator.appduct.playground +package com.callstack.appduct.playground import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshots.SnapshotStateList -import com.callstackincubator.appduct.ClientState +import com.callstack.appduct.ClientState /** - * In-memory, Compose-observable mirror of what the [com.callstackincubator.appduct.Appduct] + * In-memory, Compose-observable mirror of what the [com.callstack.appduct.Appduct] * facade reports -- connection state, session id, and a capped log of recent events/tool calls -- * so [MainActivity]'s UI updates live without owning a listener itself. Written from - * [PlaygroundApplication]'s single [com.callstackincubator.appduct.Appduct.addListener] + * [PlaygroundApplication]'s single [com.callstack.appduct.Appduct.addListener] * subscription and from the registered tool handlers; read from Composables. */ object PlaygroundState { diff --git a/playground-native/android/settings.gradle b/playground-native/android/settings.gradle index 17d1dca..0a85a9e 100644 --- a/playground-native/android/settings.gradle +++ b/playground-native/android/settings.gradle @@ -8,7 +8,7 @@ pluginManagement { rootProject.name = 'appduct-playground-native' -// Substitutes `com.callstackincubator.appduct:core`/`:core-noop` with the local Gradle +// Substitutes `com.callstack.appduct:core`/`:core-noop` with the local Gradle // projects from packages/native/android instead of resolving a published Maven artifact -- // exactly the composite-build wiring issue #48 phase 3 sketches, and how this playground stays // in lockstep with the native core as it's developed, with no publish-then-consume round trip. @@ -17,8 +17,8 @@ rootProject.name = 'appduct-playground-native' // name above, so the two composite builds' project names never collide). includeBuild("../../packages/native/android") { dependencySubstitution { - substitute(module("com.callstackincubator.appduct:core")).using(project(":core")) - substitute(module("com.callstackincubator.appduct:core-noop")).using(project(":core-noop")) + substitute(module("com.callstack.appduct:core")).using(project(":core")) + substitute(module("com.callstack.appduct:core-noop")).using(project(":core-noop")) } } diff --git a/playground-native/ios/AppductPlayground/Info.plist b/playground-native/ios/AppductPlayground/Info.plist index e36437c..f8e93ed 100644 --- a/playground-native/ios/AppductPlayground/Info.plist +++ b/playground-native/ios/AppductPlayground/Info.plist @@ -25,7 +25,7 @@ CFBundleURLName - com.callstackincubator.appduct.playgroundnative + com.callstack.appduct.playgroundnative CFBundleURLSchemes appduct-native diff --git a/playground-native/ios/README.md b/playground-native/ios/README.md index 1c36e58..dc7bfbf 100644 --- a/playground-native/ios/README.md +++ b/playground-native/ios/README.md @@ -27,7 +27,7 @@ xcodebuild build -project AppductPlayground.xcodeproj -scheme AppductPlayground -destination 'platform=iOS Simulator,name=iPhone 17' CODE_SIGNING_ALLOWED=NO xcrun simctl install booted \ ~/Library/Developer/Xcode/DerivedData/AppductPlayground-*/Build/Products/Debug-iphonesimulator/AppductPlayground.app -xcrun simctl launch booted com.callstackincubator.appduct.playgroundnative +xcrun simctl launch booted com.callstack.appduct.playgroundnative # 3. Point the CLI at it -- the app registers the `appduct-native` URL scheme. appduct link --scheme appduct-native --open ios-sim diff --git a/playground-native/ios/project.yml b/playground-native/ios/project.yml index 3a8c31e..647a150 100644 --- a/playground-native/ios/project.yml +++ b/playground-native/ios/project.yml @@ -1,6 +1,6 @@ name: AppductPlayground options: - bundleIdPrefix: com.callstackincubator.appduct + bundleIdPrefix: com.callstack.appduct deploymentTarget: iOS: "15.1" # xcodegen otherwise fabricates its own scheme with `buildTarget`-derived config choices; naming @@ -33,7 +33,7 @@ targets: product: AppductCore settings: base: - PRODUCT_BUNDLE_IDENTIFIER: com.callstackincubator.appduct.playgroundnative + PRODUCT_BUNDLE_IDENTIFIER: com.callstack.appduct.playgroundnative PRODUCT_NAME: AppductPlayground MARKETING_VERSION: "1.0.0" CURRENT_PROJECT_VERSION: "1"