diff --git a/.github/workflows/secure_nx_release.yml b/.github/workflows/secure_nx_release.yml new file mode 100644 index 00000000..82c6584b --- /dev/null +++ b/.github/workflows/secure_nx_release.yml @@ -0,0 +1,592 @@ +name: Release Workflow + +on: + push: + branches: + - main + tags: + # Matches the Nx releaseTagPattern in nx.json: "{version}-{projectName}" + - '*-*' + workflow_dispatch: + inputs: + version: + description: "Exact version to release (e.g. 6.1.0-rc.0). Overrides release-type/preid; npm dist-tag is derived from it (6.1.0-rc.0 -> rc, 6.1.0 -> latest)" + required: false + type: string + default: "" + release-type: + description: "Version bump when 'version' is empty (patch/minor/major publish to npm 'latest'; prerelease uses 'preid')" + required: false + type: choice + options: + - prerelease + - patch + - minor + - major + default: prerelease + preid: + description: "Prerelease identifier (used only when release-type=prerelease and 'version' is empty; also becomes the npm dist-tag, e.g. next | alpha | beta | rc)" + required: false + type: string + default: next + dry-run: + description: "Run release steps without making changes (no git push, no publish)" + required: false + type: boolean + default: false + release-group: + description: "Optional Nx project pattern to scope the release, e.g. firebase-core or firebase-messaging*,firebase-core (empty = all packages)" + required: false + type: string + default: "" + +concurrency: + # Avoid overlapping publishes on the same ref/branch + group: nx-release-${{ github.ref }} + cancel-in-progress: false + +permissions: + contents: write # needed to push version commits and tags + id-token: write # required for npm provenance / trusted publishing (OIDC) + +jobs: + release: + name: Version and Publish (gated by environment) + # Branch pushes only do work when the repo variable NEXT_PRERELEASE_PROJECT_ALLOWLIST names + # the projects that may auto-publish a `next` prerelease; with it unset, only tags and + # manual dispatches release anything. + if: ${{ github.actor != 'github-actions[bot]' && (github.event_name != 'push' || startsWith(github.ref, 'refs/tags/') || vars.NEXT_PRERELEASE_PROJECT_ALLOWLIST != '') }} + runs-on: ubuntu-latest + environment: + name: ${{ (github.event_name == 'workflow_dispatch' && inputs.dry-run) && 'npm-publish-dry-run' || 'npm-publish' }} + + env: + # Comma-separated Nx project names allowed to auto-publish `next` prereleases on pushes to main. + NEXT_PRERELEASE_PROJECT_ALLOWLIST: ${{ vars.NEXT_PRERELEASE_PROJECT_ALLOWLIST }} + + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit + + - name: Checkout repository (full history for tagging) + uses: actions/checkout@v7.0.1 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v7 + with: + node-version: '24' + registry-url: 'https://registry.npmjs.org' + + - name: Update npm (required for OIDC trusted publishing) + run: | + npm install -g npm@^11.5.1 + npm --version + + # No lockfile is committed (yarn.lock is gitignored), so a frozen install is not possible. + # --ignore-engines mirrors the repo's own `setup` script; Angular's engine range lags Node 24. + - name: Install dependencies + run: yarn install --ignore-engines --non-interactive + + - name: Resolve release context + id: ctx + shell: bash + env: + INPUT_VERSION: ${{ inputs.version }} + INPUT_RELEASE_TYPE: ${{ inputs['release-type'] }} + INPUT_PREID: ${{ inputs.preid }} + INPUT_SCOPE: ${{ inputs['release-group'] }} + INPUT_DRY_RUN: ${{ inputs['dry-run'] }} + run: | + set -euo pipefail + + if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then + version="${INPUT_VERSION}" + release_type="${INPUT_RELEASE_TYPE}" + preid="${INPUT_PREID}" + scope="${INPUT_SCOPE}" + dry_run="${INPUT_DRY_RUN}" + mode="dispatch" + + if [[ -n "$version" ]]; then + specifier="$version" + release_type="" + preid="" + if [[ "$version" == *-* ]]; then + # npm dist-tag is the alphabetic prerelease label (6.1.0-rc.0 -> rc); purely numeric labels fall back to next. + prerelease="${version#*-}" + dist_tag="${prerelease%%.*}" + if [[ "$dist_tag" =~ ^[0-9]+$ ]]; then + dist_tag="next" + fi + else + dist_tag="latest" + fi + else + specifier="$release_type" + # npm dist-tag follows release type: prerelease -> preid, stable -> latest + if [[ "$release_type" == "prerelease" ]]; then + dist_tag="$preid" + else + preid="" + dist_tag="latest" + fi + fi + elif [[ "${GITHUB_REF}" == refs/tags/* ]]; then + specifier="" + release_type="" + preid="" + dist_tag="" + scope="" + dry_run="false" + mode="tag" + else + specifier="prerelease" + release_type="prerelease" + preid="next" + dist_tag="next" + scope="" + dry_run="false" + mode="main" + fi + + echo "mode=${mode}" >> "$GITHUB_OUTPUT" + echo "specifier=${specifier}" >> "$GITHUB_OUTPUT" + echo "release_type=${release_type}" >> "$GITHUB_OUTPUT" + echo "preid=${preid}" >> "$GITHUB_OUTPUT" + echo "dist_tag=${dist_tag}" >> "$GITHUB_OUTPUT" + echo "scope=${scope}" >> "$GITHUB_OUTPUT" + echo "dry_run=${dry_run}" >> "$GITHUB_OUTPUT" + + - name: Determine affected release projects (main) + id: affected + if: ${{ steps.ctx.outputs.mode == 'main' }} + shell: bash + run: | + set -euo pipefail + + if [[ -z "${NEXT_PRERELEASE_PROJECT_ALLOWLIST}" ]]; then + echo "NEXT_PRERELEASE_PROJECT_ALLOWLIST is empty; nothing auto-publishes on main." + echo "projects=" >> "$GITHUB_OUTPUT" + echo "count=0" >> "$GITHUB_OUTPUT" + exit 0 + fi + + base='${{ github.event.before }}' + head='${{ github.sha }}' + + # Handle edge cases where base commit doesn't exist (first push, force-push, etc.) + # Use HEAD~1 as fallback, or just compare against HEAD if no parent exists + if [[ "$base" == "0000000000000000000000000000000000000000" ]] || ! git cat-file -e "$base" 2>/dev/null; then + echo "Base commit not available, falling back to HEAD~1" + base="HEAD~1" + # If HEAD~1 doesn't exist (first commit), use empty tree + if ! git cat-file -e "$base" 2>/dev/null; then + base="$(git hash-object -t tree /dev/null)" + fi + fi + + # Only consider main-branch prerelease libraries allowed for automatic next publishes. + affected_json=$(npx nx show projects --affected --base "$base" --head "$head" --type lib --projects "$NEXT_PRERELEASE_PROJECT_ALLOWLIST" --json) + affected_list=$(printf '%s' "$affected_json" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{const a=JSON.parse(s||"[]");process.stdout.write(a.join(","));});') + affected_count=$(printf '%s' "$affected_json" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{const a=JSON.parse(s||"[]");process.stdout.write(String(a.length));});') + + echo "projects=${affected_list}" >> "$GITHUB_OUTPUT" + echo "count=${affected_count}" >> "$GITHUB_OUTPUT" + + - name: Determine tag release project and dist-tag (tags) + id: taginfo + if: ${{ steps.ctx.outputs.mode == 'tag' }} + shell: bash + run: | + set -euo pipefail + + tag_name="${GITHUB_REF_NAME}" + + # Find the project by matching the tag suffix against known releaseable packages. + projects=$(npx nx show projects --projects "packages/*" --type lib --sep ' ') + + # Longest suffix wins so firebase-app-check-debug is not mistaken for firebase-app-check + # (and firebase-messaging-core for firebase-messaging). + best_match="" + best_len=0 + for p in $projects; do + suffix="-${p}" + if [[ "$tag_name" == *"$suffix" ]]; then + if (( ${#p} > best_len )); then + best_match="$p" + best_len=${#p} + fi + fi + done + + if [[ -z "$best_match" ]]; then + echo "Could not determine project from tag '$tag_name'. Expected '{version}-{projectName}'." >&2 + exit 1 + fi + + version_part="${tag_name%-$best_match}" + if [[ "$version_part" == *-* ]]; then + dist_tag="next" + else + dist_tag="latest" + fi + + echo "project=${best_match}" >> "$GITHUB_OUTPUT" + echo "version=${version_part}" >> "$GITHUB_OUTPUT" + echo "dist_tag=${dist_tag}" >> "$GITHUB_OUTPUT" + + - name: Configure git user for automated commits + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + # VERSION: updates versions and creates git tags following nx.json releaseTagPattern. + - name: nx release version (main) + if: ${{ steps.ctx.outputs.mode == 'main' && steps.affected.outputs.count != '0' }} + shell: bash + run: | + set -euo pipefail + npx nx release version prerelease \ + --preid next \ + --projects "${{ steps.affected.outputs.projects }}" \ + --git-commit \ + --git-push \ + --verbose + + - name: nx release version (main, no-op) + if: ${{ steps.ctx.outputs.mode == 'main' && steps.affected.outputs.count == '0' }} + run: echo "No affected release projects on main; skipping version + publish." + + # The checkout above is pinned to the SHA resolved at dispatch time; by now (install) + # the branch may have moved (e.g. a push-triggered next release), which makes the later + # release-commit push non-fast-forward. Re-sync to the branch tip before versioning. + - name: Sync to latest branch tip (dispatch) + if: ${{ steps.ctx.outputs.mode == 'dispatch' && !inputs.dry-run }} + run: | + git fetch origin "$GITHUB_REF_NAME" + git reset --hard "origin/$GITHUB_REF_NAME" + + # Orchestrated release: bumps versions, generates changelogs, commits + tags in one shot. + # The orchestrator does not accept --git-* flags (config-driven only); push is handled in the next step. + # --skip-publish keeps publishing as a separate step below so the OIDC token-clearing logic still runs. + # A project with no prior release tag gets its changelog from its first commit (release.changelog.automaticFromRef in nx.json). + - name: nx release version + changelog (dispatch) + if: ${{ steps.ctx.outputs.mode == 'dispatch' && !inputs.dry-run }} + shell: bash + run: | + set -euo pipefail + + scope="${{ steps.ctx.outputs.scope }}" + projects_arg=() + if [[ -n "$scope" ]]; then + projects_arg=(--projects "$scope") + fi + + preid="${{ steps.ctx.outputs.preid }}" + preid_arg=() + if [[ -n "$preid" ]]; then + preid_arg=(--preid "$preid") + fi + + npx nx release "${{ steps.ctx.outputs.specifier }}" \ + "${preid_arg[@]}" \ + "${projects_arg[@]}" \ + --skip-publish \ + --verbose + + # --atomic: if the branch push is rejected, the release tags are rejected with it, + # so a failed run leaves no orphaned tag behind and a plain re-dispatch is enough to retry. + - name: Push release commit and tags (dispatch) + if: ${{ steps.ctx.outputs.mode == 'dispatch' && !inputs.dry-run }} + run: git push --atomic --follow-tags origin HEAD + + - name: nx release version + changelog (dispatch, dry-run) + if: ${{ steps.ctx.outputs.mode == 'dispatch' && inputs.dry-run }} + shell: bash + run: | + set -euo pipefail + + scope="${{ steps.ctx.outputs.scope }}" + projects_arg=() + if [[ -n "$scope" ]]; then + projects_arg=(--projects "$scope") + fi + + preid="${{ steps.ctx.outputs.preid }}" + preid_arg=() + if [[ -n "$preid" ]]; then + preid_arg=(--preid "$preid") + fi + + npx nx release "${{ steps.ctx.outputs.specifier }}" \ + "${preid_arg[@]}" \ + "${projects_arg[@]}" \ + --skip-publish \ + --verbose \ + --dry-run + + # BUILD: build.all (not build) is the publishable artifact — it runs tools/scripts/build-finish.ts, + # which copies the publishing .npmignore into dist and strips fields npm must not see. + - name: Build affected projects (main) + if: ${{ steps.ctx.outputs.mode == 'main' && steps.affected.outputs.count != '0' }} + run: npx nx run-many -t build.all --projects "${{ steps.affected.outputs.projects }}" --verbose + + - name: Build projects (dispatch) + if: ${{ steps.ctx.outputs.mode == 'dispatch' }} + shell: bash + run: | + set -euo pipefail + scope="${{ steps.ctx.outputs.scope }}" + if [[ -n "$scope" ]]; then + npx nx run-many -t build.all --projects "$scope" --verbose + else + npx nx run-many -t build.all --all --verbose + fi + + # PUBLISH: OIDC trusted publishing (default). Avoid any lingering token auth. + - name: nx release publish (OIDC, main) + if: ${{ steps.ctx.outputs.mode == 'main' && steps.affected.outputs.count != '0' && vars.USE_NPM_TOKEN != 'true' }} + shell: bash + env: + NPM_CONFIG_PROVENANCE: true + NODE_AUTH_TOKEN: "" + run: | + set -euo pipefail + unset NODE_AUTH_TOKEN + rm -f ~/.npmrc || true + if [[ -n "${NPM_CONFIG_USERCONFIG:-}" ]]; then + rm -f "$NPM_CONFIG_USERCONFIG" || true + fi + + npx nx release publish \ + --projects "${{ steps.affected.outputs.projects }}" \ + --tag "${{ steps.ctx.outputs.dist_tag }}" \ + --access public \ + --verbose + + - name: nx release publish (OIDC, dispatch) + if: ${{ steps.ctx.outputs.mode == 'dispatch' && steps.ctx.outputs.dry_run != 'true' && vars.USE_NPM_TOKEN != 'true' }} + shell: bash + env: + NPM_CONFIG_PROVENANCE: true + NODE_AUTH_TOKEN: "" + run: | + set -euo pipefail + unset NODE_AUTH_TOKEN + rm -f ~/.npmrc || true + if [[ -n "${NPM_CONFIG_USERCONFIG:-}" ]]; then + rm -f "$NPM_CONFIG_USERCONFIG" || true + fi + + scope="${{ steps.ctx.outputs.scope }}" + if [[ -n "$scope" ]]; then + projects_arg="--projects $scope" + else + projects_arg="" + fi + + npx nx release publish \ + $projects_arg \ + --tag "${{ steps.ctx.outputs.dist_tag }}" \ + --access public \ + --verbose + + - name: nx release publish (OIDC, dispatch dry-run) + if: ${{ steps.ctx.outputs.mode == 'dispatch' && inputs.dry-run && vars.USE_NPM_TOKEN != 'true' }} + shell: bash + env: + NPM_CONFIG_PROVENANCE: true + NODE_AUTH_TOKEN: "" + run: | + set -euo pipefail + unset NODE_AUTH_TOKEN + rm -f ~/.npmrc || true + if [[ -n "${NPM_CONFIG_USERCONFIG:-}" ]]; then + rm -f "$NPM_CONFIG_USERCONFIG" || true + fi + + scope="${{ steps.ctx.outputs.scope }}" + if [[ -n "$scope" ]]; then + projects_arg="--projects $scope" + else + projects_arg="" + fi + + npx nx release publish \ + $projects_arg \ + --tag "${{ steps.ctx.outputs.dist_tag }}" \ + --access public \ + --verbose \ + --dry-run + + # PUBLISH: token fallback (only when explicitly enabled via repo/environment variable USE_NPM_TOKEN=true). + - name: nx release publish (token, main) + if: ${{ steps.ctx.outputs.mode == 'main' && steps.affected.outputs.count != '0' && vars.USE_NPM_TOKEN == 'true' }} + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} + NPM_CONFIG_PROVENANCE: true + run: | + npx nx release publish --projects "${{ steps.affected.outputs.projects }}" --tag "${{ steps.ctx.outputs.dist_tag }}" --access public --verbose + + - name: nx release publish (token, dispatch) + if: ${{ steps.ctx.outputs.mode == 'dispatch' && steps.ctx.outputs.dry_run != 'true' && vars.USE_NPM_TOKEN == 'true' }} + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} + NPM_CONFIG_PROVENANCE: true + run: | + set -euo pipefail + scope="${{ steps.ctx.outputs.scope }}" + if [[ -n "$scope" ]]; then + projects_arg="--projects $scope" + else + projects_arg="" + fi + npx nx release publish $projects_arg --tag "${{ steps.ctx.outputs.dist_tag }}" --access public --verbose + + - name: nx release publish (token, dispatch dry-run) + if: ${{ steps.ctx.outputs.mode == 'dispatch' && inputs.dry-run && vars.USE_NPM_TOKEN == 'true' }} + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} + NPM_CONFIG_PROVENANCE: true + run: | + set -euo pipefail + scope="${{ steps.ctx.outputs.scope }}" + if [[ -n "$scope" ]]; then + projects_arg="--projects $scope" + else + projects_arg="" + fi + npx nx release publish $projects_arg --tag "${{ steps.ctx.outputs.dist_tag }}" --access public --verbose --dry-run + + # Tag-triggered publishing: publish the single package referenced by the tag. + - name: Build project before publish (tag) + if: ${{ steps.ctx.outputs.mode == 'tag' }} + run: npx nx run "${{ steps.taginfo.outputs.project }}:build.all" --verbose + + - name: nx release publish (tag) + if: ${{ steps.ctx.outputs.mode == 'tag' && vars.USE_NPM_TOKEN != 'true' }} + shell: bash + env: + NPM_CONFIG_PROVENANCE: true + NODE_AUTH_TOKEN: "" + run: | + set -euo pipefail + unset NODE_AUTH_TOKEN + rm -f ~/.npmrc || true + if [[ -n "${NPM_CONFIG_USERCONFIG:-}" ]]; then + rm -f "$NPM_CONFIG_USERCONFIG" || true + fi + + npx nx release publish \ + --projects "${{ steps.taginfo.outputs.project }}" \ + --tag "${{ steps.taginfo.outputs.dist_tag }}" \ + --access public \ + --verbose + + - name: nx release publish (tag, token) + if: ${{ steps.ctx.outputs.mode == 'tag' && vars.USE_NPM_TOKEN == 'true' }} + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} + NPM_CONFIG_PROVENANCE: true + run: | + npx nx release publish --projects "${{ steps.taginfo.outputs.project }}" --tag "${{ steps.taginfo.outputs.dist_tag }}" --access public --verbose + + # Nx only writes CHANGELOG.md files (nx.json release.changelog has no createRelease), + # so GitHub releases are posted here. This must run after the tag push (creating a + # release for a tag GitHub doesn't have yet would auto-create it from the wrong + # commit) and after npm publish (so a GitHub API failure cannot block publishing — + # a red run here means only the release step needs manual recovery). + - name: Create GitHub releases (dispatch) + if: ${{ steps.ctx.outputs.mode == 'dispatch' && !inputs.dry-run }} + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + + tags=$(git tag --points-at HEAD) + if [[ -z "$tags" ]]; then + echo "No release tags on HEAD; skipping GitHub release creation." + exit 0 + fi + + projects=$(npx nx show projects --projects "packages/*" --type lib --sep ' ') + + for tag in $tags; do + # Tag format is {version}-{projectName} (nx.json releaseTagPattern); take the + # longest project suffix so project names containing '-' resolve correctly. + best_match="" + best_len=0 + for p in $projects; do + suffix="-${p}" + if [[ "$tag" == *"$suffix" ]] && (( ${#p} > best_len )); then + best_match="$p" + best_len=${#p} + fi + done + if [[ -z "$best_match" ]]; then + echo "Skipping tag '$tag' (no matching release project)." + continue + fi + + version="${tag%-$best_match}" + root=$(npx nx show project "$best_match" --json | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>process.stdout.write(JSON.parse(s).root))') + pkg=$(node -e "console.log(require('./${root}/package.json').name)") + + if gh release view "$tag" --json id --jq .id >/dev/null 2>&1; then + echo "Release for '$tag' already exists; skipping." + continue + fi + + notes_file="$(mktemp)" + awk -v h="## ${version} (" 'index($0, h) == 1 {found=1; next} /^## / {if (found) exit} found' "${root}/CHANGELOG.md" > "$notes_file" + + flags=(--verify-tag --title "${pkg}@${version}") + if [[ "$version" == *-* ]]; then + flags+=(--prerelease) + elif [[ "$best_match" == "firebase-core" ]]; then + # The repo's Latest badge stays on stable firebase-core releases; other packages + # must not claim it just by being published later. + flags+=(--latest) + else + flags+=(--latest=false) + fi + if [[ -s "$notes_file" ]]; then + flags+=(--notes-file "$notes_file") + else + flags+=(--generate-notes) + fi + + gh release create "$tag" "${flags[@]}" + done + + - name: Summary + if: always() + run: | + mode="${{ steps.ctx.outputs.mode }}" + + echo "Nx Release completed." + echo "- mode: ${mode}" + echo "- specifier: '${{ steps.ctx.outputs.specifier }}'" + echo "- release-type: '${{ steps.ctx.outputs.release_type }}'" + echo "- preid: '${{ steps.ctx.outputs.preid }}'" + echo "- dist-tag: ${{ steps.ctx.outputs.mode == 'tag' && steps.taginfo.outputs.dist_tag || steps.ctx.outputs.dist_tag }}" + echo "- scope: '${{ steps.ctx.outputs.scope }}'" + echo "- next-prerelease-allowlist: '${NEXT_PRERELEASE_PROJECT_ALLOWLIST}'" + if [[ "$mode" == "main" ]]; then + echo "- projects: ${{ steps.affected.outputs.projects }}" + elif [[ "$mode" == "dispatch" ]]; then + if [[ -n "${{ steps.ctx.outputs.scope }}" ]]; then + echo "- projects: ${{ steps.ctx.outputs.scope }}" + else + echo "- projects: all configured release projects" + fi + else + echo "- project: ${{ steps.taginfo.outputs.project }}" + fi + echo "- dry-run: ${{ steps.ctx.outputs.dry_run }}" + echo "- use-token: ${{ vars.USE_NPM_TOKEN == 'true' }}" diff --git a/README.md b/README.md index d3d3c572..d5d3488e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +- [@nativescript/firebase-ai](packages/firebase-ai/README.md) - [@nativescript/firebase-analytics](packages/firebase-analytics/README.md) - [@nativescript/firebase-app-check](packages/firebase-app-check/README.md) - [@nativescript/firebase-app-check-debug](packages/firebase-app-check-debug/README.md) @@ -5,7 +6,6 @@ - [@nativescript/firebase-core](packages/firebase-core/README.md) - [@nativescript/firebase-crashlytics](packages/firebase-crashlytics/README.md) - [@nativescript/firebase-database](packages/firebase-database/README.md) -- [@nativescript/firebase-dynamic-links](packages/firebase-dynamic-links/README.md) - [@nativescript/firebase-firestore](packages/firebase-firestore/README.md) - [@nativescript/firebase-functions](packages/firebase-functions/README.md) - [@nativescript/firebase-in-app-messaging](packages/firebase-in-app-messaging/README.md) @@ -17,6 +17,18 @@ - [@nativescript/firebase-storage](packages/firebase-storage/README.md) - [@nativescript/firebase-ui](packages/firebase-ui/README.md) +## Requirements + +The 6.x suite tracks Firebase iOS `12.19.x` and the Firebase Android BOM `34.19.0`, which require an +iOS deployment target of **15.0**, an Android `minSdkVersion` of **23** and a `compileSdkVersion` +of **35**. + +iOS dependencies are pulled with **Swift Package Manager** rather than CocoaPods, which needs +**NativeScript CLI 8.9.0 or newer** (9.0.3 if you want to override a plugin's package from your own +config). See +[@nativescript/firebase-core](packages/firebase-core/README.md#requirements) for the details and +where to set each value. + # How to use? This workspace manages the suite of plugins listed above. @@ -59,12 +71,31 @@ Note: *good to always clean the demo you plan to run after focusing. (You can cl ## How to publish packages? -``` -npm run publish-packages -``` +Releases run through the [Release Workflow](.github/workflows/secure_nx_release.yml) GitHub Action. It versions with `nx release`, publishes to npm through OIDC trusted publishing (provenance attached, no npm token in the repo), and creates one GitHub release per package tag. + +### Manual release (Actions → Release Workflow → Run workflow) + +- `version`: exact version such as `6.1.0` or `6.1.0-rc.0`. The npm dist-tag is derived from it (`6.1.0` → `latest`, `6.1.0-rc.0` → `rc`). +- `release-type` + `preid`: used when `version` is empty. `patch` / `minor` / `major` publish to `latest`; `prerelease` bumps e.g. `6.0.0` → `6.0.1-next.0` and publishes to the `preid` dist-tag. +- `release-group`: Nx project pattern to scope the release, e.g. `firebase-core` or `firebase-messaging*,firebase-core`. Empty releases every package under `packages/`. +- `dry-run`: prints every change and publishes nothing. + +Each run commits the bumped `package.json` and per-package `CHANGELOG.md`, tags `{version}-{projectName}` (e.g. `6.1.0-firebase-core`), builds with `build.all`, and publishes from `dist/packages/*`. + +### Tag release + +Pushing a tag shaped `{version}-{projectName}` publishes that single package at the version already in its `package.json`. A prerelease version goes to the `next` dist-tag, anything else to `latest`. + +### Automatic `next` prereleases + +A push to `main` publishes a `next` prerelease of the affected packages named in the repository variable `NEXT_PRERELEASE_PROJECT_ALLOWLIST` (comma-separated Nx project names, e.g. `firebase-core,firebase-auth`). Leave the variable unset and pushes to `main` publish nothing. + +### One-time repository setup + +- npm: on each `@nativescript/firebase-*` package, add a trusted publisher for GitHub Actions with organization `NativeScript`, repository `firebase`, workflow `secure_nx_release.yml` and environment `npm-publish`. npm only offers trusted publishing on packages that already exist, so a brand-new package must be published once with a token first. +- GitHub: environments `npm-publish` and `npm-publish-dry-run`. Required reviewers on `npm-publish` gate every real publish. +- Optional token fallback: set the repository variable `USE_NPM_TOKEN` to `true` and the secret `NPM_PUBLISH_TOKEN` to publish with a granular npm token instead of OIDC. -- You will be prompted for the package names to publish. Leaving blank and hitting enter will publish them all. -- You will then be prompted for the version to use. Leaving blank will auto bump the patch version (it also handles prerelease types like alpha, beta, rc, etc. - It even auto tags the corresponding prelease type on npm). -- You will then be given a brief sanity check 🧠😊 +The interactive `npm run publish-packages` generator still works for local, token-based publishing, but it produces no provenance, changelogs, tags or GitHub releases.

Made with ❤️

diff --git a/apps/demo-angular/package.json b/apps/demo-angular/package.json index 4d885186..056ea0ea 100644 --- a/apps/demo-angular/package.json +++ b/apps/demo-angular/package.json @@ -6,6 +6,7 @@ "@nativescript/firebase-auth": "file:../../dist/packages/firebase-auth", "@nativescript/firebase-database": "file:../../dist/packages/firebase-database", "@nativescript/firebase-firestore": "file:../../dist/packages/firebase-firestore", + "@nativescript/firebase-ai": "file:../../packages/firebase-ai", "@nativescript/firebase-analytics": "file:../../dist/packages/firebase-analytics", "@nativescript/firebase-crashlytics": "file:../../dist/packages/firebase-crashlytics", "@nativescript/firebase-app-check": "file:../../dist/packages/firebase-app-check", @@ -14,7 +15,6 @@ "@nativescript/firebase-in-app-messaging": "file:../../dist/packages/firebase-in-app-messaging", "@nativescript/firebase-performance": "file:../../dist/packages/firebase-performance", "@nativescript/firebase-installations": "file:../../dist/packages/firebase-installations", - "@nativescript/firebase-dynamic-links": "file:../../dist/packages/firebase-dynamic-links", "@nativescript/firebase-messaging": "file:../../dist/packages/firebase-messaging", "@nativescript/firebase-functions": "file:../../dist/packages/firebase-functions", "@nativescript/firebase-app-check-debug": "file:../../dist/packages/firebase-app-check-debug", diff --git a/apps/demo-angular/src/app-routing.module.ts b/apps/demo-angular/src/app-routing.module.ts index 131105c9..bc8c7ab0 100644 --- a/apps/demo-angular/src/app-routing.module.ts +++ b/apps/demo-angular/src/app-routing.module.ts @@ -7,6 +7,7 @@ import { HomeComponent } from './home.component'; const routes: Routes = [ { path: '', redirectTo: '/home', pathMatch: 'full' }, { path: 'home', component: HomeComponent }, + { path: 'firebase-ai', loadChildren: () => import('./plugin-demos/firebase-ai.module').then((m) => m.FirebaseAiModule) }, { path: 'firebase-analytics', loadChildren: () => import('./plugin-demos/firebase-analytics.module').then((m) => m.FirebaseAnalyticsModule) }, { path: 'firebase-app-check', loadChildren: () => import('./plugin-demos/firebase-app-check.module').then((m) => m.FirebaseAppCheckModule) }, { path: 'firebase-app-check-debug', loadChildren: () => import('./plugin-demos/firebase-app-check-debug.module').then((m) => m.FirebaseAppCheckDebugModule) }, @@ -14,7 +15,6 @@ const routes: Routes = [ { path: 'firebase-core', loadChildren: () => import('./plugin-demos/firebase-core.module').then((m) => m.FirebaseCoreModule) }, { path: 'firebase-crashlytics', loadChildren: () => import('./plugin-demos/firebase-crashlytics.module').then((m) => m.FirebaseCrashlyticsModule) }, { path: 'firebase-database', loadChildren: () => import('./plugin-demos/firebase-database.module').then((m) => m.FirebaseDatabaseModule) }, - { path: 'firebase-dynamic-links', loadChildren: () => import('./plugin-demos/firebase-dynamic-links.module').then((m) => m.FirebaseDynamicLinksModule) }, { path: 'firebase-firestore', loadChildren: () => import('./plugin-demos/firebase-firestore.module').then((m) => m.FirebaseFirestoreModule) }, { path: 'firebase-functions', loadChildren: () => import('./plugin-demos/firebase-functions.module').then((m) => m.FirebaseFunctionsModule) }, { path: 'firebase-in-app-messaging', loadChildren: () => import('./plugin-demos/firebase-in-app-messaging.module').then((m) => m.FirebaseInAppMessagingModule) }, diff --git a/apps/demo-angular/src/home.component.ts b/apps/demo-angular/src/home.component.ts index ba58d650..f868adfc 100644 --- a/apps/demo-angular/src/home.component.ts +++ b/apps/demo-angular/src/home.component.ts @@ -6,6 +6,9 @@ import { Component } from '@angular/core'; }) export class HomeComponent { demos = [ + { + name: 'firebase-ai', + }, { name: 'firebase-analytics', }, @@ -27,9 +30,6 @@ export class HomeComponent { { name: 'firebase-database', }, - { - name: 'firebase-dynamic-links', - }, { name: 'firebase-firestore', }, diff --git a/apps/demo-angular/src/plugin-demos/firebase-ai.component.html b/apps/demo-angular/src/plugin-demos/firebase-ai.component.html new file mode 100644 index 00000000..7a2477c6 --- /dev/null +++ b/apps/demo-angular/src/plugin-demos/firebase-ai.component.html @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/apps/demo-angular/src/plugin-demos/firebase-ai.component.ts b/apps/demo-angular/src/plugin-demos/firebase-ai.component.ts new file mode 100644 index 00000000..c7860b91 --- /dev/null +++ b/apps/demo-angular/src/plugin-demos/firebase-ai.component.ts @@ -0,0 +1,16 @@ +import { Component, NgZone } from '@angular/core'; +import { DemoSharedFirebaseAi } from '@demo/shared'; + +@Component({ + selector: 'demo-firebase-ai', + templateUrl: 'firebase-ai.component.html', +}) +export class FirebaseAiComponent { + demoShared: DemoSharedFirebaseAi; + + constructor(private _ngZone: NgZone) {} + + ngOnInit() { + this.demoShared = new DemoSharedFirebaseAi(); + } +} diff --git a/apps/demo-angular/src/plugin-demos/firebase-ai.module.ts b/apps/demo-angular/src/plugin-demos/firebase-ai.module.ts new file mode 100644 index 00000000..75d9b6f9 --- /dev/null +++ b/apps/demo-angular/src/plugin-demos/firebase-ai.module.ts @@ -0,0 +1,10 @@ +import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core'; +import { NativeScriptCommonModule, NativeScriptRouterModule } from '@nativescript/angular'; +import { FirebaseAiComponent } from './firebase-ai.component'; + +@NgModule({ + imports: [NativeScriptCommonModule, NativeScriptRouterModule.forChild([{ path: '', component: FirebaseAiComponent }])], + declarations: [FirebaseAiComponent], + schemas: [NO_ERRORS_SCHEMA], +}) +export class FirebaseAiModule {} diff --git a/apps/demo-angular/src/plugin-demos/firebase-dynamic-links.component.html b/apps/demo-angular/src/plugin-demos/firebase-dynamic-links.component.html deleted file mode 100644 index 585b791c..00000000 --- a/apps/demo-angular/src/plugin-demos/firebase-dynamic-links.component.html +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/apps/demo-angular/src/plugin-demos/firebase-dynamic-links.component.ts b/apps/demo-angular/src/plugin-demos/firebase-dynamic-links.component.ts deleted file mode 100644 index b77759ae..00000000 --- a/apps/demo-angular/src/plugin-demos/firebase-dynamic-links.component.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Component, NgZone } from '@angular/core'; -import { DemoSharedFirebaseDynamicLinks } from '@demo/shared'; -import { } from '@nativescript/firebase-dynamic-links'; - -@Component({ - selector: 'demo-firebase-dynamic-links', - templateUrl: 'firebase-dynamic-links.component.html', -}) -export class FirebaseDynamicLinksComponent { - - demoShared: DemoSharedFirebaseDynamicLinks; - - constructor(private _ngZone: NgZone) {} - - ngOnInit() { - this.demoShared = new DemoSharedFirebaseDynamicLinks(); - } - -} \ No newline at end of file diff --git a/apps/demo-angular/src/plugin-demos/firebase-dynamic-links.module.ts b/apps/demo-angular/src/plugin-demos/firebase-dynamic-links.module.ts deleted file mode 100644 index 9b468a17..00000000 --- a/apps/demo-angular/src/plugin-demos/firebase-dynamic-links.module.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core'; -import { NativeScriptCommonModule, NativeScriptRouterModule } from '@nativescript/angular'; -import { FirebaseDynamicLinksComponent } from './firebase-dynamic-links.component'; - -@NgModule({ - imports: [NativeScriptCommonModule, NativeScriptRouterModule.forChild([{ path: '', component: FirebaseDynamicLinksComponent }])], - declarations: [FirebaseDynamicLinksComponent], - schemas: [ NO_ERRORS_SCHEMA] -}) -export class FirebaseDynamicLinksModule {} diff --git a/apps/demo-angular/tsconfig.json b/apps/demo-angular/tsconfig.json index 6bcd3a50..8f60ecb6 100644 --- a/apps/demo-angular/tsconfig.json +++ b/apps/demo-angular/tsconfig.json @@ -8,6 +8,7 @@ "@nativescript/firebase-auth": ["packages/firebase-auth/index.d.ts"], "@nativescript/firebase-database": ["packages/firebase-database/index.d.ts"], "@nativescript/firebase-firestore": ["packages/firebase-firestore/index.d.ts"], + "@nativescript/firebase-ai": ["packages/firebase-ai/index.d.ts"], "@nativescript/firebase-analytics": ["packages/firebase-analytics/index.d.ts"], "@nativescript/firebase-crashlytics": ["packages/firebase-crashlytics/index.d.ts"], "@nativescript/firebase-app-check": ["packages/firebase-app-check/index.d.ts"], @@ -16,7 +17,6 @@ "@nativescript/firebase-in-app-messaging": ["packages/firebase-in-app-messaging/index.d.ts"], "@nativescript/firebase-performance": ["packages/firebase-performance/index.d.ts"], "@nativescript/firebase-installations": ["packages/firebase-installations/index.d.ts"], - "@nativescript/firebase-dynamic-links": ["packages/firebase-dynamic-links/index.d.ts"], "@nativescript/firebase-messaging": ["packages/firebase-messaging/index.d.ts"], "@nativescript/firebase-functions": ["packages/firebase-functions/index.d.ts"], "@nativescript/firebase-app-check-debug": ["packages/firebase-app-check-debug/index.d.ts"], diff --git a/apps/demo-vue/app/app.ts b/apps/demo-vue/app/app.ts index 6371f772..30be7b94 100644 --- a/apps/demo-vue/app/app.ts +++ b/apps/demo-vue/app/app.ts @@ -11,7 +11,6 @@ import '@nativescript/firebase-analytics'; import '@nativescript/firebase-auth'; import '@nativescript/firebase-crashlytics'; import '@nativescript/firebase-database'; -import '@nativescript/firebase-dynamic-links'; import '@nativescript/firebase-firestore'; import '@nativescript/firebase-functions'; import '@nativescript/firebase-in-app-messaging'; @@ -33,12 +32,6 @@ firebase() firebase().crashlytics().setCrashlyticsCollectionEnabled(true); }); -const dynamicLinks = firebase().dynamicLinks(); - -dynamicLinks.onLink((link) => { - console.log('onLink', link); -}); - Application.on('launch', (args) => { const messaging = firebase().messaging(); diff --git a/apps/demo-vue/app/components/Home.vue b/apps/demo-vue/app/components/Home.vue index 772d30bb..deecba49 100644 --- a/apps/demo-vue/app/components/Home.vue +++ b/apps/demo-vue/app/components/Home.vue @@ -18,6 +18,7 @@ diff --git a/apps/demo-vue/app/plugin-demos/firebase-dynamic-links.vue b/apps/demo-vue/app/plugin-demos/firebase-dynamic-links.vue deleted file mode 100644 index 5d0aa759..00000000 --- a/apps/demo-vue/app/plugin-demos/firebase-dynamic-links.vue +++ /dev/null @@ -1,34 +0,0 @@ - - - - - diff --git a/apps/demo-vue/package.json b/apps/demo-vue/package.json index 75f8005f..8e414832 100644 --- a/apps/demo-vue/package.json +++ b/apps/demo-vue/package.json @@ -3,13 +3,13 @@ "description": "NativeScript Application", "dependencies": { "@nativescript/core": "file:../../node_modules/@nativescript/core", + "@nativescript/firebase-ai": "file:../../packages/firebase-ai", "@nativescript/firebase-analytics": "file:../../dist/packages/firebase-analytics", "@nativescript/firebase-app-check": "file:../../packages/firebase-app-check", "@nativescript/firebase-auth": "file:../../packages/firebase-auth", "@nativescript/firebase-core": "file:../../packages/firebase-core", "@nativescript/firebase-crashlytics": "file:../../packages/firebase-crashlytics", "@nativescript/firebase-database": "file:../../packages/firebase-database", - "@nativescript/firebase-dynamic-links": "file:../../packages/firebase-dynamic-links", "@nativescript/firebase-firestore": "file:../../packages/firebase-firestore", "@nativescript/firebase-functions": "file:../../packages/firebase-functions", "@nativescript/firebase-in-app-messaging": "file:../../packages/firebase-in-app-messaging", @@ -20,7 +20,7 @@ "@nativescript/firebase-storage": "file:../../packages/firebase-storage", "@nativescript/firebase-app-check-debug": "file:../../packages/firebase-app-check-debug", "@nativescript/firebase-messaging-core": "file:../../packages/firebase-messaging-core", - "@nativescript/firebase-ui": "file:../../packages/firebase-ui" + "@nativescript/firebase-ui": "file:../../dist/packages/firebase-ui" }, "devDependencies": { "@nativescript/android": "~8.8.0", diff --git a/apps/demo-vue/tsconfig.json b/apps/demo-vue/tsconfig.json index e02a8e7a..dbc2eb31 100644 --- a/apps/demo-vue/tsconfig.json +++ b/apps/demo-vue/tsconfig.json @@ -10,6 +10,7 @@ "@nativescript/firebase-auth": ["../../packages/firebase-auth/index.d.ts"], "@nativescript/firebase-database": ["../../packages/firebase-database/index.d.ts"], "@nativescript/firebase-firestore": ["../../packages/firebase-firestore/index.d.ts"], + "@nativescript/firebase-ai": ["../../packages/firebase-ai/index.d.ts"], "@nativescript/firebase-analytics": ["../../packages/firebase-analytics/index.d.ts"], "@nativescript/firebase-crashlytics": ["../../packages/firebase-crashlytics/index.d.ts"], "@nativescript/firebase-app-check": ["../../packages/firebase-app-check/index.d.ts"], @@ -18,7 +19,6 @@ "@nativescript/firebase-in-app-messaging": ["../../packages/firebase-in-app-messaging/index.d.ts"], "@nativescript/firebase-performance": ["../../packages/firebase-performance/index.d.ts"], "@nativescript/firebase-installations": ["../../packages/firebase-installations/index.d.ts"], - "@nativescript/firebase-dynamic-links": ["../../packages/firebase-dynamic-links/index.d.ts"], "@nativescript/firebase-messaging": ["../../packages/firebase-messaging/index.d.ts"], "@nativescript/firebase-functions": ["../../packages/firebase-functions/index.d.ts"], "@nativescript/firebase-app-check-debug": ["../../packages/firebase-app-check-debug/index.d.ts"], diff --git a/apps/demo/nativescript.config.ts b/apps/demo/nativescript.config.ts index 000ec6dc..5a5af011 100644 --- a/apps/demo/nativescript.config.ts +++ b/apps/demo/nativescript.config.ts @@ -4,6 +4,18 @@ export default { //id: 'org.nativescript.firebasedemo', id: 'io.github.triniwiz.nativescript.firebasedemo', appResourcesPath: '../../tools/assets/App_Resources', + ios: { + SPMPackages: [ + // Overrides the package @nativescript/firebase-analytics declares, swapping in the + // build without ad identifiers. Matching on `name` is what makes the app win. + { + name: 'FirebaseAnalytics', + libs: ['FirebaseAnalyticsCore'], + repositoryURL: 'https://github.com/firebase/firebase-ios-sdk', + version: '>=12.19.0 <13.0.0', + }, + ], + }, android: { v8Flags: '--expose_gc', markingMode: 'none', diff --git a/apps/demo/package.json b/apps/demo/package.json index 8c2e8dad..f594359e 100644 --- a/apps/demo/package.json +++ b/apps/demo/package.json @@ -5,6 +5,7 @@ "repository": "", "dependencies": { "@nativescript/core": "file:../../node_modules/@nativescript/core", + "@nativescript/firebase-ai": "file:../../packages/firebase-ai", "@nativescript/firebase-analytics": "file:../../dist/packages/firebase-analytics", "@nativescript/firebase-app-check": "file:../../packages/firebase-app-check", "@nativescript/firebase-app-check-debug": "file:../../packages/firebase-app-check-debug", @@ -12,7 +13,6 @@ "@nativescript/firebase-core": "file:../../packages/firebase-core", "@nativescript/firebase-crashlytics": "file:../../packages/firebase-crashlytics", "@nativescript/firebase-database": "file:../../packages/firebase-database", - "@nativescript/firebase-dynamic-links": "file:../../packages/firebase-dynamic-links", "@nativescript/firebase-firestore": "file:../../packages/firebase-firestore", "@nativescript/firebase-functions": "file:../../packages/firebase-functions", "@nativescript/firebase-in-app-messaging": "file:../../packages/firebase-in-app-messaging", @@ -22,8 +22,8 @@ "@nativescript/firebase-performance": "file:../../packages/firebase-performance", "@nativescript/firebase-remote-config": "file:../../packages/firebase-remote-config", "@nativescript/firebase-storage": "file:../../packages/firebase-storage", - "@nativescript/firebase-ui": "file:../../packages/firebase-ui", - "@nativescript/google-signin": "~2.1.0" + "@nativescript/firebase-ui": "file:../../dist/packages/firebase-ui", + "@nativescript/google-signin": "^3.0.0" }, "devDependencies": { "@nativescript/android": "~8.8.0", diff --git a/apps/demo/src/app.ts b/apps/demo/src/app.ts index 543aff3f..e1939cb0 100644 --- a/apps/demo/src/app.ts +++ b/apps/demo/src/app.ts @@ -5,7 +5,6 @@ import '@nativescript/firebase-analytics'; import '@nativescript/firebase-auth'; import '@nativescript/firebase-crashlytics'; import '@nativescript/firebase-database'; -import '@nativescript/firebase-dynamic-links'; import '@nativescript/firebase-firestore'; import '@nativescript/firebase-functions'; import '@nativescript/firebase-in-app-messaging'; @@ -27,12 +26,6 @@ firebase() firebase().crashlytics().setCrashlyticsCollectionEnabled(true); }); -const dynamicLinks = firebase().dynamicLinks(); - -dynamicLinks.onLink((link) => { - console.log('onLink', link); -}); - const messaging = firebase().messaging(); messaging.onMessage((message) => { diff --git a/apps/demo/src/main-view-model.ts b/apps/demo/src/main-view-model.ts index 13b7b070..8eb29bc0 100644 --- a/apps/demo/src/main-view-model.ts +++ b/apps/demo/src/main-view-model.ts @@ -2,6 +2,9 @@ import { Observable, Frame } from '@nativescript/core'; export class MainViewModel extends Observable { demos = [ + { + name: 'firebase-ai', + }, { name: 'firebase-analytics', }, @@ -14,9 +17,6 @@ export class MainViewModel extends Observable { { name: 'firebase-database', }, - { - name: 'firebase-dynamic-links', - }, { name: 'firebase-firestore', }, diff --git a/apps/demo/src/plugin-demos/firebase-ai.ts b/apps/demo/src/plugin-demos/firebase-ai.ts new file mode 100644 index 00000000..02772a9d --- /dev/null +++ b/apps/demo/src/plugin-demos/firebase-ai.ts @@ -0,0 +1,9 @@ +import { EventData, Page } from '@nativescript/core'; +import { DemoSharedFirebaseAi } from '@demo/shared'; + +export function navigatingTo(args: EventData) { + const page = args.object; + page.bindingContext = new DemoModel(); +} + +export class DemoModel extends DemoSharedFirebaseAi {} diff --git a/apps/demo/src/plugin-demos/firebase-ai.xml b/apps/demo/src/plugin-demos/firebase-ai.xml new file mode 100644 index 00000000..266f8078 --- /dev/null +++ b/apps/demo/src/plugin-demos/firebase-ai.xml @@ -0,0 +1,15 @@ + + + + + + + + +