[
  {
    "path": ".editorconfig",
    "content": "# Editor configuration, see http://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where the package manifests are located.\n# Please see the documentation for all configuration options:\n# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file\n\nversion: 2\nupdates:\n  - package-ecosystem: \"npm\" # See documentation for possible values\n    directory: \"/\" # Location of package manifests\n    schedule:\n      interval: \"monthly\"\n    ignore:\n      # Ignore updates to these packages:\n      - dependency-name: \"@nx*\"\n      - dependency-name: \"@swc*\"\n      - dependency-name: \"@types/*\"\n      - dependency-name: \"@typescript*\"\n      - dependency-name: \"eslint*\"\n      - dependency-name: \"jest*\"\n      - dependency-name: \"nx*\"\n      - dependency-name: \"prettier*\"\n      - dependency-name: \"ts-jest\"\n      - dependency-name: \"ts-node\"\n      - dependency-name: \"tslib\"\n      - dependency-name: \"typescript\"\n      # For all packages, ignore all patch updates\n      # EDIT: Actually, we'll take all updates, but this is a good example\n      # - dependency-name: \"*\"\n      #   update-types: [\"version-update:semver-patch\"]\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "# Build, Test, Lint & e2e pushes.\nname: CI\n\non:\n  # support manual trigger of this workflow\n  workflow_dispatch:\n\n  # run this build action for all pushes to main\n  push:\n    branches: ['main']\n\n  # also run for pull requests that target main\n  pull_request:\n    branches:\n      - main    \n\njobs:\n  setup:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: pnpm/action-setup@v3\n      - uses: actions/setup-node@v4\n        with:\n          node-version-file: '.nvmrc'\n          cache: 'pnpm'\n      - run: pnpm install\n\n  build:\n    needs: [setup]\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: pnpm/action-setup@v3\n      - uses: actions/setup-node@v4\n        with:\n          node-version-file: '.nvmrc'\n          cache: 'pnpm'\n      - run: pnpm install      \n      - run: pnpm run build\n\n  lint:\n    needs: [setup]\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: pnpm/action-setup@v3\n      - uses: actions/setup-node@v4\n        with:\n          node-version-file: '.nvmrc'\n          cache: 'pnpm'\n      - run: pnpm install      \n      - run: pnpm run lint\n\n  test:\n    needs: [setup]\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: pnpm/action-setup@v3\n      - uses: actions/setup-node@v4\n        with:\n          node-version-file: '.nvmrc'\n          cache: 'pnpm'\n      - run: pnpm install      \n      - run: pnpm run test\n\n  # e2e might be too much work todo for every push, lets see.\n  e2e:\n    needs: [setup]\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: pnpm/action-setup@v3\n      - uses: actions/setup-node@v4\n        with:\n          node-version-file: '.nvmrc'\n          cache: 'pnpm'\n      - run: pnpm install      \n      - run: pnpm run e2e\n"
  },
  {
    "path": ".github/workflows/publish.yml",
    "content": "# Publish package to npm when a Github release is published\nname: Publish\n\non:\n  release:\n    types: [released]\n\njobs:\n  verify_tag:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - run: |\n          TAG_VERSION=${GITHUB_REF#refs/tags/v}\n          PACKAGE_VERSION=$(node -p \"require('./packages/nx-firebase/package.json').version\")\n          echo \"Tag version: $TAG_VERSION\"\n          echo \"Package version: $PACKAGE_VERSION\"\n          if [ \"$TAG_VERSION\" != \"$PACKAGE_VERSION\" ]; then\n            echo \"Tag version ($TAG_VERSION) does not match package.json version ($PACKAGE_VERSION)\"\n            exit 1\n          fi\n        shell: bash\n\n  publish_github:\n    needs: [verify_tag]    \n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: pnpm/action-setup@v3\n      - uses: actions/setup-node@v4\n        with:\n          node-version-file: '.nvmrc'\n          cache: 'pnpm'          \n          registry-url: 'https://npm.pkg.github.com/simondotm'\n          #scope: '@simondotm'\n      #- run: npm run addscope     \n      - run: pnpm install\n\n      - name: Build plugin\n        run: npx nx build nx-firebase\n\n      - name: Create package\n        run: pnpm pack\n        working-directory: ./dist/packages/nx-firebase\n\n      - name: Publish to GitHub Packages\n        run: pnpm publish --no-git-checks\n        working-directory: ./dist/packages/nx-firebase\n        env:\n          NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n  publish_npm:\n    needs: [verify_tag]       \n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: pnpm/action-setup@v3\n      - uses: actions/setup-node@v4\n        with:\n          node-version-file: '.nvmrc'\n          cache: 'pnpm'          \n          registry-url: 'https://registry.npmjs.org'\n      - run: pnpm install\n\n      - name: Build plugin\n        run: npx nx build nx-firebase\n\n      - name: Publish to NPM\n        run: pnpm publish --no-git-checks --access=public \n        working-directory: ./dist/packages/nx-firebase\n        env:\n          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# compiled output\ndist\ntmp\n/out-tsc\n\n# dependencies\nnode_modules\n\n# IDEs and editors\n/.idea\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# IDE - VSCode\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n\n# misc\n/.sass-cache\n/connect.lock\n/coverage\n/libpeerconnection.log\nnpm-debug.log\nyarn-error.log\ntestem.log\n/typings\n\n# System Files\n.DS_Store\nThumbs.db\n\n\ne2e.log\n\n.nx/cache\n.nx/workspace-data\n\n.nx-firebase\n.cursor/rules/nx-rules.mdc\n.github/instructions/nx.instructions.md\n"
  },
  {
    "path": ".nvmrc",
    "content": "22.21.1\n"
  },
  {
    "path": ".prettierignore",
    "content": "# Add files here to ignore them from prettier formatting\n\n/dist\n/coverage\n\n/.nx/cache\n/.nx/workspace-data"
  },
  {
    "path": ".prettierrc",
    "content": "{\n  \"semi\": false,\n  \"singleQuote\": true,\n  \"trailingComma\": \"all\",\n  \"arrowParens\": \"always\",\n  \"printWidth\": 80,\n  \"bracketSpacing\": true,\n  \"endOfLine\": \"auto\",\n  \"useTabs\": false,\n  \"quoteProps\": \"as-needed\",\n  \"tabWidth\": 2\n}\n"
  },
  {
    "path": ".vscode/extensions.json",
    "content": "{\n  \"recommendations\": [\n    \"nrwl.angular-console\",\n    \"esbenp.prettier-vscode\",\n    \"dbaeumer.vscode-eslint\",\n    \"firsttris.vscode-jest-runner\"\n  ]\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"eslint.validate\": [\"json\"]\n}\n"
  },
  {
    "path": ".vscode/tasks.json",
    "content": "{\n  \"version\": \"2.0.0\",\n  \"tasks\": [\n    {\n      \"type\": \"npm\",\n      \"script\": \"build -- nx-firebase\",\n      \"group\": \"build\",\n      \"problemMatcher\": [],\n      \"label\": \"Build Nx firebase plugin\",\n      \"detail\": \"nx build nx-firebase\"\n    },\n    {\n      \"type\": \"npm\",\n      \"script\": \"test -- nx-firebase\",\n      \"group\": \"test\",\n      \"problemMatcher\": [],\n      \"label\": \"Test Nx firebase plugin\",\n      \"detail\": \"nx test nx-firebase\"\n    },\n    {\n      \"type\": \"npm\",\n      \"script\": \"e2e -- nx-firebase-e2e\",\n      \"group\": \"test\",\n      \"problemMatcher\": [],\n      \"label\": \"Test e2e Nx firebase plugin\",\n      \"detail\": \"nx e2e nx-firebase-e2e\"\n    }\n  ]\n}\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# @simondotm/nx-firebase Changelog\n\n* **Nx badges** for each release indicate the Nx version the plugin was built against, and the minimum required version of Nx to use the plugin. Each plugin release may be with earlier or future versions of Nx, but this is not always guaranteed.\n\n* **Node badges** for each release indicate the Node version the plugin was built against, and the default engine set for Firebase functions generated by the plugin, not the node version required to use the plugin.\n\n## v17.3.1 Onwards\n\nVersioning of this plugin has been aligned with Nx versioning from v17.3.1 onwards.\n\nCheck the [GitHub releases page](https://github.com/simondotm/nx-firebase/releases) for details of each release.\n\n## v2.3.0 ![nx](https://img.shields.io/badge/Nx-v16.8.1-blue) ![nx](https://img.shields.io/badge/Node-v16-orange)\n\n- Updated plugin to be built against Nx 16.8.1\n- Updated documentation to reflect latest changes\n- App & Function generators now use the same templates as the Firebase tools SDK\n- Firebase dependencies used by generators now derived from plugin workspace & dependabot help\n  - `firebase-functions` -> 4.8.2\n  - `firebase-functions-test` -> 3.1.1\n  - `firebase-admin` -> 11.11.1\n  - `firebase` -> 10.10.0\n  - `firebase-tools` -> 12.9.1\n- Support [--projectNameAndRootFormat](https://nx.dev/nx-api/node/generators/application#projectnameandrootformat) for application and function generators\n- Support `pnpm` Nx workspaces for function deployment by adding `@google-cloud/functions-framework` to the function dependencies\n\n## v2.2.0 ![nx](https://img.shields.io/badge/Nx-v16.6.0-blue) ![nx](https://img.shields.io/badge/Node-v16-orange)\n\n- Fix package dependencies for `@nx/node`, plugin init generator now adds this if/when needed\n- This prevents scenarios where incorrect Nx plugin versions could be added to the user workspace\n- It may also fix an issue with `@nx/esbuild` plugin version being out of sync and bundling external dependencies incorrectly\n\n## v2.1.2 ![nx](https://img.shields.io/badge/Nx-v16.6.0-blue) ![nx](https://img.shields.io/badge/Node-v16-orange)\n\n- Include `@nx/devkit` as a peer dependency for the package\n- Listing in the [Nx plugin registry](https://nx.dev/plugin-registry)\n\n## v2.1.1 ![nx](https://img.shields.io/badge/Nx-v16.6.0-blue) ![nx](https://img.shields.io/badge/Node-v16-orange)\n\n- Fix issues in the v2.1.0 release\n\n## v2.1.0 ![nx](https://img.shields.io/badge/Nx-v16.6.0-blue) ![nx](https://img.shields.io/badge/Node-v16-orange)\n\n- Added support for [environment variables](docs/nx-firebase-functions-environment.md)\n- Added support for [secrets](docs/nx-firebase-functions-environment.md#environment-file-types)\n- Added a custom `serve` executor that exits the Firebase Emulator suite properly\n- Fixes to `sync` generator to update firebase app and firebase function project targets when they are renamed\n- Added a custom `migrate` generator to ensure workspace configurations match the latest plugin version schemas\n- Updated plugin to be built against Nx 16.6.0\n\nRead [here for plugin v2.0.0 -> v2.1.0+ migration instructions](docs/nx-firebase-migrations.md#migrating-from-plugin-v200-to-v210)\n\n## v2.0.0 ![nx](https://img.shields.io/badge/Nx-v16.1.1-blue) ![nx](https://img.shields.io/badge/Node-v16-orange)\n\nOfficial v2 release\n\n- Updated default function template to match firebase CLI\n\n## v2.0.0-beta.1 ![nx](https://img.shields.io/badge/Nx-v16.1.1-blue) ![nx](https://img.shields.io/badge/Node-v16-orange)\n\n- Fixed issue with dependencies\n- Sort codebases\n- Updated documents\n\n## v2.0.0-beta.0 ![nx](https://img.shields.io/badge/Nx-v16.1.1-blue) ![nx](https://img.shields.io/badge/Node-v16-orange)\n\nInitial beta of plugin version 2.0.\n\n**Changes**\n\n- Plugin is completely rewritten\n- Added `@simondotm/nx-firebase:function` generator\n- Firebase functions are now separate application nx projects\n- Now uses `esbuild` to compile & bundle firebase functions\n- Firebase functions no longer require Nx libraries to be buildable\n- Added `@simondotm/nx-firebase:sync` generator to manage Firebase workspaces\n- Minimum Nx version is now 16.1.1\n- Watch mode build of function code & libraries is now fully supported when running Firebase emulator\n\n# Version 2\n\nThis plugin was completely rewritten since V2.x to use esbuild for bundling cloud functions. For documentation of the legacy v1.x plugin version see [here](https://github.com/simondotm/nx-firebase/tree/release/v1.1.0).\n\nUsers of earlier plugin versions must read [here for plugin v1 -> v2 migration instructions](docs/nx-firebase-migrations.md#migration-from-plugin-v1x-to-v200)\n\n> **Please note that legacy v1 versions of the plugin are no longer supported from this point on, so only take this update if you are prepared to migrate your workspace and Firebase projects**\n\n# Version 1\n\n## v1.1.0 ![nx](https://img.shields.io/badge/Nx-v16.1.1-blue) ![nx](https://img.shields.io/badge/Node-v16-orange)\n\nNo changes from beta.\n\n## v1.1.0-beta.0 ![nx](https://img.shields.io/badge/Nx-v16.1.1-blue) ![nx](https://img.shields.io/badge/Node-v16-orange)\n\nUpdated for Nx 16.1.1+\n\n- You will need to migrate your workspace to Nx 16.1.1 before updating the plugin\n\n## v1.0.0 ![nx](https://img.shields.io/badge/Nx-v13.10.6-blue) ![nx](https://img.shields.io/badge/Node-v16-orange)\n\nFirst Major release. No change since `0.13.0-beta.1`.\n\nCompatible with Nx 13.10.6+\n\n## v0.13.0-beta.1 ![nx](https://img.shields.io/badge/Nx-v13.10.6-blue) ![nx](https://img.shields.io/badge/Node-v16-orange)\n\n**Changes**\n\n- `main` removed from template `package.json` for firebase app generator - this is automatically set by the builder\n- Support `nx watch` in compatible Nx workspace hosts\n- Support `nx:run-commands` in compatible Nx workspace hosts\n- Various small fixes\n\n**Migration Guide**\n\n- Check the [targets documentation](docs/nx-firebase-targets.md) if you already have a workspace that is using nx-firebase\n- Remove `main` from `package.json` files in any nx-firebase apps in your workspace\n\n## v0.13.0-beta.0 ![nx](https://img.shields.io/badge/Nx-v13.10.6-blue) ![nx](https://img.shields.io/badge/Node-v16-orange)\n\nDue to the large number of API changes in Nx from version 12 to version 13.10, this plugin has been rewritten from scratch:\n\n- **Improved compatibility**\n  - To support the latest Nx devkit API's for plugins\n- **Refactored build process**\n  - `build` executor is now entirely based on the `@nrwl/js:tsc` executor, which simplifies maintenance of the plugin\n  - this also enables `--watch` to work\n  - _(note that changes to Nx library dependencies are still not yet detectable in `--watch` mode)_\n- **Functions Node engine**\n  - Plugin now defaults to Node 16 runtime engine for firebase functions\n- **Improved Firebase configurations**\n  - `nx g @simondotm/nx-firebase:app` will now generate a `firebase.json` configuration file for the **first** firebase application in the Nx workspace. Additional generated firebase applications will have a firebase configuration named `firebase.project-name.json`\n- **Project Alias Support**\n  - If you already know the firebase project alias you are using for your application, you can now use the `--project` generator parameter to set this in the project targets eg. `nx g @simondotm/nx-firebase:app appname --project=firebaseprojectalias`\n- **Additional dependencies**\n  - `nx g @simondotm/nx-firebase:app` will add firebase and [`kill-port`](https://www.npmjs.com/package/kill-port) dependencies\n  - `kill-port` is used by the `serve` and `emulate` targets to ensure clean startup.\n- **Documentation has been updated**\n\nRecommended minimum version of Nx is now 13.10.6. See [Nx Migration](docs/nx-migration.md) documentation for more information.\n\n## v0.3.13 ![nx](https://img.shields.io/badge/Nx-v13.2.3-blue) ![nx](https://img.shields.io/badge/Node-v12-orange)\n\n\n\n## v0.3.4 ![nx](https://img.shields.io/badge/Nx-v13.2.3-blue) ![nx](https://img.shields.io/badge/Node-v12-orange)\n\nInterim release fixes for issues introduced in nx version 13.0.2+ where `createProjectGraph` was deprecated.\n\nAs of Nx v13.10.x, `copyAssets` was also broken in v0.3.4 of the plugin, but now fixed thanks to contributors.\n\n**Migration Recommendations**\n\nIf you are running on older versions of Nx, the following information may be useful:\n\n**No more `--with-deps`**\n\nFrom Nx version 12.3+, Nx now automatically checks for & builds required dependencies when running build targets.\n\nTherefore `--with-deps` is deprecated, so this parameter can be removed from any firebase Nx application targets such as `build`, `serve`, `deploy`, `configuration`, and also in the functions `predeploy` setting within any `firebase.<project>.json` configuration files you may have.\n\n**No more `--parallel`**\n\nSame applies for `--parallel` since this is now a default setting in `nx.json`\n\nIt may be necessary to pass `--parallel=3` in CI scripts however.\n\n**Nx command syntax**\n\nIt may also be worth updating commands within any firebase application targets of the format:\n\n- `nx run project:build` to the newer\n- `nx build project` syntax\n\n**Update executors**\n\nAs of Nx 13.8.8, `@nrwl/node:package` is replaced by `@nrwl/js:tsc`.\n\nNx version migrations below may handle this transition for you, but if not, you may need to update your `build` targets for imported libraries accordingly to build with `@nrwl/js:tsc`.\n\nSee [Nx Migration](docs/nx-migration.md) documentation for more information.\n\n## v0.3.3 ![nx](https://img.shields.io/badge/Nx-v12.3.4-blue) ![nx](https://img.shields.io/badge/Node-v12-orange)\n\n**General changes**\n\n- Improved listing of firebase functions dependencies; now ordered by npm module libraries first, then local libraries, sorted alphabetically.\n\n**Enhanced Support for Firebase Emulators**\n\n`nx g @simondotm/nx-firebase:app` generator now additionally:\n\n- Adds default `auth` and `pubsub` settings to `\"emulators\": {...}` config in `firebase.<appname>.json` so that these services are also emulated by default.\n\n- Adds a new `getconfig` target to firebase functions app, where:\n\n  - `nx getconfig <firebaseappname>` will fetch the [functions configuration variables](https://firebase.google.com/docs/functions/local-emulator#set_up_functions_configuration_optional) from the server and store it locally as `.runtimeconfig.json`\n\n- Adds `.runtimeconfig.json` to asset list to be copied (if it exists) from app directory to output `dist` directory when built, so that the function emulators will now run if the functions being emulated access variables from the functions config.\n\n- Adds `.runtimeconfig.json` to the Nx workspace root `.gitignore` file (if not already added), since these files should not be version controlled\n\n- Adds an `emulate` target to the Nx-firebase app, which is used by `serve` but also allows Firebase emulators to be started independently of a watched build.\n\n**Plugin maintenance**\n\n- Executors use workspace logger routines instead of console\n- Fixed minor issues in e2e tests\n- Removed redundant/legacy firebase target\n- Replaced plugin use of node `join` with workspace `joinPathFragments`\n\n**Migration from v0.3.2**\n\nFor users with existing nx-firebase applications in their workspace you may wish to add the new version schema updates manually to your workspace configuration files.\n\nIn your `angular.json` or `workspace.json` file, for each `nx-firebase` app project:\n\n1. Add the `.runtimeconfig.json` to your build assets:\n\n```\n      \"targets\": {\n        \"build\": {\n          ...\n          \"options\": {\n            ...\n            \"assets\": [\n              ...\n              \"apps/nxfirebase-root-app/.runtimeconfig.json\"\n            ]\n          }\n        },\n```\n\n2. Add the new `emulate` target to your app:\n\n```\n      \"targets\": {\n        ...\n        \"emulate\": {\n          \"executor\": \"@nrwl/workspace:run-commands\",\n          \"options\": {\n            \"command\": \"firebase emulators:start --config firebase.nxfirebase-root-app.json\"\n          }\n        },\n```\n\n3. Modify the `serve` target to:\n\n```\n      \"targets\": {\n        ...\n        \"serve\": {\n          ...\n          \"options\": {\n            \"commands\": [\n              {\n                \"command\": \"nx run <appname>:build --with-deps && nx run <appname>:build --watch\"\n              },\n              {\n                \"command\": \"nx run <appname>:emulate\"\n              }\n            ],\n            \"parallel\": true\n          }\n        },\n```\n\n4. Add the new `getconfig` target:\n\n```\n      \"targets\": {\n        ...\n        \"getconfig\": {\n          \"executor\": \"@nrwl/workspace:run-commands\",\n          \"options\": {\n            \"command\": \"firebase functions:config:get --config firebase.<appname>.json > apps/<path-to-app>/.runtimeconfig.json\"\n          }\n        },\n        ...\n```\n\nAnd in your `firebase.<appname>.json` config settings for `\"emulators\"` add `\"auth\"` and `\"pubsub\"` configs:\n\n```\n    \"emulators\": {\n        ...\n        \"auth\": {\n            \"port\": 9099\n        },\n        \"pubsub\": {\n            \"port\": 8085\n        }\n    }\n```\n\n## v0.3.2 ![nx](https://img.shields.io/badge/Nx-v12.3.4-blue) ![nx](https://img.shields.io/badge/Node-v12-orange)\n\n\n- Plugin now detects incompatible Nx library dependencies and aborts compilation when found\n\nIncompatible dependencies are as follows:\n\n1. Non `--buildable` libraries\n2. Nested libraries that were not created with `--importPath`\n\nIf either of these two types of libraries are imported by Firebase functions, the compilation will be halted, since a functional app cannot be created with these types of dependencies.\n\nSee the [README](README.md#using-nx-libraries-within-nested-sub-directories) for more information.\n \n## v0.3.1 ![nx](https://img.shields.io/badge/Nx-v12.3.4-blue) ![nx](https://img.shields.io/badge/Node-v12-orange)\n\n- Removed undocumented/unusued `firebase` target in app generator. No longer needed.\n\n- `serve` target now builds `--with-deps` before watching to ensure all dependent local libraries are built. Note that `serve` only detects incremental changes to the main application, and not dependent libraries as well at this time.\n\n## v0.3.0 ![nx](https://img.shields.io/badge/Nx-v12.3.4-blue) ![nx](https://img.shields.io/badge/Node-v12-orange)\n\nProject has been renamed from `@simondotm/nxfirebase` to `@simondotm/nx-firebase` to better match Nx plugin naming conventions. Took a deep breath and did it early before many installs occurred. Apologies to any users who this may have inconvenienced - I didn't realise I could deprecate packages until after I'd deleted & renamed the pnm project. Rest assured, I won't be making any further major modifications like this!\n\nIf you have already generated NxFirebase applications using `@simondotm/nxfirebase` you will need to migrate as follows:\n\n1. `npm uninstall @simondotm/nxfirebase`\n2. `npm install @simondotm/nx-firebase --save-dev`\n3. Update the `builder` targets in any NxFirebase applications you already have in your `workspace.json` or `angular.json` config from `@simondotm/nxfirebase:build` to `@simondotm/nx-firebase:build`\n\n## v0.2.3 ![nx](https://img.shields.io/badge/Nx-v12.3.4-blue) ![nx](https://img.shields.io/badge/Node-v12-orange)\n\nBuilt against Nx 12.3.4\n\n**Updates**\n\n- `build` executor now supports `--watch` option for incremental builds\n\n- Added `serve` target to applications which will build the application with `--with-deps` and `--watch` options, and also launch the Firebase emulator with the application's firebase configuration in parallel\n\n- Added `deploy` target to applications. Supports Nx forwarded command line arguments so commands like `nx deploy <appname> --only functions` work fine\n\n- Default template function `index.ts` now has added import of `firebase-admin` to ensure all necessary Firebase package dependencies for functions are included out of the box\n\n**Fixes**\n\n- Default `firebase.appname.json` now has a valid default hosting configuration and apps ship with a template `public/index.html` (as generated by Firebase CLI) inside the application folder\n\n- Nx-Firebase App generator sets `target` in `tsconfig.app.ts` to `es2018` to ensure Node 10 compatibility for Firebase Functions (for scenarios where root workspace `tsconfig.base.json` may be set to a later ES target)\n\n- Fixed `predeploy` scripts in `firebase.appname.json` to use `npx nx` so that they work correctly in CI environments\n\n- Fixed default `firestore.rules` file to correct a typo\n\n- Fixed default `storage.rules` file to use version 2 ruleset\n\n- Plugin peer dependencies set so there's some indication of plugin compatibility\n\n**Migrating from apps generated with v0.2.2**\n\nv0.2.3 adds these targets to your `workspace.json` or `angular.json`, so for users of earlier versions of the plugin this will have to be done manually:\n\n```\n                \"serve\": {\n                    \"builder\": \"@nrwl/workspace:run-commands\",\n                    \"options\": {\n                        \"commands\": [\n                            {\n                                \"command\": \"nx run <appname>:build --with-deps && nx run <appname>:build --watch\"\n                            },\n                            {\n                                \"command\": \"firebase emulators:start --config firebase.<appname>.json\"\n                            }\n                        ],\n                        \"parallel\": true\n                    }\n                },\n                \"deploy\": {\n                    \"builder\": \"@nrwl/workspace:run-commands\",\n                    \"options\": {\n                        \"command\": \"firebase deploy --config firebase.<appname>.json\"\n                    }\n                },\n```\n\n## v0.2.2 - Initial Release ![nx](https://img.shields.io/badge/Nx-v12.1.1-blue) ![nx](https://img.shields.io/badge/Node-v12-orange)\n\nBuilt against Nx 12.1.1\n"
  },
  {
    "path": "README.md",
    "content": "# @simondotm/nx-firebase ![actions](https://github.com/simondotm/nx-firebase/actions/workflows/ci.yml/badge.svg) ![npm](https://img.shields.io/npm/v/@simondotm/nx-firebase) ![downloads](https://img.shields.io/npm/dw/@simondotm/nx-firebase.svg)\n\nA plugin for [Nx](https://nx.dev) that integrates Firebase workflows in an Nx monorepo workspace.\n\n* Easily generate Firebase applications and functions\n* Uses `esbuild` for fast Firebase function builds so you can easily create & import shared Nx libraries with the benefits of tree-shaking\n* Supports function environment variables and secrets\n* Supports single or multiple firebase projects/apps within an Nx workspace\n* Full support for the Firebase Emulator suite for local development, with watch mode for functions\n* Keeps your `firebase.json` configurations in sync when renaming or deleting Firebase apps & functions\n* Only very lightly opinionated about your Firebase configurations and workspace layouts; you can use Nx or the Firebase CLI\n\nSee [CHANGELOG](https://github.com/simondotm/nx-firebase/blob/main/CHANGELOG.md) for release notes.\n\n## Install Plugin\n\n**`npm install @simondotm/nx-firebase --save-dev`**\n\n- Installs this plugin into your Nx workspace\n- This will also install `@nx/node` and firebase SDK's to your root workspace `package.json` if they are not already installed\n\n## Generate Firebase Application\n\n**`nx g @simondotm/nx-firebase:app my-new-firebase-app [--directory=dir] [--project=proj]`**\n\n- Generates a new Nx Firebase application project in the workspace\n- The app generator will also create a Firebase configuration file in the root of your workspace (along with a default `.firebaserc` and `firebase.json` if they don't already exist)\n- For the first firebase application you create, the project firebase configuration will be `firebase.json`\n- If you create additional firebase applications, the project firebase configuration will be `firebase.<app-project-name>.json`\n- Use `--project` to link your Firebase App to a Firebase project name in your `.firebaserc` file\n\n## Generate Firebase Function\n\n**`nx g @simondotm/nx-firebase:function my-new-firebase-function --app=my-new-firebase-app [--directory=dir]`**\n\n- Generates a new Nx Firebase function application project in the workspace\n- Firebase Function projects must be linked to a Firebase application project with the `--app` option\n- Firebase Function projects can contain one or more firebase functions\n- You can generate as many Firebase Function projects as you need for your application\n\n## Build \n\n**`nx build my-new-firebase-app`**\n\n- Compiles & builds all Firebase function applications linked to the Nx Firebase application or an individual function\n\n**`nx build my-new-firebase-function`**\n\n- Compiles & builds an individual function\n\n\n## Serve\n\n**`nx serve my-new-firebase-app`**\n\n- Builds & Watches all Firebase functions apps linked to the Firebase application\n- Starts the Firebase emulators\n\n## Deploy\n\n### Firebase Application\n\n**`nx deploy my-new-firebase-app [--only ...]`**\n\n- By default, deploys ALL of your cloud resources associated with your Firebase application (eg. sites, functions, database rules etc.)\n- Use the `--only` option to selectively deploy (same as Firebase CLI)\n\nFor initial deployment:\n\n- **`firebase login`** if not already authenticated\n- **`firebase use --add`** to add your Firebase Project(s) to the `.firebaserc` file in your workspace. This step must be completed before you can deploy anything to Firebase.\n\nNote that you can also use the firebase CLI directly if you prefer:\n\n- **`firebase deploy --config=firebase.<appname>.json --only functions`**\n\n### Firebase Function\n\n**`nx deploy my-new-firebase-function`**\n\n- Deploys only a specific Firebase function\n\n\n\n## Test\n\n**`nx test my-new-firebase-app`**\n\n- Runs unit tests for all Firebase functions apps linked to the Firebase application\n\n**`nx test my-new-firebase-function`**\n\n- Runs unit tests for an individual function\n\n\n## Lint\n\n**`nx lint my-new-firebase-app`**\n\n- Runs linter for all Firebase functions apps linked to the Firebase application or an individual function\n\n**`nx lint my-new-firebase-function`**\n\n- Runs linter for an individual function\n\n## Sync Workspace\n\n**`nx g @simondotm/nx-firebase:sync`**\n\n- Ensures that your `firebase.json` configurations are kept up to date with your workspace\n  - If you rename or move firebase application or firebase function projects\n  - If you delete firebase function projects\n\n## Further Information\n\nSee the full plugin [User Guide](https://github.com/simondotm/nx-firebase/blob/main/docs/user-guide.md) for more details."
  },
  {
    "path": "docs/nx-firebase-applications.md",
    "content": "# Nx Firebase Applications\n\nAn Nx Firebase application project is a top-level container for the various configurations for Firebase features you might wish to use such as _functions_, _storage, firestore, and real time database_.\n\nYou don't have to use all of these features, but the Nx-Firebase plugin ensures they are all there if/when you do.\n\nGenerate a new Firebase application using:\n\n**`nx g @simondotm/nx-firebase:application`**\n\nOR\n\n**`nx g @simondotm/nx-firebase:app`**\n\n| Options           | Type     | Description                                                        |\n| ----------------- | -------- | ------------------------------------------------------------------ |\n| `name`            | required | the project name for your firebase app                             |\n| `--directory=dir` | optional | the full path where this app will be located (e.g., `apps/my-app`) |\n| `--project=proj`  | optional | the `--project` option that will be used for firebase CLI commands |\n\n## About Firebase Apps\n\nFirebase app projects are a customised Nx project.\n\nWhen a new Nx Firebase application project is generated in the workspace it will create:\n\n**Within the application folder:**\n\n- Default `firestore.indexes` for Firestore database indexes\n- Default `firestore.rules` for Firestore database rules\n- Default `database.rules.json` for Firebase realtime database\n- Default `storage.rules` for Firebase storage rules\n- Default `public/index.html` for Firebase hosting - _you can delete this if your firebase configuration for hosting points elsewhere_.\n- Default `public/404.html` for Firebase hosting - _you can delete this if your firebase configuration for hosting points elsewhere_.\n- Default [environment variables](./nx-firebase-functions-environment.md) for your firebase functions\n\n**And in the workspace root:**\n\n- A `firebase.json` configuration file for the Firebase application\n- This is preset with references to the various configuration files in the application folder\n- The plugin supports multiple firebase projects in one workspace, so if other firebase applications already exist in the workspace, the firebase configuration file will be named `firebase.<firebase-app-project>.json`\n\n**It will also generate:**\n\n- A default/empty `.firebaserc` in the root of the workspace (if it doesn't already exist)\n\nYou should use `npx firebase --add` to register your [projects & aliases](nx-firebase-projects.md) in the `.firebaserc`.\n\n## Nx-Firebase Application Project Targets\n\nThese targets will be generated in `project.json` for your new Firebase application:\n\n- `build` - Build all Firebase function applications linked to this Firebase application (if any)\n- `serve` - Build all functions in `watch` mode and start the Firebase Emulators\n- `deploy` - Run the Firebase CLI `deploy` command with the application's Firebase configuration. This target accepts forwarded command line options.\n- `lint` - Lint all Firebase function applications linked to this Firebase application\n- `test` - Run Jest unit tests for all Firebase function applications linked to this Firebase application\n- `getconfig` - Fetch the firebase remote config\n- `firebase` - Run the firebase CLI with the appropriate firebase `--config` and firebase `--project` parameters automatically provided\n"
  },
  {
    "path": "docs/nx-firebase-databases.md",
    "content": "# Firebase Databases\n\n`nx-firebase` assumes you may wish to use either realtime and/or firestore database.\n\n## Rules & Indexes\n\nTo keep the root of your Nx workspace tidy, the Nx-Firebase plugin puts default Firebase rules and index files inside the nx-firebase appliocation folder, and the `firebase.json` or `firebase.<app-name>.json` configuration file simply points to them there.\n\nAgain, this works just fine with the usual Firebase CLI command, eg:\n\n**`firebase deploy --only firestore:rules --config firebase.appname.json`**\n\nOr\n\n**`nx deploy appname --only firestore:rules`**\n\nThis is also useful for cleaner separation if you have multiple Firebase projects in your Nx workspace.\n\nAgain, you are free to modify these locations or remove these cloud features if you wish by simply changing the Firebase configuration files; the `nx-firebase` plugin does not use these configuration files in any way.\n"
  },
  {
    "path": "docs/nx-firebase-emulators.md",
    "content": "# Using Firebase Emulators\n\nThe Firebase emulators work well within Nx and `nx-firebase`.\n\nTo locally develop with the Firebase emulator suite:\n\n- **`nx serve <firebase-app-name>`**\n\nThis will launch the Firebase emulators using the app's firebase configuration\n\nIt will also build all functions attached to this app in watch mode, so that any changes you make to your function code will be automatically be rebuilt and reloaded by the emulator.\n\n## Nx Issue with Firebase Emulator\n\nDue to a bug in Nx, which does not correctly pass process termination signals to child processes, CTRL+C after `serve` does not shutdown the emulator properly.\n\nFor this reason, `serve` uses the `kill-port` npm package to ensure the emulator is properly shutdown before re-running `serve`.\n\nUnfortunately this means the emulator will not properly export data on exit either.\n\nThe Nx issue is discussed [here](https://github.com/simondotm/nx-firebase/issues/40).\n\nHopefully Nx will address this issue in future releases.\n\n## Emulator workaround\n\nUntil Nx fix this problem, we are using an experimental executor in the plugin as a interim workaround:\n\nThe `serve` target in Firebase app `project.json` configurations is using:\n\n- `@simondotm/nx-firebase:serve`\n\ninstead of\n\n- `nx:run-commands`\n\nThis custom executor handles CTRL+C in a way that ensures the Firebase Emulator shuts down cleanly.\n\nWith this executor you can pass extra CLI parameters for example:\n\n- `nx serve myfirebaseapp --only functions` - only serve functions in the Emulator\n"
  },
  {
    "path": "docs/nx-firebase-functions-environment.md",
    "content": "# Firebase Function Environment Variables\n\n## Overview\n\nFirebase functions can make use of environment variables in their runtime.\n\nThe Nx-firebase [application generator](./nx-firebase-applications.md) will automatically create default function environment files for you to use:\n\n- `environment/.env`\n- `environment/.env.local`\n- `environment/.secret.local`\n\nIf you run the `getconfig` target, it will place `.runtimeconfig.json` file in the `environment` folder also.\n\nThese environment files are considered common to all of your functions by nx-firebase, and are copied from your firebase app's `environment` folder to your function's `dist` folder when the function is built.\n\nThis ensures they are available for deployment and emulation.\n\nAll functions share the same environment variable files.\n\n## Environment file types\n\n| File                   | Description                                                                                                                                                                                     | Git Ignored | Deployed |\n| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -------- |\n| `.env`                 | [General environment variables for functions](https://firebase.google.com/docs/functions/config-env?gen=2nd#env-variables)                                                                      | -           | Yes      |\n| `.env.local`           | [Environment variable overrides for function emulation](https://firebase.google.com/docs/functions/config-env?gen=2nd#emulator_support)                                                         | -           | -        |\n| `.env.<project-alias>` | [Environment variable overrides for specific deployment targets (eg. dev/prod)](https://firebase.google.com/docs/functions/config-env?gen=2nd#deploying_multiple_sets_of_environment_variables) | -           | Yes      |\n| `.secret.local`        | [Secrets only for function emulation](https://firebase.google.com/docs/functions/config-env?gen=2nd#secrets_and_credentials_in_the_emulator)                                                    | Yes         | -        |\n| `.runtimeconfig.json`  | [Function configurations](https://firebase.google.com/docs/cli#functions-commands)                                                                                                              | Yes         | -        |\n\n> _Note that the Firebase team appear to be deprecating the use of `.runtimeconfig.json` function configs and recommending migration to dotenv environment variables._\n\n## Deployed Files\n\nThe firebase CLI will deploy `.env` and/or `.env.<project-id>` files along with function code, and they can be version controlled.\n\n## Non-deployed files\n\n`.env.local` and `.secret.local` files are excluded from deployment by using an `functions.ignore` rule in `firebase.json`.\n\n## Local Files\n\n`.secret.local` and `.env.local` are only used by the Firebase emulator suite.\n\n`.secret.local` and `.runtimeconfig.json` should not be version controlled, and are both git ignored, but included in Nx dependency graph using a `.nxignore` override.\n"
  },
  {
    "path": "docs/nx-firebase-functions.md",
    "content": "# Firebase Functions\n\n- [Firebase Functions](#firebase-functions)\n  - [Nx-Firebase Functions](#nx-firebase-functions)\n  - [Generating a new function](#generating-a-new-function)\n  - [Building a Firebase function](#building-a-firebase-function)\n  - [Firebase Function Dependencies \\& Package Managers](#firebase-function-dependencies--package-managers)\n  - [Deploying a Firebase function](#deploying-a-firebase-function)\n  - [Testing \\& Linting your firebase function](#testing--linting-your-firebase-function)\n  - [Managing Functions in your Workspace](#managing-functions-in-your-workspace)\n  - [Functions \\& Nx-Firebase Applications](#functions--nx-firebase-applications)\n    - [How Nx-Firebase function apps are linked to Nx-Firebase apps](#how-nx-firebase-function-apps-are-linked-to-nx-firebase-apps)\n    - [Functions \\& Firebase Config Codebases](#functions--firebase-config-codebases)\n    - [Functions \\& ESBuild](#functions--esbuild)\n    - [Using ES Modules output](#using-es-modules-output)\n    - [Using CommonJS output](#using-commonjs-output)\n    - [Why ESBuild?](#why-esbuild)\n    - [Why not minify?](#why-not-minify)\n  - [Node Runtimes for Firebase Functions](#node-runtimes-for-firebase-functions)\n\n## Nx-Firebase Functions\n\nNx-Firebase functions are generated as individual Nx application projects, separately to the Nx-Firebase application.\n\n* This allows for multiple functions to be generated in a single workspace, each with their own `src/main.ts` entry point and `package.json` file.\n\n* The default `src/main.ts` template is the same one generated by the Firebase CLI and defaults to using 2nd gen cloud functions. This file is of course just a starting point and can be modified as needed.\n\n* Each function project is a buildable Typescript node-based application, which is compiled and bundled using [esbuild](#functions--esbuild).\n\n* Each function project can export one or more firebase cloud functions.\n\n* Each function project must be linked to a single [Firebase application project](./nx-firebase-applications.md) in the workspace, which ensures all functions can be managed by the nx-firebase plugin.\n\n* Functions can import code from [Nx shared libraries](./nx-libraries.md) and `esbuild` will tree-shake and bundle the code into a single output file.\n\n* You rename or move Firebase function projects in your Nx workspace, and use the plugin [sync](./nx-firebase-sync.md) command to keep your `firebase.json` configuration file in sync.\n\n* Functions can be [tested](#testing--linting-your-firebase-function), [linted](#testing--linting-your-firebase-function), [built](#building-a-firebase-function) and [deployed](#deploying-a-firebase-function) using the Nx CLI\n\n[package dependencies](#firebase-function-dependencies-package-managers)\n\n* Functions support deployment with `npm`, `pnpm` or `yarn` package managers.\n\n\n## Generating a new function\n\nGenerate a new Firebase function using:\n\n- **`nx g @simondotm/nx-firebase:function`**\n\nOR\n\n- **`nx g @simondotm/nx-firebase:func`**\n\n| Options                     | Type          | Description                                                |\n| --------------------------- | ------------- | ---------------------------------------------------------- |\n| `name`                      | required      | the project name for your function                         |\n| `--app=<app-project-name>`  | required      | the firebase app this function will be a dependency of     |\n| `--directory=dir`           | optional      | the full path where this function will be located (e.g., `apps/my-function`) |\n| `--format=<'cjs' or 'esm'>` | default 'esm' | specify if esbuild should generated commonJs or ES6 output |\n| `--runTime=<node versions>` | optional      | the nodejs runtime you wish to use for this function - 18, 20, 22 |\n| `--tags`                    | optional      | tags to set on the new project                         |\n| `--setParserOptionsProject` | optional      | set the parserOptions.project in the tsconfig.json file |\n\n\n## Building a Firebase function\n\nTo build your firebase function use this command:\n\n- **`nx build your-firebase-function-project-name`**\n\nThis will use `esbuild` to compile & bundle the input function Typescript source code to:\n\n- `dist/apps/your-firebase-function-project-name/main.js` - The bundled function code, in a single ESM format output file\n- `dist/apps/your-firebase-function-project-name/package.json` - The ESM format package file for firebase CLI to process and deploy\n\n## Firebase Function Dependencies & Package Managers\n\nWhen building a function, Nx will automatically generate a `package.json` file in the output `dist` directory, which contains all package dependencies used by the function.\n\nNx will also generate a pruned `package-lock.json`, `yarn.lock` or `pnpm-lock.yaml` file in the function project root, depending on the package manager used in the workspace, which ensures your deployed function has exactly the same dependencies in the cloud as it does locally.\n\n## Deploying a Firebase function\n\nTo deploy all of your firebase function projects use:\n\n- **`nx deploy your-firebase-app-name --only:functions`**\n\nTo deploy one of your firebase function projects use this command:\n\n- **`nx deploy your-firebase-function-name`**\n\nTo deploy a single function from within a firebase function project that exports multiple functions, use this command:\n\n- **`nx deploy <codebase> --only functions:<codebase>:function-name`**\n\nWhere `<codebase>` is whatever name you gave your nx-firebase:function project.\n\n> **IMPORTANT NOTE: Newly generated function projects do not deploy out-of-the-box. This is because the default Firebase CLI template for `main.ts` does not include code to call `initalizeApp()` so you will need to add this yourself:**\n\n```\n   import { initializeApp } from \"firebase-admin/app\";\n   initializeApp()\n```\n\n## Testing & Linting your firebase function\n\n- **`nx test your-firebase-function-name`**\n- **`nx lint your-firebase-function-name`**\n\n## Managing Functions in your Workspace\n\nYou can use the parent Firebase app to build, test, lint, watch and deploy all of your functions at once.\n\n- **`nx build your-firebase-app-name`**\n- **`nx test your-firebase-app-name`**\n- **`nx lint your-firebase-app-name`**\n- **`nx watch your-firebase-app-name`**\n- **`nx deploy your-firebase-app-name`**\n\nNote that there is no `serve` target for individual function projects, since serving only makes sense at a firebase app level with the Firebase Emulator suite, so use the following command instead:\n\n- **`nx serve your-firebase-app-name`**\n\nSee [Firebase Application Targets](./nx-firebase-applications.md#nx-firebase-application-project-targets) for more details.\n\n## Functions & Nx-Firebase Applications\n\n### How Nx-Firebase function apps are linked to Nx-Firebase apps\n\nThe Nx-firebase plugin requires that Firebase function projects must always be a dependency of a single Firebase application project:\n\n- This approach allows for multiple firebase projects in a single Nx workspace\n- It ensures all functions can be [managed by the nx-firebase plugin](./nx-firebase-sync.md)\n- Function application projects are added as `implicitDependencies` to the parent Firebase application, which ensures we can test, lint, build & deploy all functions from the top level Firebase application\n- All functions share the same Firebase `--config` CLI option as the parent Firebase Application\n- All functions share the same Firebase `--project` CLI option as the parent Firebase Application\n- You can create as many Firebase function projects as you like\n- Firebase function apps can export either just one or multiple firebase cloud functions\n- When running the Firebase emulator using `serve`, **all** firebase function applications are built using `watch` mode, so local development is much more convenient\n\n### Functions & Firebase Config Codebases\n\nWhen new Firebase function applications are generated in the workspace:\n\n- They are automatically added to the `functions[]` declaration in the project's `firebase.json` config file using the firebase CLI's `codebase` feature\n- The `codebase` name assigned to the function in the config is the function applications project name.\n- When using firebase `deploy`, the CLI will deploy all `codebase`'s declared in the firebase config file\n\n### Functions & ESBuild\n\n`esbuild` is configured in the function's `project.json` to only bundle 'internal' source local to the workspace:\n\n- Import paths using TS aliases to `@nx/js` libraries will be resolved as internal imports.\n- All external imports from `node_modules` will be added to the `package.json` as dependencies, since there is no good reason to bundle `node_modules` in node applications.\n\n### Using ES Modules output\n\n`esbuild` is also configured by default to always output bundled code as `esm` format modules:\n\n- This ensures tree-shaking is activated in the bundling process\n- Firebase functions with Node 16 or higher runtime all support ES modules\n- The bundled output code in `dist` is _much_ cleaner to review\n- We are only specifying that the _output_ bundle is `esm` format. The input source code sent to `esbuild` is Typescript code, which effectively uses ES6 module syntax anyway\n- **Therefore, it is not necessary to change your workspace to use `esm` format modules to use this plugin since `esbuild` builds from Typescript _source code_, not compiled JS.**\n\n### Using CommonJS output\n\nIf you still use Node `require()` in your Typescript function code, the default `esm` output setting for `esbuild` may not work. Your options are:\n\n1. Refactor your code to use `import` instead of `require`\n2. Modify the function `project.json` to set esbuild `format` to `['cjs']`\n3. Generate your function applications with the `--format=cjs` option\n\nNote that using `cjs` output may prevent tree-shaking optimizations.\n\n\n### Why ESBuild?\n\nWhile Webpack and Rollup are viable options for bundling node applications:\n\n- `esbuild` is designed for node,\n- it is _very_ fast\n- it optimizes the output using tree-shaking, which is great for fast cold starts\n- and it works very simply out of the box with Nx without any need for additional configuration files.\n\nIf you want to try Webpack or Rollup, just change your `build` target in the function's `project.json` accordingly.\n\n> The Nx Webpack bundler may be required for projects that require Typescript decorators such as NextJS.\n\n### Why not minify?\n\nThis plugin does not set or recommend the minify option for esbuild.\n\n- It is not really necessary for cloud function node runtime environments\n- Obfuscation of the code is not necessary since it executes in a private server-side environment\n- There is minimal (if any) performance benefit to be had\n- If exceptions occur in the cloud run, stack traces will be readable if code is not minified\n\n## Node Runtimes for Firebase Functions\n\nFirebase Functions are deployed by the Firebase CLI to specific Nodejs runtime environments.\n\nThe required runtime is automatically set by the nx-firebase plugin function generator, but can be manually changedt in the `firebase.json` configuration as a definition (eg. `nodejs16`, `nodejs18` etc.):\n\n```\n  \"functions\": [\n    {\n      \"codebase\": \"firebase-project1\",\n      \"runtime\": \"nodejs16\",\n      ...\n\n    }\n  ],\n```\n\nRuntimes are recommended to be set to the same value for all functions in a project.\n"
  },
  {
    "path": "docs/nx-firebase-hosting.md",
    "content": "# Firebase Hosting\n\nIf you have one or more other web apps (Angular/React/HTML) that are deployed to a hosting site on your Firebase project, simply add them to your workspace as usual using the standard `nx g` app generators.\n\nThen just update your `firebase.json` or `firebase.appname.json` [hosting configuration](https://firebase.google.com/docs/hosting/full-config) to point to the `dist/apps/<webapp>` where your web app build output is.\n\nYou can then run the Firebase CLI as usual to deploy the site:\n\n**`firebase deploy --only hosting --config firebase.appname.json`**\n\nOr\n\n**`nx deploy appname --only hosting`**\n\n## Static Sites\n\nIf you deploy static websites to Firebase Hosting (that do not need to get built by Nx), just create a folder in your `apps` directory (rather than generate an Nx web app) and put your content in that folder. Then update the `hosting` section of your `firebase.json` or `firebase.appname.json` to simply point directly to this folder.\n\nThe firebase CLI hosting deploy command above will just upload the static content as required.\n\nAn application generated by Nx-Firebase is by default configured to host content in the `apps/appname/public` directory.\n"
  },
  {
    "path": "docs/nx-firebase-migrations.md",
    "content": "# Nx-Firebase Plugin Migrations\n\nNewer versions of the plugin occasionally need to update the workspace configurations, and this page documents the strategies available across these versions.\n\nPlease note that these migrations are provided on a 'best effort' basis, due to the fact that workspaces are quite complex and often customised.\n\n- [Nx-Firebase Plugin Migrations](#nx-firebase-plugin-migrations)\n  - [Migrating to plugin v2.1 from v2.0](#migrating-to-plugin-v21-from-v20)\n  - [Migration to plugin v2.x from v1.x](#migration-to-plugin-v2x-from-v1x)\n    - [1. Workspace Migration](#1-workspace-migration)\n    - [2. Firebase Project Migration](#2-firebase-project-migration)\n    - [3. Firebase Application Migration](#3-firebase-application-migration)\n    - [4. Firebase Functions Migration](#4-firebase-functions-migration)\n    - [5. Library updates](#5-library-updates)\n    - [6. Check Migration](#6-check-migration)\n\n## Migrating from plugin v2.0.0 to v2.1.0\n\nPlugin version 2.1 [added some new features](../CHANGELOG.md#v210) that required changes to the project configurations.\n\nTo help with this & future updates, an automatic migration generator has been added:\n\n- Update to the latest plugin using `npm i @simondotm/nx-firebase@latest --save-dev`\n- Run **npx nx g @simondotm/nx-firebase:migrate**\n\nThis tool will run checks on your workspace firebase apps, functions and configurations and try to ensure they are correctly configured for compatibility with the plugin version you are using.\n\n> Please note, this generator is not the same as Nx's own migration tool, so always review the changes it makes to ensure they are appropriate for your workspace.\n\n## Migration from plugin v1.x to v2.0.0\n\nVersion 2.x of this plugin has been completely rewritten, and uses a completely new approach to building & deploying, so migrating an existing Nx workspace using V1 of the plugin to use V2 requires some manual migration procedures.\n\n### 1. Workspace Migration\n\n- First of all, your workspace will need to be migrated to at least Nx 16.1.1.\n\n- Next, update the `@simondotm/nx-firebase` plugin package to the latest v2.x version.\n\n### 2. Firebase Project Migration\n\nRun the following steps 3-5 in order, for each separate Firebase application project in your workspace.\n\n### 3. Firebase Application Migration\n\n- Next, you can either [generate a new firebase v2 application](./nx-firebase-applications.md) project and copy across your Firebase rules, indexes, storage rules etc. to the new firebase application project folder\n\nOR\n\n- you can manually modify your existing Firebase `project.json` to be structured [as shown here](./nx-firebase-project-structure.md#firebase-applications).\n\n### 4. Firebase Functions Migration\n\nIf you are using Firebase functions in your project, the migration process is as follows:\n\n- Update the `\"functions\"` section in the `firebase.json` or `firebase.project-name.json` config file for your firebase application project to be set to `\"functions\": []`\n\n- Generate a new [Firebase functions application](./nx-firebase-functions.md) in your workspace, using the `--app` parameter set to the name of your Firebase application above\n\n- Move the source code from your old Nx Firebase application `functions/src` folder to the `src` folder in the newly created Firebase function application\n\n- You will need to rename your entry point source file from `index.ts` to `main.ts`\n- If you migrated your Firebase application in place (as described above), you will need to manually delete the following from your Firebase application folder:\n\n  - `src` folder\n  - All `tsconfig.*.json` files\n  - `package.json` file\n\n- OR, if you migrated by creating a new firebase application, you can now simply delete the old v1 Firebase application project\n\nCheck the [Nx-Firebase project schemas](./nx-firebase-project-structure.md) document for more information about the v2 plugin generators project layouts.\n\n### 5. Library updates\n\nThe previous version of the plugin required that all Nx libraries imported by firebase functions were buildable.\n\nWith v2 of the plugin, this is no longer the case and Nx libraries can be buildable or non-buildable, since `esbuild` builds from Typescript source files, not compiled JS.\n\nNx Typescript libraries can be converted to non-buildable by simply removing the `build` target from their `project.json` files.\n\n### 6. Check Migration\n\nRun `nx build your-firebase-project-name` to compile & bundle your functions.\n\nRun `nx deploy your-firebase-project-name` to deploy your project.\n"
  },
  {
    "path": "docs/nx-firebase-project-structure.md",
    "content": "# Nx-Firebase schemas (plugin v2)\n\n- [Nx-Firebase schemas (plugin v2)](#nx-firebase-schemas-plugin-v2)\n  - [Overview](#overview)\n  - [Firebase Application Projects](#firebase-application-projects)\n  - [Firebase Function Projects](#firebase-function-projects)\n  - [Firebase Function Configs](#firebase-function-configs)\n\n## Overview\n\nWhilst Nx Firebase plugin provides convenient generators for firebase apps and functions, it is perfectly possible to manually create nx projects yourself with the same functionality.\n\nThe `project.json` and `firebase.json` config schemas are below.\n\n## Firebase Application Projects\n\nFirebase Application projects are a useful way to group firebase resources and provide common functionality such as deploy, or build.\n\nWe add all function app projects as implicit dependencies, so that building the app project will automatically build function dependents.\n\nWe tag all dependent projects so that we can use Nx `run-many` with tag specifiers for buid actions like `watch`, `test` and `lint`.\n\n```\n{\n  \"name\": \"your-firebase-app-project-name\",\n  \"$schema\": \"../../node_modules/nx/schemas/project-schema.json\",\n  \"sourceRoot\": \"apps/your-firebase-app-project-name\",\n  \"projectType\": \"application\",\n  \"implicitDependencies\": [\n    \"your-firebase-function-project-name\",\n  ],\n  \"targets\": {\n    \"build\": {\n      \"executor\": \"nx:run-commands\",\n      \"options\": {\n        \"command\": \"echo Build succeeded.\"\n      }\n    },\n    \"watch\": {\n      \"executor\": \"nx:run-commands\",\n      \"options\": {\n        \"command\": \"nx run-many --targets=build --projects=tag:firebase:dep:your-firebase-app-project-name --parallel=100 --watch\"\n      }\n    },\n    \"lint\": {\n      \"executor\": \"nx:run-commands\",\n      \"options\": {\n        \"command\": \"nx run-many --targets=lint --projects=tag:firebase:dep:your-firebase-app-project-name --parallel=100\"\n      }\n    },\n    \"test\": {\n      \"executor\": \"nx:run-commands\",\n      \"options\": {\n        \"command\": \"nx run-many --targets=test --projects=tag:firebase:dep:your-firebase-app-project-name --parallel=100\"\n      }\n    },\n    \"firebase\": {\n      \"executor\": \"nx:run-commands\",\n      \"options\": {\n        \"command\": \"firebase --config=<your-firebase-project-config> --project=<your-firebase-projectid>\"\n      },\n      \"configurations\": {\n        \"production\": {\n          \"command\": \"firebase --config=<your-firebase-project-config> --project=<your-firebase-production-projectid>\"\n        }\n      }\n    },\n    \"killports\": {\n      \"executor\": \"nx:run-commands\",\n      \"options\": {\n        \"command\": \"kill-port --port 9099,5001,8080,9000,5000,8085,9199,9299,4000,4400,4500\"\n      }\n    },\n    \"getconfig\": {\n      \"executor\": \"nx:run-commands\",\n      \"options\": {\n        \"command\": \"nx run your-firebase-app-project-name:firebase functions:config:get > apps/your-firebase-app-project-name/environment/.runtimeconfig.json\"\n      }\n    },\n    \"emulate\": {\n      \"executor\": \"nx:run-commands\",\n      \"options\": {\n        \"commands\": [\n          \"nx run your-firebase-app-project-name:killports\",\n          \"nx run your-firebase-app-project-name:firebase emulators:start --import=apps/your-firebase-app-project-name/.emulators --export-on-exit\"\n        ],\n        \"parallel\": false\n      }\n    },\n    \"serve\": {\n      \"executor\": \"@simondotm/nx-firebase:serve\",\n      \"options\": {\n        \"commands\": [\n          \"nx run your-firebase-app-project-name:watch\",\n          \"nx run your-firebase-app-project-name:emulate\"\n        ]\n      }\n    },\n    \"deploy\": {\n      \"executor\": \"nx:run-commands\",\n      \"dependsOn\": [\n        \"build\"\n      ],\n      \"options\": {\n        \"command\": \"nx run your-firebase-app-project-name:firebase deploy\"\n      }\n    }\n  },\n  \"tags\": [\n    \"firebase:app\",\n    \"firebase:name:your-firebase-app-project-name\"\n  ]\n}\n```\n\n## Firebase Function Projects\n\nFunction projects can export as many firebase functions as you like.\n\nFunctions use `esbuild` to compile & bundle the code.\n\n```\n{\n  \"name\": \"your-firebase-function-project-name\",\n  \"$schema\": \"../../node_modules/nx/schemas/project-schema.json\",\n  \"sourceRoot\": \"apps/your-firebase-function-project-name/src\",\n  \"projectType\": \"application\",\n  \"targets\": {\n    \"build\": {\n      \"executor\": \"@nx/esbuild:esbuild\",\n      \"outputs\": [\"{options.outputPath}\"],\n      \"options\": {\n        \"outputPath\": \"dist/apps/your-firebase-function-project-name\",\n        \"main\": \"apps/your-firebase-function-project-name/src/main.ts\",\n        \"tsConfig\": \"apps/your-firebase-function-project-name/tsconfig.app.json\",\n        \"assets\": [\n          \"apps/your-firebase-function-project-name/src/assets\",\n          { \"glob\": \"**/*\", \"input\": \"apps/your-firebase-app-project-name/environment\", \"output\": \".\"},\n        ],\n        \"generatePackageJson\": true,\n        \"platform\": \"node\",\n        \"bundle\": true,\n        \"thirdParty\": false,\n        \"dependenciesFieldType\": \"dependencies\",\n        \"target\": \"node16\",\n        \"format\": [\"esm\"],\n        \"esbuildOptions\": {\n          \"logLevel\": \"info\"\n        }\n      }\n    },\n    \"lint\": {\n      \"executor\": \"@nx/eslint:eslint\",\n      \"outputs\": [\"{options.outputFile}\"],\n      \"options\": {\n        \"lintFilePatterns\": [\"apps/your-firebase-function-project-name/**/*.ts\"]\n      }\n    },\n    \"test\": {\n      \"executor\": \"@nx/jest:jest\",\n      \"outputs\": [\"{workspaceRoot}/coverage/{projectRoot}\"],\n      \"options\": {\n        \"jestConfig\": \"apps/your-firebase-function-project-name/jest.config.ts\",\n        \"passWithNoTests\": true\n      },\n      \"configurations\": {\n        \"ci\": {\n          \"ci\": true,\n          \"codeCoverage\": true\n        }\n      }\n    },\n    \"deploy\": {\n      \"executor\": \"nx:run-commands\",\n      \"options\": {\n        \"command\": \"nx run your-firebase-app-project-name:deploy --only functions:your-firebase-function-project-name\"\n      },\n      \"dependsOn\": [\"build\"]\n    }\n  },\n  \"tags\": [\n    \"firebase:function\",\n    \"firebase:name:your-firebase-function-project-name\",\n    \"firebase:dep:your-firebase-app-project-name\"\n  ]\n}\n\n```\n\n## Firebase Function Configs\n\nWe make use of the [codebase](https://firebase.google.com/docs/functions/organize-functions?gen=2nd#organize_functions_in_codebases) feature to identify each firebase function project using the exact same name as the Nx project name for the function.\n\nFor each function project, `firebase.json` will need the following entries in the `functions` config array.\n\n```\nfunctions: [\n    {\n      \"codebase\": \"your-function-project-name\",\n      \"source\": \"dist/apps/your-function-project-name\",\n      \"runtime\": \"nodejs16\",\n      \"ignore\": [ \"*.local\" ]\n    }\n]\n\n```\n"
  },
  {
    "path": "docs/nx-firebase-projects.md",
    "content": "# Firebase CLI Projects\n\n- [Firebase CLI Projects](#firebase-cli-projects)\n  - [Introduction](#introduction)\n  - [Nx Workspaces With Single Firebase Projects](#nx-workspaces-with-single-firebase-projects)\n  - [Nx Workspaces With Multiple Firebase Projects](#nx-workspaces-with-multiple-firebase-projects)\n  - [Updating Firebase Configurations](#updating-firebase-configurations)\n  - [Binding Nx projects to Firebase projects](#binding-nx-projects-to-firebase-projects)\n  - [Deployment Environments](#deployment-environments)\n\n## Introduction\n\n[Firebase projects](https://firebase.google.com/docs/projects/learn-more) are created in the firebase web console, and then specified in your local workspace configurations as deployment targets in your `.firebaserc` file.\n\n`nx-firebase` assumes a mapping of one `@simondotm/nx-firebase:app` to one firebase CLI project and one [Firebase configuration file](./nx-firebase-project-structure.md#firebase-function-configs).\n\n## Nx Workspaces With Single Firebase Projects\n\nThe first time you run `nx g @simondotm/nx-firebase:app` in an Nx workspace, it will generate a firebase configuration file called `firebase.json` in the workspace root. If you only use a single Firebase project in your Nx workspace, this will be all you need.\n\nThe Firebase CLI will use this configuration file by default, and in this scenario there's no need to pass the additional `--config` CLI option.\n\n## Nx Workspaces With Multiple Firebase Projects\n\nThis plugin supports multiple Firebase Applications/Projects inside one Nx workspace.\n\nIf you run `nx g @simondotm/nx-firebase:app` in an Nx workspace that already has a `firebase.json` configuration file, it will generate a file called `firebase.<appname>.json` configuration in the Nx workspace root which can then be used with any Firebase CLI command by using the `--config <config>` [CLI option](https://firebase.google.com/docs/cli#initialize_a_firebase_project).\n\n## Updating Firebase Configurations\n\nOnce your Nx Firebase application has been initially generated you are free to change the firebase configurations however you like.\n\nThe Firebase CLI usually warns you anyway if you try to deploy a feature that isn't yet enabled on your Firebase Project console.\n\n## Binding Nx projects to Firebase projects\n\nWhen using multiple Firebase projects in a workspace, remember that there is only one `.firebaserc` file to contain aliases for all of your deployment targets.\n\nYou can add projects using `firebase use --add` as normal.\n\nIt's fine to add multiple Firebase projects to your workspace `.firebaserc` file, but it is important to remember to correctly switch between them using `firebase use <alias>` before any deployments!\n\nYou can ensure this is always the case for commands like `nx deploy app` etc. by adding `--project <alias>` to any firebase commands in your nx-firebase application's targets.\n\nSee also: [Changing Firebase CLI Project](./nx-firebase-sync.md#changing-firebase-cli-project)\n\n## Deployment Environments\n\nA common practice with Firebase is to generate different Firebase projects for each deployment environments (such as dev / staging / production etc.)\n\nThis can be manually achieved with `nx-firebase` by adding `production` configurations to the `firebase` target in your Nx firebase application `project.json` file eg.:\n\n```\n    firebase: {\n      executor: 'nx:run-commands',\n      options: {\n        command: `firebase --config firebase.json --project your-dev-firebase-project`,\n      },\n      configurations: {\n        production: {\n          command: `firebase --config firebase.json --project your-prod-firebase-project`,\n        },\n      },\n    },\n```\n\nYou can now run:\n\n- `nx deploy my-firebase-app` for dev deployment\n- `nx deploy my-firebase-app --prod` for production deployment\n\nNote that `your-dev-firebase-project` and `your-prod-firebase-project` must exist as [aliases](https://firebase.google.com/docs/cli#project_aliases) in your `.firebaserc` file for this to work.\n"
  },
  {
    "path": "docs/nx-firebase-sync.md",
    "content": "# Firebase Sync\n\n- [Firebase Sync](#firebase-sync)\n  - [Nx-Firebase Sync Generator](#nx-firebase-sync-generator)\n  - [Renaming Nx-Firebase Projects](#renaming-nx-firebase-projects)\n    - [Renamed firebase application projects](#renamed-firebase-application-projects)\n    - [Renamed firebase function projects](#renamed-firebase-function-projects)\n  - [Deleting Nx-Firebase Projects](#deleting-nx-firebase-projects)\n    - [Deleted firebase applications](#deleted-firebase-applications)\n    - [Deleted firebase functions](#deleted-firebase-functions)\n  - [Changing Firebase CLI Project](#changing-firebase-cli-project)\n  - [Nx-Firebase Project Tags Reference](#nx-firebase-project-tags-reference)\n    - [Tag Descriptions](#tag-descriptions)\n\n## Nx-Firebase Sync Generator\n\nWithin an Nx workspace, Nx-firebase supports:\n\n- Multiple firebase application projects\n- Multiple firebase function projects attached to the firebase applications\n\nDeleting or renaming any of these projects requires various project & firebase configurations to be updated to ensure that targets & deployments still work.\n\nTo help manage these scenarios, Nx-firebase has a `sync` generator to automate this work:\n\n**`nx g @simondotm/nx-firebase:sync`**\n\nRun this command as soon as possible after any of these operations to ensure your Nx Firebase workspace is kept in sync:\n\n- When Firebase application or function projects have been renamed\n- When Firebase application or function projects have been deleted\n\nThe sync generator also runs some checks on your hosting configurations to validate & warn if there are any `public` paths that can't be matched to a Nx project in your workspace.\n\nYou can also use the `sync` generator to [change the Firebase project](#changing-firebase-cli-project) for an Nx Firebase App.\n\n## Renaming Nx-Firebase Projects\n\nNx projects such as Firebase apps and Firebase function apps can be renamed using the `nx g move` generator.\n\nRun the `nx g @simondotm/nx-firebase:sync` generator after the rename.\n\n### Renamed firebase application projects\n\n- Functions that are dependencies of the renamed firebase app will have their `deploy` target automatically updated to run this target from the newly renamed firebase application project\n\n- Firebase applications with config files named as `firebase.my-project.json` that are used by the renamed app will also be renamed to match the new project name.\n\n- The `firebase:name:<old-project-name>` tag on the Firebase application's `project.json` will be updated to `firebase:name:<new-project-name>`\n\n- Paths to rules and indexes for Firebase database, storage and firestore are updated in the `firebase.json` config\n\n### Renamed firebase function projects\n\n- Nx automatically updates `implicitDependencies` for renamed dependency projects (Firebase function apps are dependencies of Firebase applications)\n\n- The `codebase` for the function in the firebase config will be renamed to match the new function name\n\n- The `firebase:name:<old-project-name>` tag on the Firebase function's `project.json` will be updated to `firebase:name:<new-project-name>`\n\n## Deleting Nx-Firebase Projects\n\nNx projects such as Firebase apps and Firebase function apps can be deleted using the `nx g remove` generator.\n\nRun the `nx g @simondotm/nx-firebase:sync` generator after the deletion.\n\n> Note that when deleting a firebase function project, you may need to use the additional `--forceRemove` option, since Firebase function apps are implicit dependencies of Firebase apps and Nx will warn about this.\n\n### Deleted firebase applications\n\n- The Firebase config file linked to the deleted Firebase application project will be deleted\n- Any Functions that were dependencies of the deleted firebase app will be reported as orphaned\n- Orphaned functions can be either deleted, or they can be attached to another Firebase application project by:\n  - Changing the `firebase:dep:<old-project>` tag to `firebase:dep:<new-project>`\n  - Adding `<new-project>` to the `implicitDependencies` array in the new firebase app `project.json` file\n\n### Deleted firebase functions\n\n- Nx automatically updates `implicitDependencies` for deleted dependency projects (Firebase function apps are dependencies of Firebase applications)\n- The deleted firebase function will also be deleted from the `firebase.json` config file\n\n## Changing Firebase CLI Project\n\nNx Firebase applications have a `firebase` target that runs the Firebase CLI & specifies which `firebase.json` configuration is linked to this app (`--config`), and also which Firebase project it should use when being deployed (`--project`).\n\nYou can use the sync generator update the firebase project that is sent to the firebase CLI `--project` option for your Firebase application Nx project as follows:\n\n**`nx g @simondotm/nx-firebase:sync --app=<firebase-app-project-name> --project=<firebase-cli-project-name>`**\n\nAll firebase commands used by the Nx project targets such as `deploy` will now send `--project=<firebase-project-name>` to the firebase CLI.\n\n## Nx-Firebase Project Tags Reference\n\nVarious tags are added to projects generated by the plugin to help automatically detect changes in the firebase workspace and manage the `project.json` and `firebase.json` configuration files automatically.\n\n### Tag Descriptions\n\n- `firebase:app` - All firebase application projects have this tag\n- `firebase:function` - All firebase function projects have this tag\n- `firebase:name:<project-name>` - All firebase application & function projects have this tag\n- `firebase:dep:<app-project-name>` - All firebase function projects have this tag\n\nMore details:\n\n- The `firebase:dep` tag allows the `sync` generator to keep track of which firebase application project this function is associated with. It also allows us to `nx run-many` using tag specifiers when running `build`, `test`, `lint` or `watch` targets.\n\n- The `firebase:name` tag allows the `sync` generator to detect when projects are renamed.\n\n- The `firebase:app` and `firebase:function` tags simply allow us to easily identify Firebase projects in the workspace.\n\nThese tags work alongside any additional tags you might want to add to your Nx projects.\n"
  },
  {
    "path": "docs/nx-libraries.md",
    "content": "# Using Nx Libraries with Firebase Functions\n\nNx-Firebase supports use of Nx Libraries within functions code.\n\n## Creating a library\n\nTo use a shared library with an Nx Firebase Function Application, simply create a Typescript Nx node library in your workspace:\n\n**`nx g @nx/js:lib mylib --importPath=\"@myorg/mylib [--bundler=<bundler>]`**\n\n> _Note: The `--importPath` option is highly recommended to ensure the correct typescript aliases and npm package configurations for your library._\n\n\n## Importing a library\n\nYou can now:\n\n`import { stuff } from '@myorg/mynodelib'`\n\nin your Firebase functions code as you'd normally expect.\n\n## Building with libraries\n\nYou can then build your Firebase application or function with:\n\n**`nx build <firebase-app-name>`**\n\nOR\n\n**`nx build <firebase-function-name>`**\n\n## Nx Library Notes\n\n[Firebase functions](./nx-firebase-functions.md) use `esbuild` to bundle function code, we gain a few benefits:\n\n- Nx takes care of ensuring all necessary dependencies will be also built.\n- It does not matter if libraries are buildable or non-buildable, as `esbuild` builds from Typescript sources, however buildable libraries may be preferred since `esbuild` does not do type checking of imported libraries.\n- We do not have to worry about how we structure libraries anymore for optimal function runtime.\n- For instance, we can use barrel imports freely, since `esbuild` will treeshake unused code and inline imports into the bundled output `main.js`\n- We can create as many libraries as we wish for our functions to use, and organise them in whatever makes most sense for the workspace\n- For cleaner code sharing, Firebase function applications can simply import a library module containing the firebase function export/implementation\n"
  },
  {
    "path": "docs/nx-migration.md",
    "content": "# Migrating an existing Firebase project to Nx\n\nTo bring an existing Firebase project into an Nx workspace, simply [generate the Nx Firebase application(s)](./nx-firebase-applications.md) first, and then just overwrite the generated Firebase configuration & rules/indexes with your existing `firebase.json`, rules & index files etc..\n\nSee also [Nx Versions](nx-versions.md) for further information on specific Nx version migrations.\n\nFor your Firebase functions, [generate a function application](./nx-firebase-functions.md) and copy your existing source code to this new project `src` directory.\n\nFrom here, you can simply `nx build` and `nx deploy` your firebase application & functions.\n\n## Firebase SDK versions\n\nWhich version of the Firebase CLI + SDK's you use will depend on your particular project requirements, and this plugin is not opinionated to any particular Firebase SDK.\n\nThe `nx-firebase` plugin app generator will install Firebase dependencies in the workspace if they are not already present, but it does not require, enforce or change a specific version beyond that initial setup, so you are free to install whichever versions of the firebase SDK packages you need.\n"
  },
  {
    "path": "docs/nx-plugin-commands.md",
    "content": "# Nx Plugin Development\n\nNotes mainly for my own benefit/reminder here.\n\n## To create the plugin workspace\n\n- `npx create-nx-plugin simondotm --pluginName nx-firebase`\n\n## To build the plugin\n\n- `nx run nx-firebase:build`\n\n## To test the plugin\n\n- `nx run nx-firebase:test`\n\n## To run end-to-end tests for the plugin\n\n- `nx run nx-firebase-e2e:e2e`\n- This creates a temporary workspace in `/tmp/nx-e2e/...`\n- After the e2e test has completed, the plugin can be further manually tested in this temporary workspace.\n\n## To reformat the project\n\nFor example, after changing `.prettierrc` settings\n\n- `nx format:write --all`\n\n**Unit Tests**\n\nI've not implemented a full set of unit tests yet, but the e2e tests do perform a few standard tests.\n\n> **Note**: I created this plugin to primarily serve my own needs, so feature requests may take a bit of time to consider, but feedback and collaboration is very welcome for this project, since the Nrwl Nx team have an extremely rapid release cadence and it's sometimes hard to keep up with all the changes!\n"
  },
  {
    "path": "docs/nx-setup-mac.md",
    "content": "# Nx Development Setup Guide (Mac)\n\nClean installation steps for the whole setup of node/npm/nvm/nx, assuming mac with `zsh` shell.\n\nRecommendations:\n\n- Remove any pre-existing installations of node/npm/nvm/nx for a clean setup\n- This guide recommends use of `nvm` to enable management of node versions which allows much easier switching between different node versions if you work across different projects with different dependencies\n\n## 1. Install `antigen`\n\n- Install [antigen](https://github.com/zsh-users/antigen) via [brew](https://formulae.brew.sh/formula/antigen) - `brew install antigen`\n- Add `antigen` to your `~/.zshrc` file:\n\n```\nsource $(brew --prefix)/share/antigen/antigen.zsh\n```\n\n- Restart your shell/terminal\n- If you [get 'Antigen: Another process in running.' on shell startup](https://github.com/zsh-users/antigen/issues/543) just try closing & restarting your terminal/shell\n\n## 2. Install `nvm` using the `zsh-nvm` plugin\n\n- Use the excellent [zsh-nvm plugin](https://github.com/lukechilds/zsh-nvm) for nvm to make life easier (do not install `nvm` using brew)\n- Add the following to your `~/.zshrc` file:\n\n```\nexport NVM_DIR=\"$HOME/.nvm\"\nantigen bundle lukechilds/zsh-nvm\nantigen apply\n```\n\n- Add any extra `zsh-nvm` options to your `~/.zshrc` file, such as:\n\n```\nexport NVM_LAZY_LOAD=true\nexport NVM_AUTO_USE=true\n```\n\n- The finished `~/.zshrc` file looks something like this:\n\n```\nsource $(brew --prefix)/share/antigen/antigen.zsh\nexport NVM_DIR=\"$HOME/.nvm\"\nexport NVM_LAZY_LOAD=true\nexport NVM_AUTO_USE=true\nantigen bundle lukechilds/zsh-nvm\nantigen apply\n```\n\n- Restart your shell/terminal and verify installation using the `nvm --version`\n- Upgrade `nvm` using `nvm upgrade`\n\n## 3. Install `node`\n\n- `nvm install <nodeversion>` eg. `nvm install 19.1.0`\n- This will install node and a compatible version of `npm`/`npx` to `~/.nvm/versions/node/19.1.0/...`\n- `echo $path` should now show `~/.nvm/versions/node/19.1.0/bin`\n- `node`, `npm` and `npx` should now be accessible from the terminal/shell prompt\n\n## 4. Installing multiple `node` versions (optional)\n\n- Multiple node versions can be installed with `nvm install x.y.z`\n- `nvm install node` will install the latest version\n- Switch between node versions using `nvm use <version>`\n- If your project requires a specific node version, add a `.nvmrc` file to the root of your project containing the text version required\n- Remember that `npm` packages installed with `-g` are installed to the currently selected `node` version only, if you switch version, you may have to reinstall a global package for that version\n- `nvm list` will show all installed versions\n- `nvm current` will show the currently selected version\n- `nvm default <version>` will select a version that you like to be the default when you run `nvm use default`\n\n## 5. Install `nx`\n\n- Select the node version you want to install `nx` to: `nvm use <version>`\n- `npm i -g nx`\n- The previous command ensures that `nx` command line can be run without `npx`\n- Not sure when this became a thing, nor sure if the nx cli is still required as a global install, but it works.\n- `nx` should now be accessible from the terminal/shell prompt\n\n## 6. Visual Studio Code\n\nRecommended extensions for Nx development:\n\n- [VS Code ESLint extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)\n- [Prettier Formatter for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)\n- [Nx Console](https://marketplace.visualstudio.com/items?itemName=nrwl.angular-console)\n- [vscode-jest-runner](https://marketplace.visualstudio.com/items?itemName=firsttris.vscode-jest-runner)\n"
  },
  {
    "path": "docs/nx-workspace-layout.md",
    "content": "# Nx-Firebase Workspace Layout\n\nFirebase applications and functions can be generated in whichever directories you like.\n\nWhile there are plenty of ways to organise your workspace layout, one suggestion is:\n\n```\n/apps\n    /project1\n        /firebase\n        /functions\n          /function1\n          /function2\n        /web\n            /site1-app\n            /site2-app\n        /mobile\n            /app\n    /project2\n        /firebase\n        /functions\n        /web\n        ...\nfirebase.rc\nfirebase.json\nfirebase.project2.json\n...\n```\n\n## Organising functions\n\nSince [firebase function projects](./nx-firebase-functions.md) are apps, and nx-firebase supports multiple functions for each firebase app, there is plenty of flexibility for how you wish to develop your cloud functions.\n\n1. Just have one function Nx app project that exports all of the cloud functions you need\n2. Have multiple Nx function app projects which group and export multiple cloud functions by common functionality\n3. Have one Nx function app project per cloud function\n\nOption 1 is the simplest approach.\n\nHowever, over time as your project grows you may find that options 2 or 3 bring organisational benefits, (and also potentially code start time optimisations as separated function app projects will only import the code they need to compile).\n\nFirebase CLI also has capability to check for unchanged function code when deploying, so having more codebases can also reduce the deployment time for your functions.\n"
  },
  {
    "path": "docs/user-guide.md",
    "content": "# User Guide\n\n**Nx Firebase Generators**\n\n- [Firebase Applications](./nx-firebase-applications.md)\n- [Firebase Functions](./nx-firebase-functions.md)\n- [Firebase Functions - Environment Variables](./nx-firebase-functions-environment.md)\n\n**Nx Firebase**\n\n- [Firebase Hosting](./nx-firebase-hosting.md)\n- [Firebase Emulators](./nx-firebase-emulators.md)\n- [Firebase Databases](./nx-firebase-databases.md)\n- [Firebase Projects](./nx-firebase-projects.md)\n\n**Nx Firebase Workspace Management**\n\n- [Nx-Firebase Sync](./nx-firebase-sync.md)\n- [Nx-Firebase Project Schemas](./nx-firebase-project-structure.md)\n- [Migrating to new plugin versions](./nx-firebase-migrations.md)\n  \n**Nx Workspace**\n\n- [Nx Workspace Layout Ideas](./nx-workspace-layout.md)\n- [Using Nx Libraries with Firebase Functions](./nx-libraries.md)\n- [Migrating an existing Firebase project to Nx](./nx-migration.md)\n\n**Notes**\n\n- [Plugin Development Notes](./nx-plugin-commands.md)\n- [Nx Development Setup for Mac](./nx-setup-mac.md)"
  },
  {
    "path": "e2e/compat/eslint.config.mjs",
    "content": "import baseConfig from '../../eslint.config.mjs'\n\nexport default [\n  {\n    ignores: ['**/dist'],\n  },\n  ...baseConfig,\n  {\n    files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],\n    // Override or add rules here\n    rules: {},\n    languageOptions: {\n      parserOptions: {\n        project: ['e2e/compat/tsconfig.*?.json'],\n      },\n    },\n  },\n  {\n    files: ['**/*.ts', '**/*.tsx'],\n    // Override or add rules here\n    rules: {},\n  },\n  {\n    files: ['**/*.js', '**/*.jsx'],\n    // Override or add rules here\n    rules: {},\n  },\n]\n"
  },
  {
    "path": "e2e/compat/jest.config.ts",
    "content": "/* eslint-disable */\nexport default {\n  displayName: 'compat',\n  preset: '../../jest.preset.js',\n  globals: {},\n  testEnvironment: 'node',\n  transform: {\n    '^.+\\\\.[tj]s$': [\n      'ts-jest',\n      {\n        tsconfig: '<rootDir>/tsconfig.spec.json',\n      },\n    ],\n  },\n  moduleFileExtensions: ['ts', 'js', 'html'],\n}\n"
  },
  {
    "path": "e2e/compat/project.json",
    "content": "{\n  \"name\": \"compat\",\n  \"$schema\": \"../../node_modules/nx/schemas/project-schema.json\",\n  \"sourceRoot\": \"e2e/compat/src\",\n  \"projectType\": \"application\",\n  \"tags\": [],\n  \"targets\": {\n    \"build\": {\n      \"executor\": \"@nx/webpack:webpack\",\n      \"outputs\": [\"{options.outputPath}\"],\n      \"options\": {\n        \"outputPath\": \"dist/e2e/compat\",\n        \"main\": \"e2e/compat/src/main.ts\",\n        \"tsConfig\": \"e2e/compat/tsconfig.app.json\",\n        \"assets\": [\"e2e/compat/src/assets\"],\n        \"target\": \"node\",\n        \"compiler\": \"tsc\",\n        \"webpackConfig\": \"e2e/compat/webpack.config.js\"\n      },\n      \"configurations\": {\n        \"production\": {\n          \"optimization\": true,\n          \"extractLicenses\": true,\n          \"inspect\": false,\n          \"fileReplacements\": [\n            {\n              \"replace\": \"e2e/compat/src/environments/environment.ts\",\n              \"with\": \"e2e/compat/src/environments/environment.prod.ts\"\n            }\n          ]\n        }\n      }\n    },\n    \"serve\": {\n      \"executor\": \"@nx/js:node\",\n      \"options\": {\n        \"buildTarget\": \"compat:build\"\n      }\n    },\n    \"lint\": {\n      \"executor\": \"@nx/eslint:lint\",\n      \"outputs\": [\"{options.outputFile}\"]\n    },\n    \"test\": {\n      \"executor\": \"@nx/jest:jest\",\n      \"outputs\": [\"{workspaceRoot}/coverage/e2e/compat\"],\n      \"options\": {\n        \"jestConfig\": \"e2e/compat/jest.config.ts\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "e2e/compat/src/app/config.ts",
    "content": "import { defaultCwd } from './utils/cwd'\n\n// this is the package manager that will be used for the test Nx workspace\nexport type PackageManager = 'npm' | 'yarn' | 'pnpm'\nexport const PACKAGE_MANAGER: PackageManager = 'pnpm'\n\n// const CACHE_DIR = `${defaultCwd}/node_modules/.cache/nx-firebase`\nexport const CACHE_DIR = `${defaultCwd}/.nx-firebase`\n// const CACHE_DIR = `${defaultCwd}/../.nx-firebase`\n"
  },
  {
    "path": "e2e/compat/src/app/setup.ts",
    "content": "// setup re-usable workspaces for e2e testbed\nimport { customExec } from './utils/exec'\nimport { green, info, log, red } from './utils/log'\nimport {\n  deleteDir,\n  deleteFile,\n  ensureDir,\n  fileExists,\n  setCwd,\n} from './utils/utils'\nimport { createTestDir, createWorkspace } from './workspace'\nimport { Cache } from './utils/cache'\n\n/**\n * Generate an NxWorkspace with the given versions and gzip it\n * unless the gzip archive of a version already exists\n * @param nxVersion - target nx version eg. '13.10.6'\n * @param pluginVersion - target nx-firebase version eg. '0.3.4'\n * @param force - always recreate the workspace\n */\nexport async function setupNxWorkspace(cache: Cache, force = false) {\n  try {\n    // setup the target Nx workspace\n    const archiveExists = fileExists(cache.archiveFile) && !force\n\n    info(\n      `SETUP NX VERSION '${cache.nxVersion}' WITH PLUGIN VERSION '${\n        cache.pluginVersion\n      }' ${archiveExists ? '[CACHED]' : 'INSTALLING'}\\n`,\n    )\n\n    ensureDir(cache.rootDir)\n\n    info(\n      `Creating new Nx workspace version ${cache.nxVersion} in directory '${cache.testDir}'`,\n    )\n\n    // create workspace & archive if it doesn't already exist\n    if (!archiveExists) {\n      deleteDir(cache.testDir)\n      createTestDir(cache.testDir)\n      await createWorkspace(cache)\n\n      // delete any existing archive file so we do not accidentally append to archive\n      if (fileExists(cache.archiveFile)) {\n        deleteFile(cache.archiveFile)\n      }\n\n      // cwd is workspaceDir\n      setCwd(cache.rootDir)\n      // archive the workspace\n      await customExec(`tar -zcf ${cache.archiveFile} ./${cache.nxVersion}`) // add -v for verbose\n      deleteDir(cache.testDir)\n    } else {\n      log(\n        `Workspace archive '${cache.archiveFile}' already exists for '${cache.workspaceDir}', no setup required`,\n      )\n    }\n    info(green(`SETUP VERSION '${cache.nxVersion}' SUCCEEDED\\n`))\n  } catch (err) {\n    info(err.message)\n    info(red(`SETUP VERSION '${cache.nxVersion}' FAILED\\n`))\n    // escalate, this is a show stopper\n    throw err\n  }\n}\n"
  },
  {
    "path": "e2e/compat/src/app/test.ts",
    "content": "import { Cache, getCache, isNxVersionSince } from './utils/cache'\nimport { customExec, runNxCommandAsync } from './utils/exec'\nimport { expectToContain, it } from './utils/jest-ish'\nimport { green, info, log, red, setLogFile, time } from './utils/log'\nimport { deleteDir, getFileSize, setCwd } from './utils/utils'\nimport { installPlugin } from './workspace'\n\n// const npmContent = [\n//   `Added 'npm' dependency 'firebase-admin'`,\n//   `Added 'npm' dependency 'firebase-functions'`,\n// ]\n\n// const libContent = [`Copied 'lib' dependency '@myorg/lib1'`]\n\n// const importMatch = `import * as functions from \"firebase-functions\";`\n\n// const notCachedMatch = `[existing outputs match the cache, left as is]`\n\nconst DELETE_AFTER_TEST = false\n\n/**\n * A basic e2e test suite for the plugin to check compatibility with different Nx versions\n * We just want to check that the plugin can generate and build firebase apps and functions in each nx workspace version\n * @param cache\n */\nexport async function testPlugin(cache: Cache) {\n  const workspaceDir = cache.workspaceDir\n  // const indexTsPath = `${workspaceDir}/apps/functions/src/index.ts`\n\n  // from nx 16.8.0, apps and libs dirs need to be specified in the commandline\n  let appsDirectory = ''\n  let libsDirectory = ''\n  if (isNxVersionSince(cache, '16.8.0')) {\n    appsDirectory = '--directory=apps'\n    libsDirectory = '--directory=libs'\n  }\n\n  // the function generator imports @nx/node, which is only installed to the workspace if the app is generated first\n  // so this test checks that the workspace is setup correctly\n  await it('should throw if function is generated before app', async () => {\n    let failed = false\n    try {\n      await runNxCommandAsync(\n        `g @simondotm/nx-firebase:func functions ${appsDirectory} --app=firebase`,\n      )\n    } catch (err) {\n      failed = true\n    }\n\n    expectToContain(failed ? 'failed' : 'succeeded', 'failed')\n  })\n\n  // generate a test firebase app\n  await runNxCommandAsync(\n    `g @simondotm/nx-firebase:app firebase ${appsDirectory}`,\n  )\n  // generate a test firebase function\n  await runNxCommandAsync(\n    `g @simondotm/nx-firebase:func functions ${appsDirectory} --app=firebase`,\n  )\n  // generate a test js library\n  await runNxCommandAsync(\n    `g @nx/js:lib lib1 ${libsDirectory} --importPath=\"@myorg/lib1\"`,\n  )\n\n  // await it('should build the lib', async () => {\n  //   await runNxCommandAsync('build lib1')\n  // })\n\n  // build the firebase app\n  await it('should build the firebase app', async () => {\n    await runNxCommandAsync('build firebase')\n    // const { stdout } = await runNxCommandAsync('build firebase')\n    // expectToNotContain(stdout, npmContent)\n    // expectToNotContain(stdout, libContent)\n  })\n\n  // build the firebase functions\n  await it('should build the functions app', async () => {\n    const { stdout } = await runNxCommandAsync('build functions')\n    log(stdout)\n  })\n\n  // check that sync runs\n  await it('should sync the workspace', async () => {\n    const { stdout } = await runNxCommandAsync('g @simondotm/nx-firebase:sync')\n    expectToContain(\n      stdout,\n      `This workspace has 1 firebase apps and 1 firebase functions`,\n    )\n    log(stdout)\n  })\n\n  // await it('should update index.ts so that deps are updated after creation', async () => {\n  //   addContentToTextFile(indexTsPath, importMatch, '// comment added')\n  //   const { stdout } = await runNxCommandAsync('build functions')\n  //   expectToContain(stdout, npmContent)\n  //   expectToNotContain(stdout, libContent)\n  // })\n\n  // await it('should add a lib dependency', async () => {\n  //   const importAddition = `import { lib1 } from '@myorg/lib1'\\nconsole.log(lib1())\\n`\n  //   addContentToTextFile(indexTsPath, importMatch, importAddition)\n  //   const { stdout } = await runNxCommandAsync('build functions')\n  //   expectToContain(stdout, npmContent)\n  //   expectToContain(stdout, libContent)\n  // })\n\n  // some early 16.x versions of nx seem to have a flaky esbuild implementation\n  // that intermittently fails to exclude external deps from the bundle\n  // we check for this by testing the bundle size is not >1kb\n  // eslint-disable-next-line @typescript-eslint/require-await\n  await it('should not bundle external deps', async () => {\n    const fileSize = getFileSize(`${workspaceDir}/dist/apps/functions/main.js`)\n    if (fileSize > 1024)\n      throw new Error(\n        `TEST FAILED: esbuild bundle size is >1kb (${fileSize / 1024}kb)`,\n      )\n  })\n\n  // TODO: other checks\n  // - check package.json contains the deps\n  // - check package.json has the right node engine\n  // - check all the files exist\n  // - check the firebase config looks legit\n  // - if possible, run a test deploy?\n  // - check the init generator installs the firebase deps\n  // - check the plugin peerdeps installs the @nx/js and @nx/devkit and @nx/node deps\n}\n\nexport function clean() {\n  const cache = getCache('', '')\n  info(red(`Cleaning compat test cache dir '${cache.rootDir}'`))\n  deleteDir(cache.rootDir)\n}\n\nexport async function testNxVersion(cache: Cache) {\n  let error: string | undefined\n\n  const t = Date.now()\n\n  setLogFile(`${cache.rootDir}/${cache.nxVersion}.e2e.txt`)\n\n  try {\n    info(\n      `TESTING NX VERSION '${cache.nxVersion}' AGAINST PLUGIN VERSION '${cache.pluginVersion}'\\n`,\n    )\n\n    // cleanup\n    setCwd(cache.rootDir)\n    deleteDir(cache.testDir)\n\n    // unpack the archive\n    setCwd(cache.rootDir)\n    await customExec(`tar -xzf ${cache.archiveFile}`) // add -v for verbose\n\n    setCwd(cache.workspaceDir)\n\n    if (cache.deferPluginInstall) {\n      // lets see if installing the plugin in the test suite\n      // makes things more stable...\n      await installPlugin(cache)\n    }\n\n    // run the plugin test suite\n    await testPlugin(cache)\n\n    info(green(`TESTING VERSION '${cache.nxVersion}' SUCCEEDED\\n`))\n  } catch (err) {\n    info(err.message)\n    info(\n      red(\n        `TESTING VERSION '${cache.nxVersion}' FAILED - INCOMPATIBILITY DETECTED\\n`,\n      ),\n    )\n    error = err.message\n  }\n\n  // pretty sure there's nothing but trouble doing this\n  // if (cache.disableDaemon) {\n  // stop nx daemon after the test to stop connection in use errors\n  // await runNxCommandAsync(`reset`)\n  // }\n\n  // cleanup\n  setCwd(cache.rootDir)\n\n  if (DELETE_AFTER_TEST) {\n    deleteDir(cache.testDir)\n  }\n\n  const dt = Date.now() - t\n  info(`Completed in ${time(dt)}\\n`)\n\n  return error\n}\n"
  },
  {
    "path": "e2e/compat/src/app/utils/cache.ts",
    "content": "import { CACHE_DIR } from '../config'\nimport { defaultCwd } from './cwd'\nimport { info } from './log'\nimport { satisfies } from 'semver'\n\nexport const localPluginVersion = 'local'\n\nexport type Cache = {\n  nxVersion: string\n  pluginVersion: string\n  rootDir: string\n  testDir: string\n  workspaceDir: string\n  archiveFile: string\n  pluginWorkspace: string\n  disableDaemon: boolean\n  isLocalPlugin: boolean\n  deferPluginInstall: boolean // defer plugin installs during each test suite rather than in the workspace setup\n  nodeVersion: number // major node version\n}\n\n/**\n * compat tests are run in these directories\n * @param nxVersion\n * @param pluginVersion\n * @returns\n */\nexport function getCache(nxVersion: string, pluginVersion: string): Cache {\n  info(\n    `getting Cache for nxVersion=${nxVersion} pluginVersion=${pluginVersion}, using cache dir '${CACHE_DIR}'`,\n  )\n  const rootDir = `${CACHE_DIR}/${pluginVersion}`\n  const testDir = `${rootDir}/${nxVersion}`\n  const archiveFile = `${rootDir}/${nxVersion}.tar.gz`\n  const workspaceDir = `${testDir}/myorg`\n  const pluginWorkspace = defaultCwd\n  const isLocalPlugin = pluginVersion === localPluginVersion\n  return {\n    nxVersion,\n    pluginVersion,\n    rootDir,\n    testDir,\n    workspaceDir,\n    archiveFile,\n    pluginWorkspace,\n    isLocalPlugin,\n    deferPluginInstall: true, // dont think this is needed after all, was introduced because we had an issue from not installing @nx/js plugin\n    disableDaemon: false,\n    // disableDaemon:  isLocalPlugin,\n    // deferPluginInstall: isLocalPlugin, // for local plugin tests, install them for tests so that code changes are present in tests\n    nodeVersion: parseInt(process.versions.node.split('.')[0]),\n  }\n}\n\nexport function isNxVersionSince(cache: Cache, nxVersion: string) {\n  console.log(\n    `checking isNxVersionSince satisfies ${cache.nxVersion} >= ${nxVersion}`,\n  )\n  const isOk = satisfies(cache.nxVersion, `>=${nxVersion}`)\n  console.log('isNxVersionSince check returned ', isOk)\n  return isOk\n}\n"
  },
  {
    "path": "e2e/compat/src/app/utils/cwd.ts",
    "content": "export const defaultCwd = process.cwd()\nconsole.log(`cwd=${defaultCwd}`)\n"
  },
  {
    "path": "e2e/compat/src/app/utils/exec.ts",
    "content": "import { PACKAGE_MANAGER } from '../config'\nimport { info, log } from './log'\nimport { exec } from 'child_process'\n\n/**\n * Promisify node `exec`, with stdout & stderr piped to console\n * @param command\n * @param dir - defaults to cwd if not specified\n * @returns\n */\nexport async function customExec(\n  command: string,\n  dir?: string,\n): Promise<{ stdout: string; stderr: string }> {\n  const cwd = dir ? dir : process.cwd()\n  return new Promise((resolve, reject) => {\n    info(`Executing command '${command}' in '${cwd}'`)\n    const process = exec(\n      command,\n      { cwd: cwd },\n      //      { cwd: cwd, env: { NX_DAEMON: 'false' } }, // force CI type environment so Nx Daemon doesn't act up with multiple instances\n      // { cwd: cwd, env: { CI: 'true' } }, // force CI type environment so Nx Daemon doesn't act up with multiple instances\n      (error, stdout, stderr) => {\n        if (error) {\n          console.warn(error.message)\n          reject(error)\n        }\n        resolve({ stdout, stderr })\n      },\n    )\n\n    process.stdout.on('data', (data) => {\n      log(data.toString())\n    })\n\n    process.stderr.on('data', (data) => {\n      log(data.toString())\n    })\n\n    process.on('exit', (code) => {\n      if (code) {\n        log('child process exited with code ' + code.toString())\n      }\n    })\n  })\n}\n\nexport async function runNxCommandAsync(command: string, dir?: string) {\n  const exec = PACKAGE_MANAGER === 'npm' ? 'npx' : 'pnpm exec'\n  const cmd = `${exec} nx ${command} --verbose`\n  const result = await customExec(cmd, dir)\n  return result\n}\n"
  },
  {
    "path": "e2e/compat/src/app/utils/jest-ish.ts",
    "content": "import { info } from 'console'\nimport { log, red } from './log'\n\n/**\n * Test helper function approximating the Jest style of expect().toContain()\n * @param content\n * @param expected\n * @returns true if content contains expected string\n */\nfunction etc(content: string, expected: string) {\n  const pass = content.includes(expected)\n  return pass\n}\n\nfunction expectToContainInner(content: string, expected: string | string[]) {\n  if (Array.isArray(expected)) {\n    for (const e of expected) {\n      if (!etc(content, e)) {\n        return false\n      }\n    }\n    return true\n  } else {\n    return etc(content, expected)\n  }\n}\n\nexport function expectToContain(content: string, expected: string | string[]) {\n  // log(`- expectToContain`)\n  // log(`- content='${content}'`)\n  // log(`- expected='${expected}'`)\n\n  const pass = expectToContainInner(content, expected)\n  if (!pass) {\n    throw new Error(\n      `TEST FAILED: expected '${expected}', received '${content}'`,\n    )\n  }\n  return pass\n}\n\nexport function expectToNotContain(\n  content: string,\n  expected: string | string[],\n) {\n  // log(`- expectToNotContain`)\n  // log(`- content='${content}'`)\n  // log(`- not expected='${expected}'`)\n\n  const pass = !expectToContainInner(content, expected)\n  if (!pass) {\n    throw new Error(\n      `TEST FAILED: not expected '${expected}', received '${content}'`,\n    )\n  }\n  return pass\n}\n\n// hacky jest-like tester\nexport async function it(testName: string, testFunc: () => Promise<void>) {\n  info(` - it ${testName}`)\n  log(` - it ${testName}`)\n  try {\n    await testFunc()\n  } catch (err) {\n    info(red(err))\n    throw err\n  }\n}\n"
  },
  {
    "path": "e2e/compat/src/app/utils/log.ts",
    "content": "const ENABLE_LOG = false\nconst DEFAULT_LOG_FILE = `${process.cwd()}/e2e.log`\n\nimport * as fs from 'fs'\nimport { ensureDir } from './utils'\nimport { dirname } from 'path'\n\nlet LOG_FILE: string | undefined\n\nfunction writeLog(msg: string) {\n  ensureDir(dirname(LOG_FILE))\n  fs.appendFileSync(LOG_FILE, `${msg}\\n`)\n}\n\nexport function setLogFile(path?: string) {\n  LOG_FILE = path || DEFAULT_LOG_FILE\n  console.log(`Logging to '${LOG_FILE}'`)\n  ensureDir(dirname(LOG_FILE))\n  fs.writeFileSync(LOG_FILE, '') // reset log file\n}\n\nsetLogFile()\n\nexport function log(msg: string) {\n  if (ENABLE_LOG) {\n    console.log(msg)\n  }\n  writeLog(msg)\n}\n\nexport function info(msg: string) {\n  console.log(msg)\n  writeLog(msg)\n  // fs.appendFileSync(LOG_FILE, `${msg}\\n`)\n}\n\nexport function time(ms: number) {\n  return `${(ms / 1000.0).toFixed(1)}s`\n}\n\n// https://stackoverflow.com/questions/9781218/how-to-change-node-jss-console-font-color\nconst GREEN_FG = '\\x1b[32m'\nconst RED_FG = '\\x1b[31m'\nconst RESET_FG = '\\x1b[0m'\n\nexport function green(text: string) {\n  return `${GREEN_FG}${text}${RESET_FG}`\n}\nexport function red(text: string) {\n  return `${RED_FG}${text}${RESET_FG}`\n}\n"
  },
  {
    "path": "e2e/compat/src/app/utils/utils.ts",
    "content": "import * as fs from 'fs'\n// import { readJsonFile, writeJsonFile } from '@nx/devkit'\n// import { exit } from 'process'\nimport { log } from './log'\n\n/**\n * Set current working directory\n * @param dir\n */\nexport function setCwd(dir: string) {\n  log(`Switching cwd to '${dir}'`)\n\n  process.chdir(dir)\n\n  log(`Switched cwd to '${process.cwd()}'`)\n}\n\n/**\n * Ensure given directory path exists, create if it doesn't\n * @param dir - directory path\n * @returns true if path already exists\n */\nexport function ensureDir(dir: string) {\n  const pathExists = fs.existsSync(dir)\n  if (!pathExists) {\n    console.log(` - Creating dir '${dir}'...`)\n    fs.mkdirSync(dir, { recursive: true })\n  }\n  return pathExists\n}\n\nexport function fileExists(path: string) {\n  return fs.existsSync(path)\n}\n\nexport function deleteFile(path: string) {\n  log(`deleting file '${path}'`)\n  fs.rmSync(path)\n}\n\nexport function deleteDir(path: string) {\n  log(`deleting dir '${path}'`)\n  fs.rmSync(path, { recursive: true, force: true })\n}\n\n/**\n * Replace content in file `path` that matches `match` with `addition`\n * @param path - path to the target text file\n * @param match - string to match in the index.ts\n * @param addition - string to add after the matched line in the index.ts\n */\nexport function addContentToTextFile(\n  path: string,\n  match: string,\n  addition: string,\n) {\n  const content = fs.readFileSync(path, 'utf8')\n  if (!content.includes(match)) {\n    throw Error(\n      `ERROR: addContentToTextFile: Could not find '${match}' in '${path}'`,\n    )\n  }\n  const replaced = content.replace(match, `${match}\\n${addition}`)\n  fs.writeFileSync(path, replaced)\n}\n\nexport function getFileSize(path: string) {\n  const stats = fs.statSync(path)\n  return stats.size\n}\n"
  },
  {
    "path": "e2e/compat/src/app/versions.ts",
    "content": "export const testVersions = {\n  pluginVersions: [\n    'local',\n    // '2.1.2',\n    // '0.3.4'\n  ],\n  nxReleases: {\n    '16': {\n      '1': [4],\n      '2': [2],\n      '3': [2],\n      '4': [3],\n      '5': [5],\n      '6': [0],\n      '7': [4],\n      '8': [1],\n      '9': [1],\n      '10': [0],\n    },\n    '17': {\n      '0': [3],\n      '1': [3],\n      '2': [8],\n      // --nxCloud option for create-nx-workspace changed in 17.3.2, options are yes, github, circleci, skip\n      '3': [2],\n    },\n    '18': {\n      '0': [7],\n      '1': [2],\n    },\n\n    //--------------------------------\n    // LEGACY\n    //--------------------------------\n\n    // '15': {\n    //   '4': [0, 1, 2, 3, 4, 5],\n    //   '3': [0, 2, 3],\n    //   '2': [0, 1, 2, 3, 4],\n    //   '1': [0, 1],\n    //   '0': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13],\n    // },\n    // '14': {\n    //   '8': [0, 1, 2, 3, 4, 5, 6],\n    //   '7': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18],\n    //   '6': [0, 1, 2, 3, 4, 5],\n    //   '5': [0, 1, 2, 3, 4, 5, 6, 8, 10],\n    //   '4': [0, 1, 2, 3],\n    //   '3': [0, 1, 2, 3, 4, 5, 6],\n    //   '2': [1, 2, 4],\n    //   '1': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],\n    //   '0': [0, 1, 2, 3, 4, 5],\n    // },\n    // '13': {\n    //   '10': [6],\n    // },\n  },\n}\n"
  },
  {
    "path": "e2e/compat/src/app/workspace.ts",
    "content": "import * as fs from 'fs'\nimport { Cache, isNxVersionSince } from './utils/cache'\nimport { customExec, runNxCommandAsync } from './utils/exec'\nimport { info, log } from './utils/log'\nimport { deleteDir, ensureDir, setCwd } from './utils/utils'\nimport { PACKAGE_MANAGER } from './config'\n\nexport function createTestDir(testDir: string) {\n  ensureDir(testDir)\n  setCwd(testDir)\n}\n\nexport function workspaceExists(workspaceDir: string) {\n  return fs.existsSync(workspaceDir)\n}\n\nexport function cleanWorkspace(dir: string) {\n  if (workspaceExists(dir)) {\n    log(`Cleaning workspace '${dir}'...`)\n    deleteDir(dir)\n  }\n}\n\nexport async function installPlugin(cache: Cache) {\n  // this version of plugin has peerdeps that only work with node 14 / npm 6\n  const requireLegacyPeerDeps =\n    cache.nodeVersion >= 16 && cache.pluginVersion === '0.3.4'\n  const legacyPeerDeps = requireLegacyPeerDeps ? '--legacy-peer-deps' : ''\n  if (cache.isLocalPlugin) {\n    // install the plugin from the nx-firebase workspace as a local file dependency\n    // await customExec(\n    //   `npm i ${cache.pluginWorkspace}/dist/packages/nx-firebase --save-dev ${legacyPeerDeps}`,\n    // )\n\n    // const pluginVersion = `${cache.pluginVersion}`\n    const pluginVersion = `2.2.0`\n\n    // log(\n    //   `Packing plugin '${cache.pluginWorkspace}/dist/packages/nx-firebase}'...`,\n    // )\n    // await customExec(\n    //   `npm pack`,\n    //   `${cache.pluginWorkspace}/dist/packages/nx-firebase`,\n    // )\n\n    const pluginFileSrc = `${cache.pluginWorkspace}/dist/packages/nx-firebase/simondotm-nx-firebase-${pluginVersion}.tgz`\n    const pluginFileDst = `${cache.workspaceDir}/simondotm-nx-firebase-${pluginVersion}.tgz`\n    log(`Copying plugin '${pluginFileSrc}' to '${pluginFileDst}'...`)\n    await customExec(`cp -rf ${pluginFileSrc} ${pluginFileDst}`)\n\n    log(`Installing plugin '${pluginFileDst}'...`)\n    await customExec(\n      `${PACKAGE_MANAGER} i ${pluginFileDst} --save-dev ${legacyPeerDeps}`,\n    )\n  } else {\n    await customExec(\n      `${PACKAGE_MANAGER} i @simondotm/nx-firebase@${cache.pluginVersion} --save-dev ${legacyPeerDeps}`,\n    )\n  }\n}\n\nexport async function createWorkspace(cache: Cache) {\n  cleanWorkspace(cache.workspaceDir)\n  const nxCloudOption = isNxVersionSince(cache, '17.3.2') ? 'skip' : 'false'\n\n  await customExec(\n    `npx create-nx-workspace@${cache.nxVersion} --preset=apps --interactive=false --name=myorg --nxCloud=${nxCloudOption} --packageManager=${PACKAGE_MANAGER}`,\n  )\n  setCwd(cache.workspaceDir)\n\n  // we have issues with the daemon when running the workspace with local plugin\n  // so we turn it off for these workspaces\n  if (cache.disableDaemon) {\n    log(`Disabling daemon in workspace...`)\n    const nxJsonFile = `${cache.workspaceDir}/nx.json`\n    const content = fs.readFileSync(nxJsonFile, 'utf8')\n    const nxJson = JSON.parse(content)\n    nxJson.tasksRunnerOptions.default.options.useDaemonProcess = false\n    fs.writeFileSync(nxJsonFile, JSON.stringify(nxJson))\n\n    // stop nx daemon before we run plugin - why?\n    log(`Stopping nx daemon...`)\n    await runNxCommandAsync(`reset`)\n  }\n\n  // we meed this plugin for test suite libs\n  // update: @nx/node plugin brings in this dependency\n  // https://github.com/nrwl/nx/blob/fb90767af87c77955f8b8b7cace7cd0b5e3be27d/packages/node/package.json#L32\n  // so we dont need to install it here as long as we run @simondotm/nx-firebase:init first\n  // await customExec(`npm i @nx/js@${cache.nxVersion} --save-dev`)\n\n  if (!cache.deferPluginInstall) {\n    info(`Installing plugin in workspace...`)\n    // // these should be installed with the plugin I guess?\n    // // if we dont install them here, they'll be found in the parent workspace node_modules\n    // await customExec(`npm i @nx/js@${cache.nxVersion} --save-dev`)\n    // await customExec(`npm i @nx/devkit@${cache.nxVersion} --save-dev`)\n    // await customExec(`npm i @nx/jest@${cache.nxVersion} --save-dev`)\n    // install the target plugin we want to test\n    await installPlugin(cache)\n\n    // run the plugin initialiser to ensure we have the correct dependencies installed\n    info(`Initialising plugin in workspace...`)\n    await runNxCommandAsync(`g @simondotm/nx-firebase:init`)\n  }\n\n  // if (cache.disableDaemon) {\n  // cleanup - stop nx daemon post setup to prevent connection in use errors\n  // log(`Stopping nx daemon...`)\n  // await runNxCommandAsync('reset')\n  // }\n}\n"
  },
  {
    "path": "e2e/compat/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "e2e/compat/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: true,\n}\n"
  },
  {
    "path": "e2e/compat/src/environments/environment.ts",
    "content": "export const environment = {\n  production: false,\n}\n"
  },
  {
    "path": "e2e/compat/src/main.ts",
    "content": "/**\n * Custom e2e compatibility test suite for @simondotm/nx-firebase Nx plugin\n * The plugin e2e test suite can be unreliable and has limitations in Jest\n * This script allows us to run full matrix e2e and regression tests of the plugin across:\n * - Node versions 14,16,18\n * - Nx versions against plugin versions\n * - Check firebase deployments in CI environment\n * - We only do light functional tests, this test matrix is for ensuring wide compatibility of plugin generator & executor\n */\n\nimport { green, info, red, setLogFile, time } from './app/utils/log'\nimport { setupNxWorkspace } from './app/setup'\nimport { testVersions } from './app/versions'\nimport { clean, testNxVersion } from './app/test'\nimport { getCache } from './app/utils/cache'\nimport { customExec } from './app/utils/exec'\nimport { defaultCwd } from './app/utils/cwd'\n\n// Force CI environment if necessary\n// process.env.CI = 'true'\n// process.env.NX_DAEMON = 'false'\n\ntype CmdOptions = {\n  onlySetup: boolean\n  force: boolean\n  clean: boolean\n}\n\nasync function main(options: CmdOptions) {\n  const t = Date.now()\n\n  const pluginVersions = testVersions.pluginVersions\n\n  // gather all nx versions in the test matrix\n  const nxReleases: string[] = []\n  for (const maj in testVersions.nxReleases) {\n    const majVersions = testVersions.nxReleases[maj]\n    for (const min in majVersions) {\n      const patchVersions = majVersions[min]\n      const latestVersion = patchVersions[patchVersions.length - 1]\n      const version = `${maj}.${min}.${latestVersion}`\n      nxReleases.push(version)\n    }\n  }\n\n  info(`Packing plugin '${defaultCwd}/dist/packages/nx-firebase'...`)\n  await customExec(`npm pack`, `${defaultCwd}/dist/packages/nx-firebase`)\n\n  //-----------------------------------------------------------------------\n  // setup phase - generates workspaces for each Nx minor release\n  //-----------------------------------------------------------------------\n  //  gzip's and caches them for re-use\n  // splitting the setup phase from the test phase allows us to cache\n  // node_modules in CI github actions for this compat test\n  const testMatrixSize = nxReleases.length * pluginVersions.length * 2 // 2 = setup+test\n\n  //-----------------------------------------------------------------------\n  // test phase - tests each Nx minor release\n  //-----------------------------------------------------------------------\n  let testCounter = 0\n  const errors: string[] = []\n  for (let i = 0; i < nxReleases.length; ++i) {\n    for (const pluginVersion of pluginVersions) {\n      const release = nxReleases[i]\n\n      //-----------------------------------------------------------------------\n      // setup phase - generates workspaces for each Nx minor release\n      //-----------------------------------------------------------------------\n      //  gzip's and caches them for re-use\n      // splitting the setup phase from the test phase allows us to cache\n      // node_modules in CI github actions for this compat test\n\n      const cache = getCache(release, pluginVersion)\n\n      setLogFile(`${cache.rootDir}/${cache.nxVersion}.log.txt`)\n\n      info(\n        `-- ${\n          testCounter + 1\n        }/${testMatrixSize} --------------------------------------------------------------------------\\n`,\n      )\n\n      await setupNxWorkspace(cache, options.force)\n      ++testCounter\n\n      //-----------------------------------------------------------------------\n      // test phase - tests each Nx minor release\n      //-----------------------------------------------------------------------\n      info(\n        `-- ${\n          testCounter + 1\n        }/${testMatrixSize} --------------------------------------------------------------------------\\n`,\n      )\n\n      if (!options.onlySetup) {\n        const result = await testNxVersion(cache)\n        if (result) {\n          errors.push(result)\n        }\n      }\n      ++testCounter\n    }\n  }\n\n  // report error summary\n  if (errors.length) {\n    info(red('TEST ERRORS:`n'))\n    for (const error of errors) {\n      info(red(error))\n    }\n  } else {\n    info(green('ALL TESTS SUCCEEDED'))\n  }\n\n  //-----------------------------------------------------------------------\n  // Complete\n  //-----------------------------------------------------------------------\n  const dt = Date.now() - t\n  info(`Total time ${time(dt)}`)\n}\n\n// entry\nconst options: CmdOptions = { onlySetup: false, force: false, clean: false }\nif (process.argv.length > 2) {\n  if (process.argv[2] === '--setup') {\n    options.onlySetup = true\n  } else if (process.argv[2] === '--clean') {\n    options.clean = true\n  } else if (process.argv[2] === '--force') {\n    options.force = true\n  }\n}\n\nif (options.clean) {\n  clean()\n} else {\n  // eslint-disable-next-line @typescript-eslint/no-floating-promises\n  main(options)\n}\n"
  },
  {
    "path": "e2e/compat/tsconfig.app.json",
    "content": "{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../dist/out-tsc\",\n    \"module\": \"commonjs\",\n    \"types\": [\"node\"]\n  },\n  \"exclude\": [\"**/*.spec.ts\", \"**/*.test.ts\", \"jest.config.ts\"],\n  \"include\": [\"**/*.ts\"]\n}\n"
  },
  {
    "path": "e2e/compat/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.base.json\",\n  \"files\": [],\n  \"include\": [],\n  \"references\": [\n    {\n      \"path\": \"./tsconfig.app.json\"\n    },\n    {\n      \"path\": \"./tsconfig.spec.json\"\n    }\n  ]\n}\n"
  },
  {
    "path": "e2e/compat/tsconfig.spec.json",
    "content": "{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../dist/out-tsc\",\n    \"module\": \"commonjs\",\n    \"types\": [\"jest\", \"node\"]\n  },\n  \"include\": [\"**/*.test.ts\", \"**/*.spec.ts\", \"**/*.d.ts\", \"jest.config.ts\"]\n}\n"
  },
  {
    "path": "e2e/compat/webpack.config.js",
    "content": "const { composePlugins, withNx } = require('@nx/webpack')\n\n// Nx plugins for webpack.\nmodule.exports = composePlugins(withNx(), (config) => {\n  // Note: This was added by an Nx migration. Webpack builds are required to have a corresponding Webpack config file.\n  // See: https://nx.dev/recipes/webpack/webpack-config-setup\n  return config\n})\n"
  },
  {
    "path": "e2e/nx-firebase-e2e/jest.config.js",
    "content": "module.exports = {\n  displayName: 'nx-firebase-e2e',\n  preset: '../../jest.preset.js',\n  globals: {},\n  testEnvironment: 'node',  \n  transform: {\n    '^.+\\\\.[tj]s$': [\n      'ts-jest',\n      {\n        tsconfig: '<rootDir>/tsconfig.spec.json',\n      },\n    ],\n  },\n  moduleFileExtensions: ['ts', 'js', 'html'],\n\n  // Global setup runs once before all test files\n  globalSetup: '<rootDir>/jest.globalSetup.js',\n  globalTeardown: '<rootDir>/jest.globalTeardown.js',\n\n  // Custom sequencer ensures tests run in correct order\n  testSequencer: '<rootDir>/jest.testSequencer.js',\n\n  // Long timeout for e2e tests (3 minutes per test)\n  testTimeout: 180000,\n\n  // Match spec files in tests directory\n  testMatch: ['<rootDir>/tests/**/*.spec.ts'],\n}\n"
  },
  {
    "path": "e2e/nx-firebase-e2e/jest.globalSetup.js",
    "content": "/**\n * Global setup for e2e tests - runs once before all test files\n * Creates and configures the e2e test workspace\n */\nconst {\n  ensureNxProject,\n  updateFile,\n  runNxCommandAsync,\n} = require('@nx/plugin/testing')\n\nconst pluginName = '@simondotm/nx-firebase'\nconst pluginPath = 'dist/packages/nx-firebase'\n\nconst workspaceLayout = {\n  appsDir: 'apps',\n  libsDir: 'libs',\n}\n\nmodule.exports = async function globalSetup() {\n  console.log('\\n[Global Setup] Creating e2e test workspace...\\n')\n\n  // Ensure daemon is disabled\n  process.env['CI'] = 'true'\n  process.env['NX_DAEMON'] = 'false'\n\n  // Create the e2e workspace with the plugin\n  ensureNxProject(pluginName, pluginPath)\n\n  // Configure workspace layout for consistent project naming\n  updateFile('nx.json', (text) => {\n    const json = JSON.parse(text)\n    json.workspaceLayout = workspaceLayout\n    // Nx 17+ uses root-level useDaemonProcess instead of tasksRunnerOptions.default\n    json.useDaemonProcess = false\n    return JSON.stringify(json, null, 2)\n  })\n\n  // Reset nx to ensure clean state\n  await runNxCommandAsync('reset')\n\n  console.log('\\n[Global Setup] Workspace ready\\n')\n}\n"
  },
  {
    "path": "e2e/nx-firebase-e2e/jest.globalTeardown.js",
    "content": "/**\n * Global teardown for e2e tests - runs once after all test files\n * Cleans up the e2e test workspace\n */\nconst { runNxCommandAsync } = require('@nx/plugin/testing')\n\nmodule.exports = async function globalTeardown() {\n  console.log('\\n[Global Teardown] Cleaning up...\\n')\n\n  try {\n    // `nx reset` kills the daemon and performs cleanup\n    await runNxCommandAsync('reset')\n  } catch (e) {\n    console.log('Teardown warning:', e.message)\n  }\n\n  console.log('\\n[Global Teardown] Complete\\n')\n}\n"
  },
  {
    "path": "e2e/nx-firebase-e2e/jest.testSequencer.js",
    "content": "/**\n * Custom test sequencer to ensure e2e tests run in the correct order.\n * Some tests depend on shared libraries created by earlier tests.\n */\nconst Sequencer = require('@jest/test-sequencer').default\n\nclass CustomSequencer extends Sequencer {\n  /**\n   * Define the order in which test files should run.\n   * test-workspace must run first to verify setup\n   * test-libraries must run second to create shared libraries\n   * other tests can then run and use those libraries\n   */\n  sort(tests) {\n    const testOrder = [\n      'test-workspace.spec.ts',\n      'test-libraries.spec.ts',\n      'test-application.spec.ts',\n      'test-function.spec.ts',\n      'test-bundler.spec.ts',\n      'test-sync.spec.ts',\n      'test-migrate.spec.ts',\n      'test-targets.spec.ts',\n    ]\n\n    const getOrder = (test) => {\n      const filename = test.path.split('/').pop() || ''\n      const index = testOrder.indexOf(filename)\n      return index === -1 ? testOrder.length : index\n    }\n\n    return [...tests].sort((a, b) => getOrder(a) - getOrder(b))\n  }\n}\n\nmodule.exports = CustomSequencer\n"
  },
  {
    "path": "e2e/nx-firebase-e2e/project.json",
    "content": "{\n  \"name\": \"nx-firebase-e2e\",\n  \"$schema\": \"../../node_modules/nx/schemas/project-schema.json\",\n  \"projectType\": \"application\",\n  \"sourceRoot\": \"e2e/nx-firebase-e2e/src\",\n  \"tags\": [],\n  \"implicitDependencies\": [\"nx-firebase\"],\n  \"targets\": {\n    \"e2e\": {\n      \"executor\": \"@nx/jest:jest\",\n      \"options\": {\n        \"jestConfig\": \"e2e/nx-firebase-e2e/jest.config.js\",\n        \"runInBand\": true,\n        \"passWithNoTests\": false,\n        \"bail\": true\n      },\n      \"dependsOn\": [\"nx-firebase:build\"]\n    }\n  }\n}\n"
  },
  {
    "path": "e2e/nx-firebase-e2e/test-utils/index.ts",
    "content": "export * from './test-utils-apps'\nexport * from './test-utils-commands'\nexport * from './test-utils-functions'\nexport * from './test-utils-helpers'\nexport * from './test-utils-imports'\nexport * from './test-utils-logger'\nexport * from './test-utils-project-data'\nexport * from './test-shared-data'\n"
  },
  {
    "path": "e2e/nx-firebase-e2e/test-utils/test-shared-data.ts",
    "content": "import { getProjectData } from './test-utils-project-data'\n\n/**\n * Shared library data that persists across all e2e tests.\n * These libraries are created by test-libraries.spec.ts and used by other tests.\n *\n * Note: Project names must be unique across the workspace.\n * The subdir variants use different names (e.g., 'subdir-buildablelib').\n */\nexport const buildableLibData = getProjectData('libs', 'buildablelib')\nexport const nonbuildableLibData = getProjectData('libs', 'nonbuildablelib')\nexport const subDirBuildableLibData = getProjectData(\n  'libs',\n  'subdir-buildablelib',\n  { dir: 'subdir' },\n)\nexport const subDirNonbuildableLibData = getProjectData(\n  'libs',\n  'subdir-nonbuildablelib',\n  { dir: 'subdir' },\n)\n"
  },
  {
    "path": "e2e/nx-firebase-e2e/test-utils/test-utils-apps.ts",
    "content": "import type { ProjectData } from './test-utils-project-data'\nimport { readJson } from '@nx/plugin/testing'\n\nexport function expectedAppProjectTargets(appProject: ProjectData) {\n  return {\n    build: {\n      executor: 'nx:run-commands',\n      dependsOn: ['^build'],\n      options: {\n        command: `echo Build succeeded.`,\n      },\n    },\n    watch: {\n      executor: 'nx:run-commands',\n      options: {\n        command: `nx run-many --targets=build --projects=tag:firebase:dep:${appProject.projectName} --parallel=100 --watch`,\n      },\n    },\n    lint: {\n      executor: 'nx:run-commands',\n      options: {\n        command: `nx run-many --targets=lint --projects=tag:firebase:dep:${appProject.projectName} --parallel=100`,\n      },\n    },\n    test: {\n      executor: 'nx:run-commands',\n      options: {\n        command: `nx run-many --targets=test --projects=tag:firebase:dep:${appProject.projectName} --parallel=100`,\n      },\n    },\n    firebase: {\n      executor: 'nx:run-commands',\n      options: {\n        command: `firebase --config=firebase.json`,\n      },\n      configurations: {\n        production: {\n          command: `firebase --config=firebase.json`,\n        },\n      },\n    },\n    killports: {\n      executor: 'nx:run-commands',\n      options: {\n        command: `kill-port --port 9099,5001,8080,9000,5000,8085,9199,9299,4000,4400,4500`,\n      },\n    },\n    getconfig: {\n      executor: 'nx:run-commands',\n      options: {\n        command: `nx run ${appProject.projectName}:firebase functions:config:get > ${appProject.projectDir}/environment/.runtimeconfig.json`,\n      },\n    },\n    emulate: {\n      executor: 'nx:run-commands',\n      options: {\n        commands: [\n          `nx run ${appProject.projectName}:killports`,\n          `nx run ${appProject.projectName}:firebase emulators:start --import=${appProject.projectDir}/.emulators --export-on-exit`,\n        ],\n        parallel: false,\n      },\n    },\n    serve: {\n      executor: '@simondotm/nx-firebase:serve',\n      options: {\n        commands: [\n          `nx run ${appProject.projectName}:watch`,\n          `nx run ${appProject.projectName}:emulate`,\n        ],\n      },\n    },\n    deploy: {\n      executor: 'nx:run-commands',\n      dependsOn: ['build'],\n      options: {\n        command: `nx run ${appProject.projectName}:firebase deploy`,\n      },\n    },\n  }\n}\n\nexport function validateProjectConfig(appProject: ProjectData) {\n  const project = readJson(`${appProject.projectDir}/project.json`)\n  // expect(project.root).toEqual(`apps/${projectName}`)\n  expect(project.targets).toEqual(\n    expect.objectContaining(expectedAppProjectTargets(appProject)),\n  )\n}\n"
  },
  {
    "path": "e2e/nx-firebase-e2e/test-utils/test-utils-commands.ts",
    "content": "import { expectStrings, expectNoStrings } from './test-utils-helpers'\nimport { testDebug, red, green } from './test-utils-logger'\nimport { runNxCommandAsync } from '@nx/plugin/testing'\nimport { ProjectData } from './test-utils-project-data'\n\nconst STRIP_ANSI_MATCHER =\n  /[\\u001b\\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g\n\nexport async function safeRunNxCommandAsync(cmd: string) {\n  testDebug(`- safeRunNxCommandAsync ${cmd}`)\n  try {\n    async function runCommand(cmd: string) {\n      const result = await runNxCommandAsync(`${cmd} --verbose`, {\n        silenceError: true,\n      })\n      // strip chalk TTY ANSI codes from output\n      result.stdout = result.stdout.replace(STRIP_ANSI_MATCHER, '')\n      result.stderr = result.stderr.replace(STRIP_ANSI_MATCHER, '')\n      if (result.stdout) {\n        testDebug(green(result.stdout))\n      }\n      if (result.stderr) {\n        testDebug(red(result.stderr))\n      }\n      return result\n    }\n    // getting wierd lock file errors from Nx, so retry at least once\n    // SM Mar'24: this was due to 16.8.1+ e2e tests having the Nx Daemon enabled\n    // It's now disabled\n    let result = await runCommand(cmd)\n    // if (result.stdout.includes('LOCK-FILES-CHANGED') || result.stderr.includes('LOCK-FILES-CHANGED')) {\n    //   testDebug(red(`Re-running command ${cmd} due to LOCK-FILES-CHANGED`))\n    //   result = await runCommand(cmd)\n    // }\n\n    return result\n  } catch (e) {\n    testDebug(red(`ERROR: Running command ${(e as Error).message}`))\n    throw e\n  }\n}\n\nexport async function runTargetAsync(\n  projectData: ProjectData,\n  target: string = 'build',\n) {\n  testDebug(`- runTargetAsync ${target} ${projectData.projectName}`)\n  const result = await safeRunNxCommandAsync(\n    `${target} ${projectData.projectName}`,\n  )\n\n  if (target === 'build') {\n    expectStrings(result.stdout, [\n      `Successfully ran target ${target} for project ${projectData.projectName}`,\n    ])\n  }\n\n  return result\n}\n\nexport async function removeProjectAsync(projectData: ProjectData) {\n  const result = await safeRunNxCommandAsync(\n    `g @nx/workspace:remove ${projectData.projectName} --forceRemove`,\n  )\n  expectStrings(result.stdout, [\n    `DELETE ${projectData.projectDir}/project.json`,\n    `DELETE ${projectData.projectDir}`,\n  ])\n  return result\n}\n\nexport async function renameProjectAsync(\n  projectData: ProjectData,\n  renameProjectData: ProjectData,\n) {\n  // In Nx 20, @nx/workspace:move requires:\n  // --project: the current project name\n  // --destination: the new directory path (also becomes the new project name by default)\n  // --newProjectName: explicitly set the new project name (optional, defaults to last segment of destination)\n  const result = await safeRunNxCommandAsync(\n    `g @nx/workspace:move --projectName=${projectData.projectName} --destination=${renameProjectData.projectDir} --newProjectName=${renameProjectData.projectName}`,\n  )\n  expectStrings(result.stdout, [\n    `DELETE ${projectData.projectDir}/project.json`,\n    `DELETE ${projectData.projectDir}`,\n    `CREATE ${renameProjectData.projectDir}/project.json`,\n  ])\n  return result\n}\n\nexport async function appGeneratorAsync(\n  projectData: ProjectData,\n  params: string = '',\n) {\n  testDebug(`- appGeneratorAsync ${projectData.projectName} ${params}`)\n  const result = await safeRunNxCommandAsync(\n    `g @simondotm/nx-firebase:app ${projectData.name} --directory=${projectData.directory} ${params}`,\n  )\n  return result\n}\n\nexport async function functionGeneratorAsync(\n  projectData: ProjectData,\n  params: string = '',\n) {\n  testDebug(`- functionGeneratorAsync ${projectData.projectName} ${params}`)\n  const result = await safeRunNxCommandAsync(\n    `g @simondotm/nx-firebase:function ${projectData.name} --directory=${projectData.directory} ${params}`,\n  )\n  return result\n}\n\nexport async function libGeneratorAsync(\n  projectData: ProjectData,\n  params: string = '',\n) {\n  testDebug(`- libGeneratorAsync ${projectData.projectName}`)\n  const result = await safeRunNxCommandAsync(\n    `g @nx/js:lib ${projectData.name} --directory=${projectData.directory} ${params}`,\n  )\n  return result\n}\n\nexport async function syncGeneratorAsync(params: string = '') {\n  testDebug(`- syncGeneratorAsync ${params}`)\n  return await safeRunNxCommandAsync(`g @simondotm/nx-firebase:sync ${params}`)\n}\n\nexport async function migrateGeneratorAsync(params: string = '') {\n  testDebug(`- migrateGeneratorAsync ${params}`)\n  return await safeRunNxCommandAsync(\n    `g @simondotm/nx-firebase:migrate ${params}`,\n  )\n}\n\nexport async function cleanAppAsync(\n  projectData: ProjectData,\n  options?: { appsRemaining: number; functionsRemaining: number },\n) {\n  testDebug(`- cleanAppAsync ${projectData.projectName}`)\n  await removeProjectAsync(projectData)\n  const result = await syncGeneratorAsync(projectData.projectName)\n  testDebug(result.stdout)\n  expect(result.stdout).toMatch(/DELETE (firebase)(\\S*)(.json)/)\n  // Verify the config was cleaned up\n  expectStrings(result.stdout, [\n    `CHANGE Firebase config '${projectData.configName}' is no longer referenced by any firebase app, deleted`,\n  ])\n  // Only assert on exact counts if explicitly provided\n  if (options) {\n    expectStrings(result.stdout, [\n      `This workspace has ${options.appsRemaining} firebase apps and ${options.functionsRemaining} firebase functions`,\n    ])\n  }\n}\n\nexport async function cleanFunctionAsync(projectData: ProjectData) {\n  testDebug(`- cleanFunctionAsync ${projectData.projectName}`)\n  await removeProjectAsync(projectData)\n}\n"
  },
  {
    "path": "e2e/nx-firebase-e2e/test-utils/test-utils-functions.ts",
    "content": "import type { ProjectData } from './test-utils-project-data'\nimport { readJson } from '@nx/plugin/testing'\n\nexport function expectedFunctionProjectTargets(\n  functionProject: ProjectData,\n  appProject: ProjectData,\n) {\n  return {\n    build: {\n      executor: '@nx/esbuild:esbuild',\n      outputs: ['{options.outputPath}'],\n      options: {\n        platform: 'node',\n        outputPath: `dist/${functionProject.projectDir}`,\n        main: `${functionProject.projectDir}/src/main.ts`,\n        tsConfig: `${functionProject.projectDir}/tsconfig.app.json`,\n        assets: [\n          `${functionProject.projectDir}/src/assets`,\n          {\n            glob: '**/*',\n            input: `${appProject.projectDir}/environment`,\n            output: '.',\n          },\n        ],\n        generatePackageJson: true,\n        bundle: true,\n        dependenciesFieldType: 'dependencies',\n        format: ['esm'],\n        thirdParty: false,\n        target: 'node20',\n        esbuildOptions: {\n          logLevel: 'info',\n        },\n      },\n    },\n    deploy: {\n      executor: 'nx:run-commands',\n      options: {\n        command: `nx run ${appProject.projectName}:deploy --only functions:${functionProject.projectName}`,\n      },\n      dependsOn: ['build'],\n    },\n    lint: {\n      executor: '@nx/eslint:lint',\n    },\n    // Test target is kept with passWithNoTests so functions without tests don't fail\n    // with passWithNoTests so functions without tests don't fail\n    // Nx 22+ uses jest.config.cts for ESM projects\n    test: {\n      executor: '@nx/jest:jest',\n      outputs: ['{workspaceRoot}/coverage/{projectRoot}'],\n      options: {\n        jestConfig: expect.stringMatching(\n          new RegExp(`^${functionProject.projectDir}/jest\\\\.config\\\\.c?ts$`),\n        ),\n        passWithNoTests: true,\n      },\n    },\n  }\n}\n\nexport function validateFunctionConfig(\n  functionProject: ProjectData,\n  appProject: ProjectData,\n) {\n  const project = readJson(`${functionProject.projectDir}/project.json`)\n  // expect(project.root).toEqual(`apps/${projectName}`)\n  expect(project.targets).toEqual(\n    expect.objectContaining(\n      expectedFunctionProjectTargets(functionProject, appProject),\n    ),\n  )\n}\n"
  },
  {
    "path": "e2e/nx-firebase-e2e/test-utils/test-utils-helpers.ts",
    "content": "export function expectStrings(input: string, contains: string[]) {\n  contains.forEach((item) => {\n    expect(input).toContain(item)\n  })\n}\n\nexport function expectNoStrings(input: string, contains: string[]) {\n  contains.forEach((item) => {\n    expect(input).not.toContain(item)\n  })\n}\n"
  },
  {
    "path": "e2e/nx-firebase-e2e/test-utils/test-utils-imports.ts",
    "content": "import type { ProjectData } from './test-utils-project-data'\n\nconst IMPORT_MATCH = `import * as logger from \"firebase-functions/logger\";`\n\nexport function getMainTs() {\n  return `\n/**\n * Import function triggers from their respective submodules:\n *\n * import {onCall} from \"firebase-functions/v2/https\";\n * import {onDocumentWritten} from \"firebase-functions/v2/firestore\";\n *\n * See a full list of supported triggers at https://firebase.google.com/docs/functions\n */\n\nimport {onRequest} from \"firebase-functions/v2/https\";\n${IMPORT_MATCH}\n\n// Start writing functions\n// https://firebase.google.com/docs/functions/typescript\n\nexport const helloWorld = onRequest((request, response) => {\n  logger.info(\"Hello logs!\", {structuredData: true});\n  response.send(\"Hello from Firebase!\");\n});\n`\n}\n\n/**\n * return the import function for a generated library\n */\nexport function getLibImport(projectData: ProjectData) {\n  // convert kebab-case project name to camelCase library import\n  const libName = projectData.projectName\n    .split('-')\n    .map((part, index) =>\n      index > 0 ? part[0].toUpperCase() + part.substring(1) : part,\n    )\n    .join('')\n  return libName\n}\n\nexport function addImport(mainTs: string, addition: string) {\n  const replaced = mainTs.replace(IMPORT_MATCH, `${IMPORT_MATCH}\\n${addition}`)\n  return replaced\n}\n"
  },
  {
    "path": "e2e/nx-firebase-e2e/test-utils/test-utils-logger.ts",
    "content": "const ENABLE_TEST_DEBUG_INFO = true\n\n// https://stackoverflow.com/questions/9781218/how-to-change-node-jss-console-font-color\nconst GREEN_FG = '\\x1b[32m'\nconst RED_FG = '\\x1b[31m'\nconst RESET_FG = '\\x1b[0m'\n\nexport function green(text: string) {\n  return `${GREEN_FG}${text}${RESET_FG}`\n}\nexport function red(text: string) {\n  return `${RED_FG}${text}${RESET_FG}`\n}\n\nexport function testDebug(info: string) {\n  if (ENABLE_TEST_DEBUG_INFO) {\n    console.debug(info)\n  }\n}\n"
  },
  {
    "path": "e2e/nx-firebase-e2e/test-utils/test-utils-project-data.ts",
    "content": "import { joinPathFragments } from '@nx/devkit'\n\nconst NPM_SCOPE = '@proj'\n\nexport interface ProjectData {\n  name: string\n  directory: string // the --directory option value for generators\n  projectName: string\n  projectDir: string\n  srcDir: string\n  distDir: string\n  mainTsPath: string\n  npmScope: string\n  configName: string\n}\n\n/**\n * Generate test project data\n *\n * Nx 20+ uses \"as-provided\" naming only:\n * - projectName = name (exactly as provided)\n * - projectRoot = directory (exactly as provided)\n *\n * To maintain backwards compatibility with the original e2e test behavior:\n * - The `type` parameter ('apps' or 'libs') is used as the directory prefix\n * - If `dir` option is provided, projects are nested under it: type/dir/name\n * - Project names must be unique (tests use uniq() for this)\n * - This ensures projectRoot (e.g., 'apps/my-app') differs from projectName (e.g., 'my-app')\n *   which is important because @nx/workspace:move does string replacement of project roots\n *\n * Note: call this function AFTER initial app firebase.json has been created in order to have a\n *  correct configName\n * @param type - 'libs' or 'apps' - used as directory prefix to match original test behavior\n * @param name - project name (must be unique, tests use uniq() for this)\n * @param options - optional directory and customConfig settings\n * @returns - asset locations for this project\n */\nexport function getProjectData(\n  type: 'libs' | 'apps',\n  name: string,\n  options?: { dir?: string; customConfig?: boolean },\n): ProjectData {\n  // Project name is exactly as provided (must be unique across workspace)\n  const projectName = name\n\n  // Use the type ('apps' or 'libs') as the base directory prefix\n  // If additional dir is provided, nest under that as well\n  // This maintains the original e2e test folder structure\n  const directory = options?.dir\n    ? joinPathFragments(type, options.dir, name)\n    : joinPathFragments(type, name)\n\n  // Project directory is exactly the directory (which is the full project root)\n  const projectDir = directory\n\n  const srcDir = joinPathFragments(projectDir, 'src')\n  const mainTsPath = joinPathFragments(srcDir, 'main.ts')\n  const distDir = joinPathFragments('dist', projectDir)\n\n  return {\n    name: projectName, // name passed to generator\n    directory, // --directory option for generators (full project root path)\n    projectName, // project name (must be unique)\n    projectDir,\n    srcDir,\n    distDir,\n    mainTsPath,\n    npmScope: `${NPM_SCOPE}/${projectName}`,\n    configName: options?.customConfig\n      ? `firebase.${projectName}.json`\n      : 'firebase.json',\n  }\n}\n"
  },
  {
    "path": "e2e/nx-firebase-e2e/tests/test-application.spec.ts",
    "content": "import {\n  readJson,\n  runNxCommandAsync,\n  uniq,\n  checkFilesExist,\n  readFile,\n} from '@nx/plugin/testing'\n\nimport {\n  ProjectData,\n  appGeneratorAsync,\n  cleanAppAsync,\n  getProjectData,\n  testDebug,\n  validateProjectConfig,\n} from '../test-utils'\n\n// Ensure daemon is disabled for all e2e tests\nbeforeAll(() => {\n  process.env['CI'] = 'true'\n  process.env['NX_DAEMON'] = 'false'\n})\n\nfunction expectedAppFiles(projectData: ProjectData) {\n  const projectPath = projectData.projectDir\n  return [\n    `${projectPath}/public/index.html`,\n    `${projectPath}/public/404.html`,\n    `${projectPath}/database.rules.json`,\n    `${projectPath}/firestore.indexes.json`,\n    `${projectPath}/firestore.rules`,\n    `${projectPath}/project.json`,\n    `${projectPath}/readme.md`,\n    `${projectPath}/storage.rules`,\n    `${projectData.configName}`,\n    `.firebaserc`,\n  ]\n}\n\n//--------------------------------------------------------------------------------------------------\n// Application generator e2e tests\n//--------------------------------------------------------------------------------------------------\ndescribe('nx-firebase application', () => {\n  // Track current test's app for cleanup\n  let currentAppData: ProjectData | null = null\n\n  // Always run cleanup after each test, even on failure\n  afterEach(async () => {\n    if (currentAppData) {\n      try {\n        await cleanAppAsync(currentAppData)\n      } catch (e) {\n        testDebug(`Cleanup warning: ${(e as Error).message}`)\n      }\n      currentAppData = null\n    }\n  })\n\n  it('should create nx-firebase app', async () => {\n    currentAppData = getProjectData('apps', uniq('firebaseSetupApp'))\n    await appGeneratorAsync(currentAppData)\n\n    // test generator output\n    expect(() =>\n      checkFilesExist(...expectedAppFiles(currentAppData!)),\n    ).not.toThrow()\n\n    validateProjectConfig(currentAppData)\n\n    // check that the firestore.rules file has had the IN_30_DAYS placeholder replaced\n    const firestoreRules = readFile(`${currentAppData.projectDir}/firestore.rules`)\n    testDebug(firestoreRules)\n    expect(firestoreRules).not.toContain('IN_30_DAYS')\n  })\n\n  it('should build nx-firebase app', async () => {\n    currentAppData = getProjectData('apps', uniq('firebaseSetupApp'))\n    await appGeneratorAsync(currentAppData)\n\n    // test app builder\n    // at this point there are no functions so it does nothing\n    const result = await runNxCommandAsync(`build ${currentAppData.projectName}`)\n    expect(result.stdout).toContain('Build succeeded.')\n  })\n\n  describe('--directory', () => {\n    it('should create nx-firebase app in the specified directory', async () => {\n      currentAppData = getProjectData('apps', uniq('firebaseSetupApp'), {\n        dir: 'subdir',\n      })\n      await appGeneratorAsync(currentAppData)\n      expect(() =>\n        checkFilesExist(...expectedAppFiles(currentAppData!)),\n      ).not.toThrow()\n\n      const project = readJson(`${currentAppData.projectDir}/project.json`)\n      expect(project.name).toEqual(`${currentAppData.projectName}`)\n\n      validateProjectConfig(currentAppData)\n    })\n  })\n\n  describe('--tags', () => {\n    it('should add tags to the project', async () => {\n      currentAppData = getProjectData('apps', uniq('firebaseSetupApp'))\n      await appGeneratorAsync(currentAppData, `--tags e2etag,e2ePackage`)\n      const project = readJson(`${currentAppData.projectDir}/project.json`)\n      expect(project.tags).toEqual([\n        'firebase:app',\n        `firebase:name:${currentAppData.projectName}`,\n        'e2etag',\n        'e2ePackage',\n      ])\n    })\n  })\n})\n"
  },
  {
    "path": "e2e/nx-firebase-e2e/tests/test-bundler.spec.ts",
    "content": "import {\n  readJson,\n  uniq,\n  updateFile,\n  checkFilesExist,\n  readFile,\n} from '@nx/plugin/testing'\n\nimport {\n  appGeneratorAsync,\n  cleanAppAsync,\n  cleanFunctionAsync,\n  functionGeneratorAsync,\n  getProjectData,\n  validateFunctionConfig,\n  runTargetAsync,\n  getMainTs,\n  getLibImport,\n  addImport,\n  expectStrings,\n  testDebug,\n  ProjectData,\n  // Shared library data from test-libraries\n  buildableLibData,\n  nonbuildableLibData,\n  subDirBuildableLibData,\n  subDirNonbuildableLibData,\n} from '../test-utils'\n\n// Ensure daemon is disabled for all e2e tests\nbeforeAll(() => {\n  process.env['CI'] = 'true'\n  process.env['NX_DAEMON'] = 'false'\n})\n\n//--------------------------------------------------------------------------------------------------\n// Test import & dependency handling\n//--------------------------------------------------------------------------------------------------\ndescribe('nx-firebase bundle dependencies', () => {\n  // Track current test's projects for cleanup\n  let currentAppData: ProjectData | null = null\n  let currentFunctionData: ProjectData | null = null\n\n  // Always run cleanup after each test, even on failure\n  afterEach(async () => {\n    if (currentFunctionData) {\n      try {\n        await cleanFunctionAsync(currentFunctionData)\n      } catch (e) {\n        testDebug(`Function cleanup warning: ${(e as Error).message}`)\n      }\n      currentFunctionData = null\n    }\n    if (currentAppData) {\n      try {\n        await cleanAppAsync(currentAppData)\n      } catch (e) {\n        testDebug(`App cleanup warning: ${(e as Error).message}`)\n      }\n      currentAppData = null\n    }\n  })\n\n  it('should inline library dependencies into function bundle', async () => {\n    // use libs we generated earlier\n    currentAppData = getProjectData('apps', uniq('firebaseDepsApp'))\n    currentFunctionData = getProjectData('apps', uniq('firebaseDepsFunction'))\n\n    await appGeneratorAsync(currentAppData)\n    await functionGeneratorAsync(\n      currentFunctionData,\n      `--app ${currentAppData.projectName}`,\n    )\n\n    validateFunctionConfig(currentFunctionData, currentAppData)\n\n    // add buildable & nonbuildable lib dependencies using import statements\n    let mainTs = getMainTs()\n\n    // import from a buildable lib\n    const libImport1 = getLibImport(buildableLibData)\n    const importAddition1 = `import { ${libImport1} } from '${buildableLibData.npmScope}'\\nconsole.log(${libImport1}())\\n`\n    mainTs = addImport(mainTs, importAddition1)\n\n    // import from a non buildable lib\n    const libImport2 = getLibImport(nonbuildableLibData)\n    const importAddition2 = `import { ${libImport2} } from '${nonbuildableLibData.npmScope}'\\nconsole.log(${libImport2}())\\n`\n    mainTs = addImport(mainTs, importAddition2)\n\n    // import from a buildable subdir lib\n    const libImport3 = getLibImport(subDirBuildableLibData)\n    const importAddition3 = `import { ${libImport3} } from '${subDirBuildableLibData.npmScope}'\\nconsole.log(${libImport3}())\\n`\n    mainTs = addImport(mainTs, importAddition3)\n\n    // import from a non buildable subdir lib\n    const libImport4 = getLibImport(subDirNonbuildableLibData)\n    const importAddition4 = `import { ${libImport4} } from '${subDirNonbuildableLibData.npmScope}'\\nconsole.log(${libImport4}())\\n`\n    mainTs = addImport(mainTs, importAddition4)\n\n    // write the new main.ts\n    updateFile(currentFunctionData.mainTsPath, () => {\n      return mainTs\n    })\n\n    // confirm the file changes\n    const updatedMainTs = readFile(currentFunctionData.mainTsPath)\n    expect(updatedMainTs).toContain(importAddition1)\n    expect(updatedMainTs).toContain(importAddition2)\n    expect(updatedMainTs).toContain(importAddition3)\n    expect(updatedMainTs).toContain(importAddition4)\n\n    // build\n    const result = await runTargetAsync(currentFunctionData, `build`)\n    // check console output\n    expectStrings(result.stdout, [\n      `Running target build for project ${currentFunctionData.projectName}`,\n      `nx run ${buildableLibData.projectName}:build`,\n      `nx run ${subDirBuildableLibData.projectName}:build`,\n      `Compiling TypeScript files for project \"${subDirBuildableLibData.projectName}\"`,\n      `Compiling TypeScript files for project \"${buildableLibData.projectName}\"`,\n      `Done compiling TypeScript files for project \"${buildableLibData.projectName}\"`,\n      `Done compiling TypeScript files for project \"${subDirBuildableLibData.projectName}\"`,\n      `nx run ${currentFunctionData.projectName}:build`,\n      `Successfully ran target build for project ${currentFunctionData.projectName}`,\n    ])\n    expectStrings(result.stderr, [`${currentFunctionData.distDir}/main.js`])\n    // make sure output build is not megabytes in size, which would mean we've\n    // bundled node_modules as well\n    expect(result.stdout).not.toContain('Mb')\n\n    // check dist outputs\n    expect(() =>\n      checkFilesExist(\n        `${currentFunctionData!.distDir}/package.json`,\n        `${currentFunctionData!.distDir}/main.js`,\n        `${currentFunctionData!.distDir}/.env`,\n        `${currentFunctionData!.distDir}/.env.local`,\n        `${currentFunctionData!.distDir}/.secret.local`,\n      ),\n    ).not.toThrow()\n\n    // check dist package contains external imports\n    const distPackage = readJson(`${currentFunctionData.distDir}/package.json`)\n    const deps = distPackage['dependencies']\n    expect(deps).toBeDefined()\n    // firebase-admin not in the template anymore\n    // expect(deps['firebase-admin']).toBeDefined()\n    expect(deps['firebase-functions']).toBeDefined()\n\n    // check bundled code contains the libcode we added\n    const bundle = readFile(`${currentFunctionData.distDir}/main.js`)\n\n    // check that node modules were not bundled, happens in e2e if nx reset not called\n    // probably the earlier check for deps in the package.json already detects this scenario too\n    expect(bundle).not.toContain(`require_firebase_app`)\n\n    // our imported lib modules should be inlined in the bundle\n    expect(bundle).toContain(`function ${libImport1}`)\n    expect(bundle).toContain(`return \"${buildableLibData.projectName}\"`)\n    expect(bundle).toContain(`function ${libImport2}`)\n    expect(bundle).toContain(`return \"${nonbuildableLibData.projectName}\"`)\n    expect(bundle).toContain(`function ${libImport3}`)\n    expect(bundle).toContain(`return \"${subDirBuildableLibData.projectName}\"`)\n    expect(bundle).toContain(`function ${libImport4}`)\n    expect(bundle).toContain(\n      `return \"${subDirNonbuildableLibData.projectName}\"`,\n    )\n  })\n})\n"
  },
  {
    "path": "e2e/nx-firebase-e2e/tests/test-function.spec.ts",
    "content": "import { readJson, uniq, checkFilesExist, exists, tmpProjPath } from '@nx/plugin/testing'\nimport { detectPackageManager } from '@nx/devkit'\n\nimport {\n  ProjectData,\n  appGeneratorAsync,\n  cleanAppAsync,\n  cleanFunctionAsync,\n  functionGeneratorAsync,\n  getProjectData,\n  validateFunctionConfig,\n  runTargetAsync,\n  expectStrings,\n  testDebug,\n} from '../test-utils'\n\n// Ensure daemon is disabled for all e2e tests\nbeforeAll(() => {\n  process.env['CI'] = 'true'\n  process.env['NX_DAEMON'] = 'false'\n})\n\n// Detect package manager in e2e workspace, not main project\nconst packageManager = detectPackageManager(tmpProjPath())\nconst packageLockFile =\n  packageManager === 'npm'\n    ? 'package-lock.json'\n    : packageManager === 'pnpm'\n    ? 'pnpm-lock.yaml'\n    : 'yarn.lock'\n\nfunction expectedFunctionFiles(projectData: ProjectData) {\n  const projectPath = projectData.projectDir\n  return [\n    `${projectPath}/src/main.ts`,\n    `${projectPath}/package.json`,\n    `${projectPath}/project.json`,\n    `${projectPath}/readme.md`,\n    `${projectPath}/tsconfig.app.json`,\n    `${projectPath}/tsconfig.json`,\n    `${projectPath}/tsconfig.spec.json`,\n  ]\n}\n\n//--------------------------------------------------------------------------------------------------\n// Function generator e2e tests\n//--------------------------------------------------------------------------------------------------\ndescribe('nx-firebase function', () => {\n  // Track current test's projects for cleanup\n  let currentAppData: ProjectData | null = null\n  let currentFunctionData: ProjectData | null = null\n\n  // Always run cleanup after each test, even on failure\n  afterEach(async () => {\n    // Clean up function first (it depends on app)\n    if (currentFunctionData) {\n      try {\n        await cleanFunctionAsync(currentFunctionData)\n      } catch (e) {\n        testDebug(`Function cleanup warning: ${(e as Error).message}`)\n      }\n      currentFunctionData = null\n    }\n    // Then clean up app\n    if (currentAppData) {\n      try {\n        await cleanAppAsync(currentAppData)\n      } catch (e) {\n        testDebug(`App cleanup warning: ${(e as Error).message}`)\n      }\n      currentAppData = null\n    }\n  })\n\n  it('should not create nx-firebase function without --app', async () => {\n    const functionData = getProjectData('apps', uniq('firebaseFunction'))\n    const result = await functionGeneratorAsync(functionData)\n    expect(result.stdout).toContain(\"Required property 'app' is missing\")\n    // no cleanup required - function wasn't created\n  })\n\n  it('should not create nx-firebase function with an invalid --app', async () => {\n    const functionData = getProjectData('apps', uniq('firebaseFunction'))\n    const result = await functionGeneratorAsync(functionData, '--app badapple')\n    expect(result.stdout).toContain(\n      \"A firebase application project called 'badapple' was not found in this workspace.\",\n    )\n    // no cleanup required - function wasn't created\n  })\n\n  it('should create nx-firebase function', async () => {\n    currentAppData = getProjectData('apps', uniq('firebaseApp'))\n    currentFunctionData = getProjectData('apps', uniq('firebaseFunction'))\n\n    await appGeneratorAsync(currentAppData)\n    await functionGeneratorAsync(\n      currentFunctionData,\n      `--app ${currentAppData.projectName}`,\n    )\n\n    // test generator output\n    expect(() =>\n      checkFilesExist(...expectedFunctionFiles(currentFunctionData!)),\n    ).not.toThrow()\n\n    // check dist files dont exist and we havent accidentally run this test out of sequence\n    expect(() =>\n      checkFilesExist(\n        `dist/${currentFunctionData!.projectDir}/main.js`,\n        `dist/${currentFunctionData!.projectDir}/package.json`,\n        `dist/${currentFunctionData!.projectDir}/${packageLockFile}`,\n        `dist/${currentFunctionData!.projectDir}/.env`,\n        `dist/${currentFunctionData!.projectDir}/.env.local`,\n        `dist/${currentFunctionData!.projectDir}/.secret.local`,\n      ),\n    ).toThrow()\n\n    validateFunctionConfig(currentFunctionData, currentAppData)\n\n    // check that google-cloud/functions-framework is added to package.json if pnpm being used\n    const packageJson = readJson(`${currentFunctionData.projectDir}/package.json`)\n    if (packageManager === 'pnpm') {\n      expect(\n        packageJson.dependencies['@google-cloud/functions-framework'],\n      ).toBeDefined()\n    } else {\n      expect(\n        packageJson.dependencies['@google-cloud/functions-framework'],\n      ).not.toBeDefined()\n    }\n  })\n\n  it('should build nx-firebase function from the app', async () => {\n    currentAppData = getProjectData('apps', uniq('firebaseApp'))\n    currentFunctionData = getProjectData('apps', uniq('firebaseFunction'))\n\n    await appGeneratorAsync(currentAppData)\n    await functionGeneratorAsync(\n      currentFunctionData,\n      `--app ${currentAppData.projectName}`,\n    )\n\n    validateFunctionConfig(currentFunctionData, currentAppData)\n\n    const result = await runTargetAsync(currentAppData, 'build')\n    expect(result.stdout).toContain('Build succeeded.')\n\n    expect(() =>\n      checkFilesExist(\n        `dist/${currentFunctionData!.projectDir}/main.js`,\n        `dist/${currentFunctionData!.projectDir}/package.json`,\n        `dist/${currentFunctionData!.projectDir}/${packageLockFile}`,\n        `dist/${currentFunctionData!.projectDir}/.env`,\n        `dist/${currentFunctionData!.projectDir}/.env.local`,\n        `dist/${currentFunctionData!.projectDir}/.secret.local`,\n      ),\n    ).not.toThrow()\n\n    // check that nx preserves the function `package.json` dependencies in the output `package.json`\n    const packageJson = readJson(\n      `dist/${currentFunctionData.projectDir}/package.json`,\n    )\n    if (packageManager === 'pnpm') {\n      expect(\n        packageJson.dependencies['@google-cloud/functions-framework'],\n      ).toBeDefined()\n    } else {\n      expect(\n        packageJson.dependencies['@google-cloud/functions-framework'],\n      ).not.toBeDefined()\n    }\n  })\n\n  it('should build nx-firebase function directly', async () => {\n    currentAppData = getProjectData('apps', uniq('firebaseApp'))\n    currentFunctionData = getProjectData('apps', uniq('firebaseFunction'))\n\n    await appGeneratorAsync(currentAppData)\n    await functionGeneratorAsync(\n      currentFunctionData,\n      `--app ${currentAppData.projectName}`,\n    )\n\n    validateFunctionConfig(currentFunctionData, currentAppData)\n\n    const result = await runTargetAsync(currentFunctionData, 'build')\n    expect(result.stdout).toContain(\n      `nx run ${currentFunctionData.projectName}:build`,\n    )\n    // esbuild outputs to stderr for some reason\n    expect(result.stderr).toContain(`${currentFunctionData.distDir}/main.js`)\n    // make sure it hasnt bundled node_modules, indicator is that bundle size is megabytes in size\n    expect(result.stderr).not.toContain(`Mb`)\n  })\n\n  it('should add correct dependencies to the built function package.json', async () => {\n    currentAppData = getProjectData('apps', uniq('firebaseApp'))\n    currentFunctionData = getProjectData('apps', uniq('firebaseFunction'))\n\n    await appGeneratorAsync(currentAppData)\n    await functionGeneratorAsync(\n      currentFunctionData,\n      `--app ${currentAppData.projectName}`,\n    )\n\n    validateFunctionConfig(currentFunctionData, currentAppData)\n\n    const result = await runTargetAsync(currentFunctionData, 'build')\n    expect(result.stdout).toContain(\n      `Successfully ran target build for project ${currentFunctionData.projectName}`,\n    )\n\n    expectStrings(result.stderr, [`${currentFunctionData.distDir}/main.js`])\n    // make sure output build is not megabytes in size, which would mean we've\n    // bundled node_modules as well\n    expect(result.stdout).not.toContain('Mb')\n\n    const distPackageFile = `${currentFunctionData.distDir}/package.json`\n    expect(() => checkFilesExist(distPackageFile)).not.toThrow() \n\n    const distPackage = readJson(distPackageFile)\n    const deps = distPackage['dependencies']\n    expect(deps).toBeDefined()\n    // firebase-admin is No longer in the default main.ts template\n    // expect(deps['firebase-admin']).toBeDefined()\n    expect(deps['firebase-functions']).toBeDefined()\n  })\n\n  it('should add tags to the function project', async () => {\n    currentAppData = getProjectData('apps', uniq('firebaseApp'))\n    currentFunctionData = getProjectData('apps', uniq('firebaseFunction'))\n\n    await appGeneratorAsync(currentAppData)\n    await functionGeneratorAsync(\n      currentFunctionData,\n      `--app ${currentAppData.projectName}  --tags e2etag,e2ePackage`,\n    )\n\n    validateFunctionConfig(currentFunctionData, currentAppData)\n\n    const project = readJson(`${currentFunctionData.projectDir}/project.json`)\n    expect(project.tags).toEqual([\n      'firebase:function',\n      `firebase:name:${currentFunctionData.projectName}`,\n      `firebase:dep:${currentAppData.projectName}`,\n      'e2etag',\n      'e2ePackage',\n    ])\n  })\n})\n"
  },
  {
    "path": "e2e/nx-firebase-e2e/tests/test-libraries.spec.ts",
    "content": "import { readJson, checkFilesExist } from '@nx/plugin/testing'\nimport {\n  safeRunNxCommandAsync,\n  libGeneratorAsync,\n  buildableLibData,\n  nonbuildableLibData,\n  subDirBuildableLibData,\n  subDirNonbuildableLibData,\n} from '../test-utils'\n\n// Ensure daemon is disabled for all e2e tests\nbeforeAll(() => {\n  process.env['CI'] = 'true'\n  process.env['NX_DAEMON'] = 'false'\n})\n\nconst compileComplete = 'Done compiling TypeScript files for project'\nconst buildSuccess = 'Successfully ran target build for project'\n\n//--------------------------------------------------------------------------------------------------\n// Create Libraries for e2e function generator tests\n// NOTE: This test file creates shared libraries that other tests depend on.\n//       It does NOT clean up after itself as other tests use these libraries.\n//--------------------------------------------------------------------------------------------------\ndescribe('setup libraries', () => {\n  it('should create buildable typescript library', async () => {\n    await libGeneratorAsync(\n      buildableLibData,\n      `--bundler=tsc --importPath=\"${buildableLibData.npmScope}\"`,\n    )\n\n    // no need to test the js library generator, only that it ran ok\n    expect(() =>\n      checkFilesExist(`${buildableLibData.projectDir}/package.json`),\n    ).not.toThrow()\n\n    const result = await safeRunNxCommandAsync(\n      `build ${buildableLibData.projectName}`,\n    )\n    expect(result.stdout).toContain(compileComplete)\n    expect(result.stdout).toContain(\n      `${buildSuccess} ${buildableLibData.projectName}`,\n    )\n  })\n\n  it('should create buildable typescript library in subdir', async () => {\n    await libGeneratorAsync(\n      subDirBuildableLibData,\n      `--bundler=tsc --importPath=\"${subDirBuildableLibData.npmScope}\"`,\n    )\n\n    // no need to test the js library generator, only that it ran ok\n    expect(() =>\n      checkFilesExist(`${subDirBuildableLibData.projectDir}/package.json`),\n    ).not.toThrow()\n\n    const result = await safeRunNxCommandAsync(\n      `build ${subDirBuildableLibData.projectName}`,\n    )\n    expect(result.stdout).toContain(compileComplete)\n    expect(result.stdout).toContain(\n      `${buildSuccess} ${subDirBuildableLibData.projectName}`,\n    )\n  })\n\n  it('should create non-buildable typescript library', async () => {\n    await libGeneratorAsync(\n      nonbuildableLibData,\n      `--bundler=none --importPath=\"${nonbuildableLibData.npmScope}\"`,\n    )\n\n    expect(() =>\n      checkFilesExist(`${nonbuildableLibData.projectDir}/package.json`),\n    ).toThrow()\n\n    const project = readJson(`${nonbuildableLibData.projectDir}/project.json`)\n    expect(project.targets.build).not.toBeDefined()\n  })\n\n  it('should create non-buildable typescript library in subdir', async () => {\n    await libGeneratorAsync(\n      subDirNonbuildableLibData,\n      `--bundler=none --importPath=\"${subDirNonbuildableLibData.npmScope}\"`,\n    )\n\n    expect(() =>\n      checkFilesExist(`${subDirNonbuildableLibData.projectDir}/package.json`),\n    ).toThrow()\n\n    const project = readJson(\n      `${subDirNonbuildableLibData.projectDir}/project.json`,\n    )\n    expect(project.targets.build).not.toBeDefined()\n  })\n})\n"
  },
  {
    "path": "e2e/nx-firebase-e2e/tests/test-migrate.spec.ts",
    "content": "import { readJson, uniq, updateFile, renameFile } from '@nx/plugin/testing'\n\nimport {\n  ProjectData,\n  appGeneratorAsync,\n  cleanAppAsync,\n  cleanFunctionAsync,\n  functionGeneratorAsync,\n  getProjectData,\n  validateProjectConfig,\n  validateFunctionConfig,\n  migrateGeneratorAsync,\n  expectStrings,\n  expectNoStrings,\n  testDebug,\n} from '../test-utils'\nimport { ProjectConfiguration, joinPathFragments } from '@nx/devkit'\n\n// Ensure daemon is disabled for all e2e tests\nbeforeAll(() => {\n  process.env['CI'] = 'true'\n  process.env['NX_DAEMON'] = 'false'\n})\n\n//--------------------------------------------------------------------------------------------------\n// Test migrations\n//--------------------------------------------------------------------------------------------------\ndescribe('nx-firebase migrate', () => {\n  // Track current test's projects for cleanup\n  let currentAppData: ProjectData | null = null\n  let currentFunctionData: ProjectData | null = null\n\n  // Always run cleanup after each test, even on failure\n  afterEach(async () => {\n    // Clean up function first (it depends on app)\n    if (currentFunctionData) {\n      try {\n        await cleanFunctionAsync(currentFunctionData)\n      } catch (e) {\n        testDebug(`Function cleanup warning: ${(e as Error).message}`)\n      }\n      currentFunctionData = null\n    }\n    // Then clean up app\n    if (currentAppData) {\n      try {\n        await cleanAppAsync(currentAppData)\n      } catch (e) {\n        testDebug(`App cleanup warning: ${(e as Error).message}`)\n      }\n      currentAppData = null\n    }\n  })\n\n  it('should successfuly migrate for legacy app', async () => {\n    currentAppData = getProjectData('apps', uniq('firebaseMigrateApp'))\n    currentFunctionData = getProjectData('apps', uniq('firebaseMigrateFunction'))\n    await appGeneratorAsync(currentAppData)\n    await functionGeneratorAsync(\n      currentFunctionData,\n      `--app ${currentAppData.projectName}`,\n    )\n\n    const result = await migrateGeneratorAsync()\n    expectStrings(result.stdout, [`Running plugin migrations for workspace`])\n\n    // modify firebase app to be v2 schema\n    const projectFile = `${currentAppData.projectDir}/project.json`\n    const projectJson = readJson<ProjectConfiguration>(projectFile)\n    projectJson.targets['serve'].executor = 'nx:run-commands'\n    projectJson.targets[\n      'getconfig'\n    ].options.command = `nx run ${currentAppData.projectName}:firebase functions:config:get > ${currentAppData.projectDir}/.runtimeconfig.json`\n    updateFile(projectFile, JSON.stringify(projectJson, null, 3))\n\n    // remove environment folder from app\n    // cant delete in e2e, so lets just rename environment dir for now\n    renameFile(\n      joinPathFragments(currentAppData.projectDir, 'environment'),\n      joinPathFragments(currentAppData.projectDir, uniq('environment')),\n    )\n\n    // modify firebase.json to be v2 schema\n    const configFile = `firebase.json`\n    const configJson = readJson(configFile)\n    delete configJson.functions[0].ignore\n    updateFile(configFile, JSON.stringify(configJson, null, 3))\n\n    // remove globs from function project\n    const functionFile = `${currentFunctionData.projectDir}/project.json`\n    const functionJson = readJson<ProjectConfiguration>(functionFile)\n    const options = functionJson.targets['build'].options\n    const assets = options.assets as string[]\n    options.assets = [assets.shift()]\n    updateFile(functionFile, JSON.stringify(functionJson, null, 3))\n\n    // run migrate script\n    const result2 = await migrateGeneratorAsync()\n    expectStrings(result2.stdout, [\n      `MIGRATE Added default environment file 'environment/.env' for firebase app '${currentAppData.projectName}'`,\n      `MIGRATE Added default environment file 'environment/.env.local' for firebase app '${currentAppData.projectName}'`,\n      `MIGRATE Added default environment file 'environment/.secret.local' for firebase app '${currentAppData.projectName}'`,\n      `MIGRATE Updated getconfig target to use ignore environment directory for firebase app '${currentAppData.projectName}'`,\n      `MIGRATE Updated serve target for firebase app '${currentAppData.projectName}'`,\n      `MIGRATE Added assets glob for firebase function app '${currentFunctionData.projectName}'`,\n      `UPDATE firebase.json`,\n      `CREATE ${currentAppData.projectDir}/environment/.env`,\n      `CREATE ${currentAppData.projectDir}/environment/.env.local`,\n      `CREATE ${currentAppData.projectDir}/environment/.secret.local`,\n      `UPDATE ${currentAppData.projectDir}/project.json`,\n    ])\n\n    validateProjectConfig(currentAppData)\n\n    //todo: validateFunctionConfig - IMPORTANT since we missed some errors in last release due to this missing test\n    // where assets glob was malformed\n    validateFunctionConfig(currentFunctionData, currentAppData)\n\n    // run it again\n    const result3 = await migrateGeneratorAsync()\n    expectStrings(result.stdout, [`Running plugin migrations for workspace`])\n    expectNoStrings(result3.stdout, [\n      `MIGRATE Added default environment file 'environment/.env' for firebase app '${currentAppData.projectName}'`,\n      `MIGRATE Added default environment file 'environment/.env.local' for firebase app '${currentAppData.projectName}'`,\n      `MIGRATE Added default environment file 'environment/.secret.local' for firebase app '${currentAppData.projectName}'`,\n      `MIGRATE Updated getconfig target to use ignore environment directory for firebase app '${currentAppData.projectName}'`,\n      `MIGRATE Updated serve target for firebase app '${currentAppData.projectName}'`,\n      `MIGRATE Added assets glob for firebase function app '${currentFunctionData.projectName}'`,\n      `UPDATE firebase.json`,\n      `CREATE ${currentAppData.projectDir}/environment/.env`,\n      `CREATE ${currentAppData.projectDir}/environment/.env.local`,\n      `CREATE ${currentAppData.projectDir}/environment/.secret.local`,\n      `UPDATE ${currentAppData.projectDir}/project.json`,\n    ])\n  })\n})\n"
  },
  {
    "path": "e2e/nx-firebase-e2e/tests/test-sync.spec.ts",
    "content": "import { readJson, uniq, exists, checkFilesExist } from '@nx/plugin/testing'\n\nimport {\n  ProjectData,\n  appGeneratorAsync,\n  cleanAppAsync,\n  cleanFunctionAsync,\n  functionGeneratorAsync,\n  getProjectData,\n  syncGeneratorAsync,\n  validateProjectConfig,\n  validateFunctionConfig,\n  removeProjectAsync,\n  renameProjectAsync,\n  expectStrings,\n  testDebug,\n} from '../test-utils'\n\n// Ensure daemon is disabled for all e2e tests\nbeforeAll(() => {\n  process.env['CI'] = 'true'\n  process.env['NX_DAEMON'] = 'false'\n})\n\n//--------------------------------------------------------------------------------------------------\n// Test the nx-firebase sync generator\n//--------------------------------------------------------------------------------------------------\ndescribe('nx-firebase sync', () => {\n  // Track current test's projects for cleanup\n  let currentAppData: ProjectData | null = null\n  let currentAppData2: ProjectData | null = null\n  let currentFunctionData: ProjectData | null = null\n  let currentFunctionData2: ProjectData | null = null\n  let renamedAppData: ProjectData | null = null\n  let renamedFunctionData: ProjectData | null = null\n\n  // Always run cleanup after each test, even on failure\n  afterEach(async () => {\n    // Clean up functions first (they depend on apps)\n    if (renamedFunctionData) {\n      try {\n        await cleanFunctionAsync(renamedFunctionData)\n      } catch (e) {\n        testDebug(`Renamed function cleanup warning: ${(e as Error).message}`)\n      }\n      renamedFunctionData = null\n    }\n    if (currentFunctionData2) {\n      try {\n        await cleanFunctionAsync(currentFunctionData2)\n      } catch (e) {\n        testDebug(`Function2 cleanup warning: ${(e as Error).message}`)\n      }\n      currentFunctionData2 = null\n    }\n    if (currentFunctionData) {\n      try {\n        await cleanFunctionAsync(currentFunctionData)\n      } catch (e) {\n        testDebug(`Function cleanup warning: ${(e as Error).message}`)\n      }\n      currentFunctionData = null\n    }\n    // Then clean up apps\n    if (renamedAppData) {\n      try {\n        await cleanAppAsync(renamedAppData)\n      } catch (e) {\n        testDebug(`Renamed app cleanup warning: ${(e as Error).message}`)\n      }\n      renamedAppData = null\n    }\n    if (currentAppData2) {\n      try {\n        await cleanAppAsync(currentAppData2)\n      } catch (e) {\n        testDebug(`App2 cleanup warning: ${(e as Error).message}`)\n      }\n      currentAppData2 = null\n    }\n    if (currentAppData) {\n      try {\n        await cleanAppAsync(currentAppData)\n      } catch (e) {\n        testDebug(`App cleanup warning: ${(e as Error).message}`)\n      }\n      currentAppData = null\n    }\n  })\n\n  it('should sync firebase workspace with no changes', async () => {\n    const result = await syncGeneratorAsync()\n    expect(result.stdout).not.toContain('CHANGE')\n    expect(result.stdout).not.toContain('UPDATE')\n    expect(result.stdout).not.toContain('CREATE')\n    expect(result.stdout).not.toContain('DELETE')\n  })\n\n  describe('--project', () => {\n    it('should set firebase app project using --project', async () => {\n      currentAppData = getProjectData('apps', uniq('firebaseSyncApp'))\n      await appGeneratorAsync(currentAppData)\n\n      expect(\n        readJson(`${currentAppData.projectDir}/project.json`).targets.firebase\n          .options.command,\n      ).not.toContain(`--project`)\n      const result = await syncGeneratorAsync(\n        `--app=${currentAppData.projectName} --project=test`,\n      )\n      expectStrings(result.stdout, [\n        `CHANGE setting firebase target --project for '${currentAppData.projectName}' to '--project=test'`,\n        `UPDATE ${currentAppData.projectDir}/project.json`,\n      ])\n      expect(\n        readJson(`${currentAppData.projectDir}/project.json`).targets.firebase\n          .options.command,\n      ).toContain(`--project=test`)\n    })\n\n    it('should update firebase app project using --project', async () => {\n      currentAppData = getProjectData('apps', uniq('firebaseSyncApp'))\n      await appGeneratorAsync(currentAppData, `--project=test`)\n\n      expect(\n        readJson(`${currentAppData.projectDir}/project.json`).targets.firebase\n          .options.command,\n      ).toContain(`--project=test`)\n      const result = await syncGeneratorAsync(\n        `--app=${currentAppData.projectName} --project=test2`,\n      )\n      expectStrings(result.stdout, [\n        `CHANGE updating firebase target --project for '${currentAppData.projectName}' to '--project=test2'`,\n        `UPDATE ${currentAppData.projectDir}/project.json`,\n      ])\n      expect(\n        readJson(`${currentAppData.projectDir}/project.json`).targets.firebase\n          .options.command,\n      ).toContain(`--project=test2`)\n    })\n  })\n\n  describe('deletions', () => {\n    it('should detect deleted firebase functions', async () => {\n      currentAppData = getProjectData('apps', uniq('firebaseSyncApp'))\n      currentFunctionData = getProjectData('apps', uniq('firebaseSyncFunction'))\n      await appGeneratorAsync(currentAppData)\n      await functionGeneratorAsync(\n        currentFunctionData,\n        `--app ${currentAppData.projectName}`,\n      )\n\n      await removeProjectAsync(currentFunctionData)\n      // Mark as null since we manually removed it\n      const removedFunctionData = currentFunctionData\n      currentFunctionData = null\n\n      const result = await syncGeneratorAsync()\n      expectStrings(result.stdout, [\n        `CHANGE Firebase function '${removedFunctionData.projectName}' was deleted, removing function codebase from '${currentAppData.configName}'`,\n        `UPDATE ${currentAppData.configName}`,\n      ])\n    })\n\n    it('should detect deleted firebase apps', async () => {\n      currentAppData = getProjectData('apps', uniq('firebaseSyncApp'))\n      currentFunctionData = getProjectData('apps', uniq('firebaseSyncFunction'))\n      await appGeneratorAsync(currentAppData)\n      await functionGeneratorAsync(\n        currentFunctionData,\n        `--app ${currentAppData.projectName}`,\n      )\n\n      await removeProjectAsync(currentAppData)\n      // Mark as null since we manually removed it\n      const removedAppData = currentAppData\n      currentAppData = null\n\n      const result = await syncGeneratorAsync()\n      expectStrings(result.stdout, [`DELETE ${removedAppData.configName}`])\n      expectStrings(result.stderr, [\n        `CHANGE Firebase app '${removedAppData.projectName}' was deleted, firebase:dep tag for firebase function '${currentFunctionData.projectName}' is no longer linked to a Firebase app.`,\n      ])\n    })\n\n    it('should warn when no firebase apps use firebase.json config', async () => {\n      currentAppData = getProjectData('apps', uniq('firebaseSyncApp'))\n      currentAppData2 = getProjectData('apps', uniq('firebaseSyncApp'), {\n        customConfig: true,\n      })\n      await appGeneratorAsync(currentAppData)\n      await appGeneratorAsync(currentAppData2)\n\n      // delete the app that used firebase.json\n      await removeProjectAsync(currentAppData)\n      const removedAppData = currentAppData\n      currentAppData = null\n\n      const result = await syncGeneratorAsync()\n      expectStrings(result.stderr, [\n        `None of the Firebase apps in this workspace use 'firebase.json' as their config. Firebase CLI may not work as expected. This can be fixed by renaming the config for one of your firebase projects to 'firebase.json'.`,\n      ])\n    })\n  })\n\n  describe('renames', () => {\n    it('should detect renamed firebase functions', async () => {\n      currentAppData = getProjectData('apps', uniq('firebaseSyncApp'))\n      currentFunctionData = getProjectData('apps', uniq('firebaseSyncFunction'))\n      renamedFunctionData = getProjectData('apps', uniq('firebaseSyncFunction'))\n      await appGeneratorAsync(currentAppData)\n      await functionGeneratorAsync(\n        currentFunctionData,\n        `--app ${currentAppData.projectName}`,\n      )\n\n      expect(\n        readJson(`${currentFunctionData.projectDir}/project.json`).targets\n          .deploy.options.command,\n      ).toContain(`--only functions:${currentFunctionData.projectName}`)\n\n      await renameProjectAsync(currentFunctionData, renamedFunctionData)\n      // The original function is now renamed, clear the reference\n      const originalFunctionData = currentFunctionData\n      currentFunctionData = null\n\n      const result = await syncGeneratorAsync()\n\n      expectStrings(result.stdout, [\n        `CHANGE Firebase function '${originalFunctionData.projectName}' was renamed to '${renamedFunctionData.projectName}', updated firebase:name tag`,\n        `CHANGE Firebase function '${originalFunctionData.projectName}' was renamed to '${renamedFunctionData.projectName}', updated codebase in '${currentAppData.configName}'`,\n        `CHANGE Firebase function '${originalFunctionData.projectName}' was renamed to '${renamedFunctionData.projectName}', updated deploy target to '--only=functions:${renamedFunctionData.projectName}'`,\n        `UPDATE ${renamedFunctionData.projectDir}/project.json`,\n        `UPDATE ${currentAppData.configName}`,\n      ])\n\n      expect(\n        readJson(`${renamedFunctionData.projectDir}/project.json`).targets\n          .deploy.options.command,\n      ).toContain(`--only functions:${renamedFunctionData.projectName}`)\n\n      validateFunctionConfig(renamedFunctionData, currentAppData)\n    })\n\n    it('should detect renamed firebase apps', async () => {\n      currentAppData = getProjectData('apps', uniq('firebaseSyncApp'))\n      currentFunctionData = getProjectData('apps', uniq('firebaseSyncFunction'))\n      currentFunctionData2 = getProjectData(\n        'apps',\n        uniq('firebaseSyncFunction'),\n      )\n      renamedAppData = getProjectData('apps', uniq('firebaseSyncApp'))\n\n      await appGeneratorAsync(currentAppData)\n      await functionGeneratorAsync(\n        currentFunctionData,\n        `--app ${currentAppData.projectName}`,\n      )\n      await functionGeneratorAsync(\n        currentFunctionData2,\n        `--app ${currentAppData.projectName}`,\n      )\n\n      await renameProjectAsync(currentAppData, renamedAppData)\n      const originalAppData = currentAppData\n      currentAppData = null\n\n      const result = await syncGeneratorAsync()\n\n      expectStrings(result.stdout, [\n        `CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', updated firebase:name tag`,\n        `CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', updated targets`,\n        `CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', updated firebase:dep tag in firebase function '${currentFunctionData.projectName}'`,\n        `CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', updated environment assets path in firebase function '${currentFunctionData.projectName}'`,\n        `CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', updated firebase:dep tag in firebase function '${currentFunctionData2.projectName}'`,\n        `CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', updated environment assets path in firebase function '${currentFunctionData2.projectName}'`,\n        `CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', updated database rules in '${renamedAppData.configName}'`,\n        `CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', updated firestore rules in '${renamedAppData.configName}'`,\n        `CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', updated firestore indexes in '${renamedAppData.configName}'`,\n        `CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', updated storage rules in '${renamedAppData.configName}'`,\n        `CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', updated firebase deploy command in firebase function '${currentFunctionData.projectName}'`,\n        `CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', updated firebase deploy command in firebase function '${currentFunctionData2.projectName}'`,\n        `UPDATE ${renamedAppData.configName}`,\n        `UPDATE ${renamedAppData.projectDir}/project.json`,\n        `UPDATE ${currentFunctionData.projectDir}/project.json`,\n        `UPDATE ${currentFunctionData2.projectDir}/project.json`,\n      ])\n\n      expectStrings(result.stderr, [\n        `WARNING: Can't match hosting target with public dir '${originalAppData.projectDir}/public' in '${renamedAppData.configName}' to a project in this workspace. Is it configured correctly?`,\n      ])\n\n      // we should not rename config if it is called firebase.json\n      expect(result.stdout).not.toContain(\n        `CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', renamed config file to '${renamedAppData.configName}'`,\n      )\n      expect(result.stdout).not.toContain(`DELETE ${originalAppData.configName}`)\n      expect(result.stdout).not.toContain(\n        `CREATE ${renamedAppData.configName}`,\n      )\n\n      // check that app project has correct --config setting after rename\n      expect(\n        readJson(`${renamedAppData.projectDir}/project.json`).targets.firebase\n          .options.command,\n      ).toContain(`--config=${renamedAppData.configName}`)\n\n      // check rename was successful\n      validateProjectConfig(renamedAppData)\n      validateFunctionConfig(currentFunctionData, renamedAppData)\n      validateFunctionConfig(currentFunctionData2, renamedAppData)\n\n      // run another sync to check there should be no orphaned functions from an app rename\n      const result2 = await syncGeneratorAsync()\n      expect(result2.stderr).not.toContain(\n        'is no longer linked to a Firebase app',\n      )\n      expect(result2.stdout).not.toContain('UPDATE')\n    })\n\n    it('should detect renamed firebase apps & functions', async () => {\n      currentAppData = getProjectData('apps', uniq('firebaseSyncApp'))\n      currentFunctionData = getProjectData('apps', uniq('firebaseSyncFunction'))\n      renamedFunctionData = getProjectData('apps', uniq('firebaseSyncFunction'))\n      renamedAppData = getProjectData('apps', uniq('firebaseSyncApp'))\n\n      await appGeneratorAsync(currentAppData)\n      await functionGeneratorAsync(\n        currentFunctionData,\n        `--app ${currentAppData.projectName}`,\n      )\n\n      // rename app & function\n      await renameProjectAsync(currentAppData, renamedAppData)\n      await renameProjectAsync(currentFunctionData, renamedFunctionData)\n      const originalAppData = currentAppData\n      const originalFunctionData = currentFunctionData\n      currentAppData = null\n      currentFunctionData = null\n\n      const result = await syncGeneratorAsync()\n\n      expectStrings(result.stdout, [\n        `CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', updated firebase:name tag`,\n        `CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', updated targets`,\n        `CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', updated firebase:dep tag in firebase function '${renamedFunctionData.projectName}'`,\n        `CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', updated environment assets path in firebase function '${renamedFunctionData.projectName}'`,\n        `CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', updated database rules in '${renamedAppData.configName}'`,\n        `CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', updated firestore rules in '${renamedAppData.configName}'`,\n        `CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', updated firestore indexes in '${renamedAppData.configName}'`,\n        `CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', updated storage rules in '${renamedAppData.configName}'`,\n        `CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', updated firebase deploy command in firebase function '${renamedFunctionData.projectName}'`,\n        `CHANGE Firebase function '${originalFunctionData.projectName}' was renamed to '${renamedFunctionData.projectName}', updated firebase:name tag`,\n        `CHANGE Firebase function '${originalFunctionData.projectName}' was renamed to '${renamedFunctionData.projectName}', updated deploy target to '--only=functions:${renamedFunctionData.projectName}'`,\n        `CHANGE Firebase function '${originalFunctionData.projectName}' was renamed to '${renamedFunctionData.projectName}', updated codebase in '${renamedAppData.configName}'`,\n        `UPDATE ${renamedAppData.projectDir}/project.json`,\n        `UPDATE ${renamedFunctionData.projectDir}/project.json`,\n      ])\n\n      expectStrings(result.stderr, [\n        `WARNING: Can't match hosting target with public dir '${originalAppData.projectDir}/public' in '${renamedAppData.configName}' to a project in this workspace. Is it configured correctly?`,\n      ])\n\n      // we should not rename config if it is called firebase.json\n      expect(result.stdout).not.toContain(\n        `CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', renamed config file to '${renamedAppData.configName}'`,\n      )\n      expect(result.stdout).not.toContain(`DELETE ${originalAppData.configName}`)\n      expect(result.stdout).not.toContain(\n        `CREATE ${renamedAppData.configName}`,\n      )\n\n      // check that app project has correct --config setting after rename\n      expect(\n        readJson(`${renamedAppData.projectDir}/project.json`).targets.firebase\n          .options.command,\n      ).toContain(`--config=${renamedAppData.configName}`)\n      // check that function project has correct --config setting after rename\n      expect(\n        readJson(`${renamedFunctionData.projectDir}/project.json`).targets\n          .deploy.options.command,\n      ).toContain(`--only functions:${renamedFunctionData.projectName}`)\n\n      // check rename was successful\n      validateProjectConfig(renamedAppData)\n      validateFunctionConfig(renamedFunctionData, renamedAppData)\n    })\n\n    it('should rename configs for renamed firebase apps when multiple apps in workspace', async () => {\n      // Nx 20 exists() function does not use the e2e tmpPath (bug?) so we need to use checkFilesExist instead\n      // expect(exists('firebase.json')).toBe(false)\n      expect(() => checkFilesExist('firebase.json')).toThrow() \n      \n      // create first project that will have the primary firebase.json config\n      currentAppData = getProjectData('apps', uniq('firebaseSyncApp'))\n      currentFunctionData = getProjectData('apps', uniq('firebaseSyncFunction'))\n      await appGeneratorAsync(currentAppData)\n\n      expect(currentAppData.configName).toEqual('firebase.json')\n      expect(\n        readJson(`${currentAppData.projectDir}/project.json`).targets.firebase\n          .options.command,\n      ).toContain(`--config=firebase.json`)\n\n      // expect(exists('firebase.json')).toBe(true)\n      expect(() => checkFilesExist('firebase.json')).not.toThrow() \n\n      // generate second app after first app is generated so that first config is detected\n      currentAppData2 = getProjectData('apps', uniq('firebaseSyncApp'), {\n        customConfig: true,\n      })\n      renamedAppData = getProjectData('apps', uniq('firebaseSyncApp'), {\n        customConfig: true,\n      })\n\n      expect(currentAppData2.configName).not.toEqual('firebase.json')\n      expect(renamedAppData.configName).not.toEqual('firebase.json')\n\n      await appGeneratorAsync(currentAppData2)\n      await functionGeneratorAsync(\n        currentFunctionData,\n        `--app ${currentAppData2.projectName}`,\n      )\n\n      expect(\n        readJson(`${currentAppData2.projectDir}/project.json`).targets.firebase\n          .options.command,\n      ).not.toContain(`--config=firebase.json`)\n      expect(\n        readJson(`${currentAppData2.projectDir}/project.json`).targets.firebase\n          .options.command,\n      ).toContain(`--config=${currentAppData2.configName}`)\n\n      await renameProjectAsync(currentAppData2, renamedAppData)\n      const originalAppData2 = currentAppData2\n      currentAppData2 = null\n\n      const result = await syncGeneratorAsync()\n\n      expectStrings(result.stdout, [\n        `CHANGE Firebase app '${originalAppData2.projectName}' was renamed to '${renamedAppData.projectName}', renamed config file to '${renamedAppData.configName}'`,\n        `CHANGE Firebase app '${originalAppData2.projectName}' was renamed to '${renamedAppData.projectName}', updated firebase:name tag`,\n        `CHANGE Firebase app '${originalAppData2.projectName}' was renamed to '${renamedAppData.projectName}', updated firebase:dep tag in firebase function '${currentFunctionData.projectName}'`,\n        `CHANGE Firebase app '${originalAppData2.projectName}' was renamed to '${renamedAppData.projectName}', updated database rules in '${renamedAppData.configName}'`,\n        `CHANGE Firebase app '${originalAppData2.projectName}' was renamed to '${renamedAppData.projectName}', updated firestore rules in '${renamedAppData.configName}'`,\n        `CHANGE Firebase app '${originalAppData2.projectName}' was renamed to '${renamedAppData.projectName}', updated firestore indexes in '${renamedAppData.configName}'`,\n        `CHANGE Firebase app '${originalAppData2.projectName}' was renamed to '${renamedAppData.projectName}', updated storage rules in '${renamedAppData.configName}'`,\n        `CHANGE Firebase app '${originalAppData2.projectName}' was renamed to '${renamedAppData.projectName}', updated firebase deploy command in firebase function '${currentFunctionData.projectName}'`,\n        `UPDATE ${renamedAppData.projectDir}/project.json`,\n        `UPDATE ${currentFunctionData.projectDir}/project.json`,\n        `DELETE ${originalAppData2.configName}`,\n        `CREATE ${renamedAppData.configName}`,\n      ])\n\n      expectStrings(result.stderr, [\n        `WARNING: Can't match hosting target with public dir '${originalAppData2.projectDir}/public' in '${renamedAppData.configName}' to a project in this workspace. Is it configured correctly?`,\n      ])\n\n      // check that app project has correct --config setting after rename\n      expect(\n        readJson(`${renamedAppData.projectDir}/project.json`).targets.firebase\n          .options.command,\n      ).toContain(`--config=${renamedAppData.configName}`)\n\n      // run another sync to check there should be no orphaned functions from an app rename\n      const result2 = await syncGeneratorAsync()\n      expect(result2.stderr).not.toContain(\n        'is no longer linked to a Firebase app',\n      )\n      expect(result2.stdout).not.toContain('UPDATE')\n    })\n  })\n})\n"
  },
  {
    "path": "e2e/nx-firebase-e2e/tests/test-targets.spec.ts",
    "content": "import { uniq } from '@nx/plugin/testing'\n\nimport {\n  ProjectData,\n  appGeneratorAsync,\n  cleanAppAsync,\n  cleanFunctionAsync,\n  functionGeneratorAsync,\n  getProjectData,\n  runTargetAsync,\n  expectStrings,\n  testDebug,\n} from '../test-utils'\n\n// Ensure daemon is disabled for all e2e tests\nbeforeAll(() => {\n  process.env['CI'] = 'true'\n  process.env['NX_DAEMON'] = 'false'\n})\n\n// Since targets is the last e2e test suite to run, we can disable cleanup here to leave the e2e\n// tmp workspace intact for inspection if needed.\nconst ENABLE_CLEANUP = false\n\n//--------------------------------------------------------------------------------------------------\n// Test app targets\n//--------------------------------------------------------------------------------------------------\ndescribe('nx-firebase app targets', () => {\n  // Track current test's projects for cleanup\n  let currentAppData: ProjectData | null = null\n  let currentFunctionData: ProjectData | null = null\n  let currentFunctionData2: ProjectData | null = null\n\n  // Only cleanup if enabled\n  if (ENABLE_CLEANUP) {\n    // Always run cleanup after each test, even on failure\n    afterEach(async () => {\n      // Clean up functions first (they depend on apps)\n      if (currentFunctionData2) {\n        try {\n          await cleanFunctionAsync(currentFunctionData2)\n        } catch (e) {\n          testDebug(`Function2 cleanup warning: ${(e as Error).message}`)\n        }\n        currentFunctionData2 = null\n      }\n      if (currentFunctionData) {\n        try {\n          await cleanFunctionAsync(currentFunctionData)\n        } catch (e) {\n          testDebug(`Function cleanup warning: ${(e as Error).message}`)\n        }\n        currentFunctionData = null\n      }\n      // Then clean up app\n      if (currentAppData) {\n        try {\n          await cleanAppAsync(currentAppData)\n        } catch (e) {\n          testDebug(`App cleanup warning: ${(e as Error).message}`)\n        }\n        currentAppData = null\n      }\n    \n    })\n  }\n  it('should run lint target for app', async () => {\n    currentAppData = getProjectData('apps', uniq('firebaseTargetsApp'))\n    currentFunctionData = getProjectData('apps', uniq('firebaseTargetsFunction'))\n    currentFunctionData2 = getProjectData(\n      'apps',\n      uniq('firebaseTargetsFunction'),\n    )\n    await appGeneratorAsync(currentAppData)\n    await functionGeneratorAsync(\n      currentFunctionData,\n      `--app ${currentAppData.projectName}`,\n    )\n    await functionGeneratorAsync(\n      currentFunctionData2,\n      `--app ${currentAppData.projectName}`,\n    )\n\n    const result = await runTargetAsync(currentAppData, 'lint')\n    expectStrings(result.stdout, [\n      `nx run ${currentAppData.projectName}:lint`,\n      `nx run ${currentFunctionData.projectName}:lint`,\n      `nx run ${currentFunctionData2.projectName}:lint`,\n      // SM March 2024: The firebase SDK templates dont pass linting !\n      // `All files pass linting`,\n      `Successfully ran target lint for 2 projects`,\n      `Successfully ran target lint for project ${currentAppData.projectName}`,\n    ])\n  })\n\n  it('should run test target for app', async () => {\n    currentAppData = getProjectData('apps', uniq('firebaseTargetsApp'))\n    currentFunctionData = getProjectData('apps', uniq('firebaseTargetsFunction'))\n    currentFunctionData2 = getProjectData(\n      'apps',\n      uniq('firebaseTargetsFunction'),\n    )\n    await appGeneratorAsync(currentAppData)\n    await functionGeneratorAsync(\n      currentFunctionData,\n      `--app ${currentAppData.projectName}`,\n    )\n    await functionGeneratorAsync(\n      currentFunctionData2,\n      `--app ${currentAppData.projectName}`,\n    )\n\n    const result = await runTargetAsync(currentAppData, 'test')\n    expectStrings(result.stdout, [\n      `nx run ${currentAppData.projectName}:test`,\n      `Running target test for 2 projects`,\n      `nx run ${currentFunctionData.projectName}:test`,\n      `nx run ${currentFunctionData2.projectName}:test`,\n      `Successfully ran target test for 2 projects`,\n      `Successfully ran target test for project ${currentAppData.projectName}`,\n    ])\n  })\n\n  it('should run deploy target for app', async () => {\n    currentAppData = getProjectData('apps', uniq('firebaseTargetsApp'))\n    currentFunctionData = getProjectData('apps', uniq('firebaseDepsFunction'))\n    currentFunctionData2 = getProjectData('apps', uniq('firebaseDepsFunction'))\n    await appGeneratorAsync(currentAppData)\n    await functionGeneratorAsync(\n      currentFunctionData,\n      `--app ${currentAppData.projectName}`,\n    )\n    await functionGeneratorAsync(\n      currentFunctionData2,\n      `--app ${currentAppData.projectName}`,\n    )\n\n    // deploy target will fail because theres no firebase project but thats ok\n    // we cannot e2e a real firebase project setup atm\n    const result = await runTargetAsync(currentAppData, 'deploy')\n    expectStrings(result.stdout, [\n      `Running target deploy for project ${currentAppData.projectName}`,\n      `nx run ${currentAppData.projectName}:deploy`,\n      `nx run ${currentAppData.projectName}:firebase deploy`,\n    ])\n    // build target will also execute, since functions are implicit dep of app\n    // In Nx 17+, when build runs as a dependency of deploy, all output goes to stdout\n    expectStrings(result.stdout, [\n      `nx run ${currentAppData.projectName}:build`,\n      `nx run ${currentFunctionData.projectName}:build`,\n      `nx run ${currentFunctionData2.projectName}:build`,\n      `Build succeeded`,\n      `${currentFunctionData.distDir}/main.js`,\n      `${currentFunctionData2.distDir}/main.js`,\n    ])\n  })\n\n  it('should run deploy target for function', async () => {\n    currentAppData = getProjectData('apps', uniq('firebaseTargetsApp'))\n    currentFunctionData = getProjectData(\n      'apps',\n      uniq('firebaseTargetsFunction'),\n    )\n    await appGeneratorAsync(currentAppData)\n    await functionGeneratorAsync(\n      currentFunctionData,\n      `--app ${currentAppData.projectName}`,\n    )\n\n    const result = await runTargetAsync(currentFunctionData, 'deploy')\n    expectStrings(result.stdout, [\n      `Running target deploy for project ${currentFunctionData.projectName}`,\n      `nx run ${currentAppData.projectName}:deploy`,\n      `nx run ${currentAppData.projectName}:firebase deploy`,\n    ])\n    // build target will also execute, since functions are implicit dep of app\n    expectStrings(result.stdout, [\n      `nx run ${currentFunctionData.projectName}:build`,\n      `Build succeeded`,\n    ])\n    expectStrings(result.stderr, [`${currentFunctionData.distDir}/main.js`])\n  })\n})\n"
  },
  {
    "path": "e2e/nx-firebase-e2e/tests/test-workspace.spec.ts",
    "content": "import { readJson, tmpProjPath } from '@nx/plugin/testing'\nimport { detectPackageManager } from '@nx/devkit'\nimport { safeRunNxCommandAsync } from '../test-utils'\n\n// Ensure daemon is disabled for all e2e tests\nbeforeAll(() => {\n  process.env['CI'] = 'true'\n  process.env['NX_DAEMON'] = 'false'\n})\n\n//--------------------------------------------------------------------------------------------------\n// Test the workspace setup & init generator\n// NOTE: This test file runs first and sets up the workspace for other tests.\n//       It does NOT clean up after itself as other tests depend on this state.\n//--------------------------------------------------------------------------------------------------\ndescribe('workspace setup', () => {\n  it('should create workspace without firebase dependencies', async () => {\n    // test that generator adds dependencies to workspace package.json\n    // should not be initially set\n    const packageJson = readJson(`package.json`)\n    expect(packageJson.dependencies['firebase']).toBeUndefined()\n    expect(packageJson.dependencies['firebase-admin']).toBeUndefined()\n    expect(packageJson.dependencies['firebase-functions']).toBeUndefined()\n    expect(\n      packageJson.devDependencies['firebase-functions-test'],\n    ).toBeUndefined()\n    expect(packageJson.devDependencies['firebase-tools']).toBeUndefined()\n  })\n\n  it('should create workspace without nx dependencies', async () => {\n    // test that generator adds dependencies to workspace package.json\n    // should not be initially set\n    const packageJson = readJson(`package.json`)\n    expect(packageJson.devDependencies['@nx/node']).toBeUndefined()\n  })\n\n  it('should run nx-firebase init', async () => {\n    await safeRunNxCommandAsync(`generate @simondotm/nx-firebase:init`)\n    // test that generator adds dependencies to workspace package.json\n    const packageJson = readJson(`package.json`)\n    expect(packageJson.dependencies['firebase']).toBeDefined()\n    expect(packageJson.dependencies['firebase-admin']).toBeDefined()\n    expect(packageJson.dependencies['firebase-functions']).toBeDefined()\n    expect(\n      packageJson.devDependencies['firebase-functions-test'],\n    ).toBeDefined()\n\n    // check that plugin init generator adds @google-cloud/functions-framework if pnpm is being used\n    // Use tmpProjPath() to detect the package manager in the e2e workspace, not the main project\n    if (detectPackageManager(tmpProjPath()) === 'pnpm') {\n      expect(\n        packageJson.dependencies['@google-cloud/functions-framework'],\n      ).toBeDefined()\n    } else {\n      expect(\n        packageJson.dependencies['@google-cloud/functions-framework'],\n      ).not.toBeDefined()\n    }\n\n    // test that generator adds dev dependencies to workspace package.json\n    expect(packageJson.devDependencies['firebase-tools']).toBeDefined()\n    //SM: Mar'24: our plugin init generator now only add @nx/node\n    expect(packageJson.devDependencies['@nx/node']).toBeDefined()\n  })\n})\n"
  },
  {
    "path": "e2e/nx-firebase-e2e/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.base.json\",\n  \"files\": [],\n  \"include\": [],\n  \"references\": [\n    {\n      \"path\": \"./tsconfig.spec.json\"\n    }\n  ]\n}\n"
  },
  {
    "path": "e2e/nx-firebase-e2e/tsconfig.spec.json",
    "content": "{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../dist/out-tsc\",\n    \"module\": \"commonjs\",\n    \"types\": [\"jest\", \"node\"]\n  },\n  \"include\": [\"jest.config.ts\", \"**/*.test.ts\", \"**/*.spec.ts\", \"**/*.d.ts\"]\n}\n"
  },
  {
    "path": "eslint.config.mjs",
    "content": "import { FlatCompat } from '@eslint/eslintrc'\nimport { dirname } from 'path'\nimport { fileURLToPath } from 'url'\nimport js from '@eslint/js'\nimport nxEslintPlugin from '@nx/eslint-plugin'\nimport eslintPluginPrettier from 'eslint-plugin-prettier'\nimport eslintPluginUnusedImports from 'eslint-plugin-unused-imports'\n\nconst compat = new FlatCompat({\n  baseDirectory: dirname(fileURLToPath(import.meta.url)),\n  recommendedConfig: js.configs.recommended,\n})\n\nexport default [\n  {\n    ignores: ['**/dist'],\n  },\n  {\n    plugins: {\n      '@nx': nxEslintPlugin,\n      prettier: eslintPluginPrettier,\n      'unused-imports': eslintPluginUnusedImports,\n    },\n  },\n  {\n    files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],\n    rules: {\n      '@nx/enforce-module-boundaries': [\n        'error',\n        {\n          enforceBuildableLibDependency: true,\n          allow: [],\n          depConstraints: [\n            {\n              sourceTag: '*',\n              onlyDependOnLibsWithTags: ['*'],\n            },\n          ],\n        },\n      ],\n    },\n  },\n  ...compat\n    .config({\n      extends: ['plugin:@nx/typescript'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts'],\n      rules: {\n        ...config.rules,\n        '@typescript-eslint/no-restricted-types': 'warn',\n        '@typescript-eslint/no-require-imports': 'warn',\n        '@typescript-eslint/no-inferrable-types': 'warn',\n        '@typescript-eslint/no-empty-object-type': 'warn',\n        '@typescript-eslint/no-empty-function': 'warn',\n        'no-prototype-builtins': 'warn',\n        'no-useless-escape': 'warn',\n        'no-inner-declarations': 'off',\n        'no-fallthrough': 'warn',\n        'no-irregular-whitespace': 'warn',\n        'no-constant-condition': 'warn',\n        '@typescript-eslint/require-await': 'warn',\n        '@typescript-eslint/no-floating-promises': 'error',\n        '@typescript-eslint/await-thenable': 'error',\n        '@typescript-eslint/no-namespace': 'warn',\n        '@typescript-eslint/no-this-alias': 'warn',\n        'no-duplicate-imports': 'error',\n        '@typescript-eslint/no-unused-vars': 'off',\n        'unused-imports/no-unused-imports': 'warn',\n        'unused-imports/no-unused-vars': [\n          'warn',\n          {\n            vars: 'all',\n            varsIgnorePattern: '^_',\n            args: 'after-used',\n            argsIgnorePattern: '^_',\n          },\n        ],\n      },\n    })),\n  ...compat\n    .config({\n      extends: ['plugin:prettier/recommended'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.ts', '**/*.tsx'],\n      rules: {\n        ...config.rules,\n        'prettier/prettier': 'warn',\n      },\n    })),\n  ...compat\n    .config({\n      extends: ['plugin:@nx/javascript'],\n    })\n    .map((config) => ({\n      ...config,\n      files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'],\n      rules: {\n        ...config.rules,\n      },\n    })),\n]\n"
  },
  {
    "path": "jest.config.ts",
    "content": "const { getJestProjectsAsync } = require('@nx/jest')\n\nexport default async () => ({\n  projects: await getJestProjectsAsync(),\n  testEnvironment: 'node',\n});\n"
  },
  {
    "path": "jest.preset.js",
    "content": "const nxPreset = require('@nx/jest/preset').default\n\nmodule.exports = {\n  ...nxPreset,\n  /* TODO: Update to latest Jest snapshotFormat\n   * By default Nx has kept the older style of Jest Snapshot formats\n   * to prevent breaking of any existing tests with snapshots.\n   * It's recommend you update to the latest format.\n   * You can do this by removing snapshotFormat property\n   * and running tests with --update-snapshot flag.\n   * Example: \"nx affected --targets=e2e --update-snapshot\"\n   * More info: https://jestjs.io/docs/upgrading-to-jest29#snapshot-format\n   */\n  snapshotFormat: { escapeString: true, printBasicPrototype: true },\n}\n"
  },
  {
    "path": "migrations.json",
    "content": "{\n  \"migrations\": [\n    {\n      \"version\": \"22.0.0-beta.1\",\n      \"description\": \"Updates release version config based on the breaking changes in Nx v22\",\n      \"implementation\": \"./src/migrations/update-22-0-0/release-version-config-changes\",\n      \"package\": \"nx\",\n      \"name\": \"22-0-0-release-version-config-changes\"\n    },\n    {\n      \"version\": \"22.0.0-beta.2\",\n      \"description\": \"Consolidates releaseTag* options into nested releaseTag object structure\",\n      \"implementation\": \"./src/migrations/update-22-0-0/consolidate-release-tag-config\",\n      \"package\": \"nx\",\n      \"name\": \"22-0-0-consolidate-release-tag-config\"\n    },\n    {\n      \"cli\": \"nx\",\n      \"version\": \"22.1.0-beta.5\",\n      \"description\": \"Updates the nx wrapper.\",\n      \"implementation\": \"./src/migrations/update-22-1-0/update-nx-wrapper\",\n      \"package\": \"nx\",\n      \"name\": \"22-1-0-update-nx-wrapper\"\n    },\n    {\n      \"version\": \"22.0.0-beta.0\",\n      \"description\": \"Remove the deprecated `external` and `externalBuildTargets` options from the `@nx/js:swc` and `@nx/js:tsc` executors.\",\n      \"factory\": \"./src/migrations/update-22-0-0/remove-external-options-from-js-executors\",\n      \"package\": \"@nx/js\",\n      \"name\": \"remove-external-options-from-js-executors\"\n    },\n    {\n      \"version\": \"22.1.0-rc.1\",\n      \"description\": \"Removes redundant TypeScript project references from project's tsconfig.json files when runtime tsconfig files (e.g., tsconfig.lib.json, tsconfig.app.json) exist.\",\n      \"factory\": \"./src/migrations/update-22-1-0/remove-redundant-ts-project-references\",\n      \"package\": \"@nx/js\",\n      \"name\": \"remove-redundant-ts-project-references\"\n    },\n    {\n      \"version\": \"22.2.0-beta.2\",\n      \"description\": \"Convert jest.config.ts files from ESM to CJS syntax (export default -> module.exports, import -> require) for projects using CommonJS resolution to ensure correct loading under Node.js type-stripping.\",\n      \"implementation\": \"./src/migrations/update-22-2-0/convert-jest-config-to-cjs\",\n      \"package\": \"@nx/jest\",\n      \"name\": \"convert-jest-config-to-cjs\"\n    },\n    {\n      \"version\": \"22.3.2-beta.0\",\n      \"requires\": { \"jest\": \">=30.0.0\" },\n      \"description\": \"Replace removed matcher aliases in Jest v30 with their corresponding matcher\",\n      \"implementation\": \"./src/migrations/update-21-3-0/replace-removed-matcher-aliases\",\n      \"package\": \"@nx/jest\",\n      \"name\": \"replace-removed-matcher-aliases-v22-3\"\n    },\n    {\n      \"cli\": \"nx\",\n      \"version\": \"22.0.0-beta.0\",\n      \"description\": \"Remove deprecated deleteOutputPath and sassImplementation options from @nx/webpack:webpack\",\n      \"implementation\": \"./src/migrations/update-22-0-0/remove-deprecated-options\",\n      \"package\": \"@nx/webpack\",\n      \"name\": \"update-22-0-0-remove-deprecated-options\"\n    }\n  ]\n}\n"
  },
  {
    "path": "nx.json",
    "content": "{\n  \"workspaceLayout\": {\n    \"appsDir\": \"e2e\",\n    \"libsDir\": \"packages\"\n  },\n  \"defaultProject\": \"nx-firebase\",\n  \"$schema\": \"./node_modules/nx/schemas/nx-schema.json\",\n  \"targetDefaults\": {\n    \"build\": {\n      \"dependsOn\": [\"^build\"],\n      \"inputs\": [\"production\", \"^production\"],\n      \"cache\": true\n    },\n    \"@nx/eslint:lint\": {\n      \"inputs\": [\n        \"default\",\n        \"{workspaceRoot}/.eslintrc.json\",\n        \"{workspaceRoot}/eslint.config.mjs\"\n      ],\n      \"cache\": true\n    },\n    \"@nx/jest:jest\": {\n      \"cache\": true,\n      \"inputs\": [\"default\", \"^production\", \"{workspaceRoot}/jest.preset.js\"],\n      \"options\": {\n        \"passWithNoTests\": true\n      },\n      \"configurations\": {\n        \"ci\": {\n          \"ci\": true,\n          \"codeCoverage\": true\n        }\n      }\n    }\n  },\n  \"namedInputs\": {\n    \"default\": [\"{projectRoot}/**/*\", \"sharedGlobals\"],\n    \"sharedGlobals\": [],\n    \"production\": [\n      \"default\",\n      \"!{projectRoot}/.eslintrc.json\",\n      \"!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)\",\n      \"!{projectRoot}/tsconfig.spec.json\",\n      \"!{projectRoot}/jest.config.[jt]s\",\n      \"!{projectRoot}/src/test-setup.[jt]s\",\n      \"!{projectRoot}/eslint.config.mjs\"\n    ]\n  },\n  \"useInferencePlugins\": false,\n  \"defaultBase\": \"main\"\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"simondotm\",\n  \"version\": \"0.0.0\",\n  \"license\": \"MIT\",\n  \"packageManager\": \"pnpm@8.15.5\",\n  \"scripts\": {\n    \"preinstall\": \"npx only-allow pnpm\",\n    \"postinstall\": \"node ./tools/generate-package-versions.js\",\n    \"start\": \"nx serve\",\n    \"build\": \"nx run nx-firebase:build\",\n    \"test\": \"nx run nx-firebase:test\",\n    \"lint\": \"nx run nx-firebase:lint\",\n    \"e2e\": \"pnpm exec nx reset && rm -rf dist/packages/nx-firebase && rm -rf tmp && pnpm build && nx run nx-firebase-e2e:e2e --bail=true --silent=true\",\n    \"release\": \"cd packages/nx-firebase && npm version\",\n    \"release-help\": \"echo `npm run version -- v1.2.3` to set package version & commit tag\",\n    \"compat:test\": \"npm run build && nx run compat:build && node dist/e2e/compat/main.js\",\n    \"compat:clean\": \"nx run compat:build && node dist/e2e/compat/main.js --clean\",\n    \"compat:setup\": \"npm run build && nx run compat:build && node dist/e2e/compat/main.js --setup\"\n  },\n  \"private\": true,\n  \"devDependencies\": {\n    \"@eslint/eslintrc\": \"^2.1.1\",\n    \"@google-cloud/functions-framework\": \"^3.3.0\",\n    \"@nx/devkit\": \"22.3.3\",\n    \"@nx/eslint\": \"22.3.3\",\n    \"@nx/eslint-plugin\": \"22.3.3\",\n    \"@nx/jest\": \"22.3.3\",\n    \"@nx/js\": \"22.3.3\",\n    \"@nx/node\": \"22.3.3\",\n    \"@nx/plugin\": \"22.3.3\",\n    \"@nx/webpack\": \"22.3.3\",\n    \"@nx/workspace\": \"22.3.3\",\n    \"@types/jest\": \"30.0.0\",\n    \"@types/node\": \"20.19.0\",\n    \"@types/semver\": \"^7.3.13\",\n    \"eslint\": \"^9.8.0\",\n    \"eslint-config-prettier\": \"10.1.8\",\n    \"eslint-plugin-prettier\": \"^5.x\",\n    \"eslint-plugin-unused-imports\": \"^4.x\",\n    \"firebase\": \"12.8.0\",\n    \"firebase-admin\": \"13.6.0\",\n    \"firebase-functions\": \"7.0.3\",\n    \"firebase-functions-test\": \"3.4.1\",\n    \"firebase-tools\": \"15.3.1\",\n    \"jest\": \"30.0.5\",\n    \"jest-environment-jsdom\": \"30.0.5\",\n    \"kill-port\": \"2.0.1\",\n    \"nx\": \"22.3.3\",\n    \"only-allow\": \"^1.2.1\",\n    \"prettier\": \"^3.x\",\n    \"semver\": \"^7.6.0\",\n    \"ts-jest\": \"29.4.6\",\n    \"tslib\": \"^2.0.0\",\n    \"typescript\": \"5.9.3\",\n    \"typescript-eslint\": \"^8.19.0\",\n    \"jest-util\": \"30.0.5\"\n  },\n  \"dependencies\": {\n    \"ts-node\": \"^10.9.2\",\n    \"tslib\": \"^2.0.0\"\n  }\n}\n"
  },
  {
    "path": "packages/nx-firebase/README.md",
    "content": "# @simondotm/nx-firebase ![actions](https://github.com/simondotm/nx-firebase/actions/workflows/ci.yml/badge.svg) ![npm](https://img.shields.io/npm/v/@simondotm/nx-firebase) ![downloads](https://img.shields.io/npm/dw/@simondotm/nx-firebase.svg)\n\nA plugin for [Nx](https://nx.dev) that integrates Firebase workflows in an Nx monorepo workspace.\n\n* Easily generate Firebase applications and functions\n* Uses `esbuild` for fast Firebase function builds so you can easily create & import shared Nx libraries with the benefits of tree-shaking\n* Supports function environment variables and secrets\n* Supports single or multiple firebase projects/apps within an Nx workspace\n* Full support for the Firebase Emulator suite for local development, with watch mode for functions\n* Keeps your `firebase.json` configurations in sync when renaming or deleting Firebase apps & functions\n* Only very lightly opinionated about your Firebase configurations and workspace layouts; you can use Nx or the Firebase CLI\n\nSee [CHANGELOG](https://github.com/simondotm/nx-firebase/blob/main/CHANGELOG.md) for release notes.\n\n## Install Plugin\n\n**`npm install @simondotm/nx-firebase --save-dev`**\n\n- Installs this plugin into your Nx workspace\n- This will also install `@nx/node` and firebase SDK's to your root workspace `package.json` if they are not already installed\n\n## Generate Firebase Application\n\n**`nx g @simondotm/nx-firebase:app my-new-firebase-app [--directory=dir] [--project=proj]`**\n\n- Generates a new Nx Firebase application project in the workspace\n- The app generator will also create a Firebase configuration file in the root of your workspace (along with a default `.firebaserc` and `firebase.json` if they don't already exist)\n- For the first firebase application you create, the project firebase configuration will be `firebase.json`\n- If you create additional firebase applications, the project firebase configuration will be `firebase.<app-project-name>.json`\n- Use `--project` to link your Firebase App to a Firebase project name in your `.firebaserc` file\n\n## Generate Firebase Function\n\n**`nx g @simondotm/nx-firebase:function my-new-firebase-function --app=my-new-firebase-app [--directory=dir]`**\n\n- Generates a new Nx Firebase function application project in the workspace\n- Firebase Function projects must be linked to a Firebase application project with the `--app` option\n- Firebase Function projects can contain one or more firebase functions\n- You can generate as many Firebase Function projects as you need for your application\n\n## Build \n\n**`nx build my-new-firebase-app`**\n\n- Compiles & builds all Firebase function applications linked to the Nx Firebase application or an individual function\n\n**`nx build my-new-firebase-function`**\n\n- Compiles & builds an individual function\n\n\n## Serve\n\n**`nx serve my-new-firebase-app`**\n\n- Builds & Watches all Firebase functions apps linked to the Firebase application\n- Starts the Firebase emulators\n\n## Deploy\n\n### Firebase Application\n\n**`nx deploy my-new-firebase-app [--only ...]`**\n\n- By default, deploys ALL of your cloud resources associated with your Firebase application (eg. sites, functions, database rules etc.)\n- Use the `--only` option to selectively deploy (same as Firebase CLI)\n\nFor initial deployment:\n\n- **`firebase login`** if not already authenticated\n- **`firebase use --add`** to add your Firebase Project(s) to the `.firebaserc` file in your workspace. This step must be completed before you can deploy anything to Firebase.\n\nNote that you can also use the firebase CLI directly if you prefer:\n\n- **`firebase deploy --config=firebase.<appname>.json --only functions`**\n\n### Firebase Function\n\n**`nx deploy my-new-firebase-function`**\n\n- Deploys only a specific Firebase function\n\n\n\n## Test\n\n**`nx test my-new-firebase-app`**\n\n- Runs unit tests for all Firebase functions apps linked to the Firebase application\n\n**`nx test my-new-firebase-function`**\n\n- Runs unit tests for an individual function\n\n\n## Lint\n\n**`nx lint my-new-firebase-app`**\n\n- Runs linter for all Firebase functions apps linked to the Firebase application or an individual function\n\n**`nx lint my-new-firebase-function`**\n\n- Runs linter for an individual function\n\n## Sync Workspace\n\n**`nx g @simondotm/nx-firebase:sync`**\n\n- Ensures that your `firebase.json` configurations are kept up to date with your workspace\n  - If you rename or move firebase application or firebase function projects\n  - If you delete firebase function projects\n\n## Further Information\n\nSee the full plugin [User Guide](https://github.com/simondotm/nx-firebase/blob/main/docs/user-guide.md) for more details."
  },
  {
    "path": "packages/nx-firebase/eslint.config.mjs",
    "content": "import { FlatCompat } from '@eslint/eslintrc'\nimport { dirname } from 'path'\nimport { fileURLToPath } from 'url'\nimport js from '@eslint/js'\nimport baseConfig from '../../eslint.config.mjs'\n\nconst compat = new FlatCompat({\n  baseDirectory: dirname(fileURLToPath(import.meta.url)),\n  recommendedConfig: js.configs.recommended,\n})\n\nexport default [\n  {\n    ignores: ['**/dist'],\n  },\n  ...baseConfig,\n  {\n    files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],\n    // Override or add rules here\n    rules: {},\n    languageOptions: {\n      parserOptions: {\n        project: ['packages/nx-firebase/tsconfig.*?.json'],\n      },\n    },\n  },\n  {\n    files: ['**/*.ts', '**/*.tsx'],\n    // Override or add rules here\n    rules: {},\n  },\n  {\n    files: ['**/*.js', '**/*.jsx'],\n    // Override or add rules here\n    rules: {},\n  },\n  {\n    files: ['./package.json', './generators.json', './executors.json'],\n    rules: {\n      '@nx/nx-plugin-checks': 'error',\n    },\n    languageOptions: {\n      parser: await import('jsonc-eslint-parser'),\n    },\n  },\n]\n"
  },
  {
    "path": "packages/nx-firebase/executors.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/schema\",\n  \"executors\": {\n    \"serve\": {\n      \"implementation\": \"./src/executors/serve/serve\",\n      \"schema\": \"./src/executors/serve/schema.json\",\n      \"description\": \"firebase cli emulation serve executor\"\n    }\n  }\n}\n"
  },
  {
    "path": "packages/nx-firebase/generators.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/schema\",\n  \"name\": \"nx-firebase\",\n  \"version\": \"0.0.1\",\n  \"generators\": {\n    \"init\": {\n      \"factory\": \"./src/generators/init/init\",\n      \"schema\": \"./src/generators/init/schema.json\",\n      \"description\": \"nx-firebase initializer\",\n      \"hidden\": true\n    },\n    \"application\": {\n      \"factory\": \"./src/generators/application/application\",\n      \"schema\": \"./src/generators/application/schema.json\",\n      \"description\": \"nx-firebase application generator\",\n      \"aliases\": [\"app\"]\n    },\n    \"function\": {\n      \"factory\": \"./src/generators/function/function\",\n      \"schema\": \"./src/generators/function/schema.json\",\n      \"description\": \"nx-firebase function generator\",\n      \"aliases\": [\"func\"]\n    },\n    \"sync\": {\n      \"factory\": \"./src/generators/sync/sync\",\n      \"schema\": \"./src/generators/sync/schema.json\",\n      \"description\": \"nx-firebase project sync tool\"\n    },\n    \"migrate\": {\n      \"factory\": \"./src/generators/migrate/migrate\",\n      \"schema\": \"./src/generators/migrate/schema.json\",\n      \"description\": \"nx-firebase project migration tool\"\n    }\n  }\n}\n"
  },
  {
    "path": "packages/nx-firebase/jest.config.ts",
    "content": "/* eslint-disable */\nexport default {\n  displayName: 'nx-firebase',\n  preset: '../../jest.preset.js',\n  globals: {},\n  testEnvironment: 'node',\n  setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],\n  transform: {\n    '^.+\\\\.[tj]s$': [\n      'ts-jest',\n      {\n        tsconfig: '<rootDir>/tsconfig.spec.json',\n      },\n    ],\n  },\n  moduleFileExtensions: ['ts', 'js', 'html'],\n}\n"
  },
  {
    "path": "packages/nx-firebase/jest.setup.ts",
    "content": "// Disable the Nx daemon during unit tests\nprocess.env.NX_DAEMON = 'false'\n\n// Mock the project graph to prevent unit tests from depending on the\n// actual Nx workspace structure. This is recommended by the Nx team\n// to isolate unit tests and ensure repeatable results.\n// See: https://github.com/nrwl/nx/blob/master/scripts/unit-test-setup.js\njest.doMock('@nx/devkit', () => ({\n  ...jest.requireActual('@nx/devkit'),\n  createProjectGraphAsync: jest.fn().mockImplementation(async () => {\n    return {\n      nodes: {},\n      dependencies: {},\n    }\n  }),\n}))\n"
  },
  {
    "path": "packages/nx-firebase/package.json",
    "content": "{\n  \"name\": \"@simondotm/nx-firebase\",\n  \"version\": \"22.3.3\",\n  \"description\": \"A Firebase plugin for Nx monorepo workspaces.\",\n  \"author\": \"Simon Morris\",\n  \"license\": \"MIT\",\n  \"homepage\": \"https://github.com/simondotm/nx-firebase\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git@github.com:simondotm/nx-firebase.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/simondotm/nx-firebase/issues\"\n  },\n  \"keywords\": [\n    \"Firebase\",\n    \"Nx\",\n    \"Monorepo\"\n  ],\n  \"main\": \"src/index.js\",\n  \"generators\": \"./generators.json\",\n  \"executors\": \"./executors.json\",\n  \"peerDependencies\": {\n    \"@nx/devkit\": \">= 22.3.3\",\n    \"@nx/workspace\": \">= 22.3.3\",\n    \"nx\": \">= 22.3.3\"\n  },\n  \"dependencies\": {}\n}\n"
  },
  {
    "path": "packages/nx-firebase/project.json",
    "content": "{\n  \"name\": \"nx-firebase\",\n  \"$schema\": \"../../node_modules/nx/schemas/project-schema.json\",\n  \"sourceRoot\": \"packages/nx-firebase/src\",\n  \"tags\": [],\n  \"targets\": {\n    \"build\": {\n      \"executor\": \"@nx/js:tsc\",\n      \"outputs\": [\"{options.outputPath}\"],\n      \"options\": {\n        \"outputPath\": \"dist/packages/nx-firebase\",\n        \"main\": \"packages/nx-firebase/src/index.ts\",\n        \"tsConfig\": \"packages/nx-firebase/tsconfig.lib.json\",\n        \"assets\": [\n          \"packages/nx-firebase/*.md\",\n          {\n            \"input\": \"./packages/nx-firebase/src\",\n            \"glob\": \"**/!(*.ts)\",\n            \"output\": \"./src\"\n          },\n          {\n            \"input\": \"./packages/nx-firebase/src\",\n            \"glob\": \"**/*.d.ts\",\n            \"output\": \"./src\"\n          },\n          {\n            \"input\": \"./packages/nx-firebase\",\n            \"glob\": \"generators.json\",\n            \"output\": \".\"\n          },\n          {\n            \"input\": \"./packages/nx-firebase\",\n            \"glob\": \"executors.json\",\n            \"output\": \".\"\n          }\n        ]\n      }\n    },\n    \"lint\": {\n      \"executor\": \"@nx/eslint:lint\",\n      \"outputs\": [\"{options.outputFile}\"]\n    },\n    \"test\": {\n      \"executor\": \"@nx/jest:jest\",\n      \"outputs\": [\"{workspaceRoot}/coverage/packages/nx-firebase\"],\n      \"options\": {\n        \"jestConfig\": \"packages/nx-firebase/jest.config.ts\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "packages/nx-firebase/src/__generated__/nx-firebase-versions.ts",
    "content": "//------------------------------------------------------------------------------\n// This file is automatically generated by tools/generate-package-versions.js\n// Do not edit this file manually\n//------------------------------------------------------------------------------\nexport const packageVersions = {\n  nx: '22.3.3',\n  firebase: '12.8.0',\n  firebaseAdmin: '13.6.0',\n  firebaseFunctions: '7.0.3',\n  firebaseFunctionsTest: '3.4.1',\n  firebaseTools: '15.3.1',\n  killPort: '2.0.1',\n  nodeEngine: '20',\n  googleCloudFunctionsFramework: '3.3.0',\n}\n"
  },
  {
    "path": "packages/nx-firebase/src/executors/serve/schema.d.ts",
    "content": "// maintains the existing executor format as `nx:run-commands` for compatibility\nexport interface FirebaseServeExecutorSchema {\n  commands: string[]\n  __unparsed__: string[]\n}\n"
  },
  {
    "path": "packages/nx-firebase/src/executors/serve/schema.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/schema\",\n  \"cli\": \"nx\",\n  \"title\": \"Firebase serve\",\n  \"description\": \"Runs the Firebase CLI emulator for the target project\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"commands\": {\n      \"description\": \"The serve commands - watch & emulate.\",\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"__unparsed__\": {\n      \"hidden\": true,\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"string\"\n      },\n      \"$default\": {\n        \"$source\": \"unparsed\"\n      },\n      \"x-priority\": \"internal\"\n    }\n  },\n  \"required\": []\n}\n"
  },
  {
    "path": "packages/nx-firebase/src/executors/serve/serve.spec.ts",
    "content": "// import { FirebaseServeExecutorSchema } from './schema'\n// import runFirebaseServeExecutor from './firebase'\n\n// const options: FirebaseServeExecutorSchema = {}\n\ndescribe('Firebase Serve Executor', () => {\n  it('can run', async () => {\n    // const output = await runFirebaseServeExecutor([])\n    // expect(output.success).toBe(true)\n  })\n})\n"
  },
  {
    "path": "packages/nx-firebase/src/executors/serve/serve.ts",
    "content": "import {\n  ExecutorContext,\n  logger,\n  TargetConfiguration,\n  ProjectConfiguration,\n} from '@nx/devkit'\nimport { FirebaseServeExecutorSchema } from './schema'\nimport { spawn } from 'child_process'\n\ntype NxRunCommandsTargetConfiguration = TargetConfiguration<{\n  command?: string\n  commands?: string[]\n}>\n\ntype ValidTarget = 'firebase' | 'watch' | 'emulate'\n\nfunction getCommandFromTarget(\n  project: ProjectConfiguration,\n  targetName: ValidTarget,\n  commandGrep: string,\n) {\n  const target = project.targets[targetName] as NxRunCommandsTargetConfiguration\n  if (!target) {\n    throw new Error(\n      `Could not find target '${targetName}' in project '${project.name}'`,\n    )\n  }\n  const commands: string[] = [\n    ...(target.options.command ? [target.options.command] : []),\n    ...(target.options.commands ? target.options.commands : []),\n  ].filter((cmd) => cmd.includes(commandGrep))\n\n  if (commands.length === 0) {\n    throw new Error(\n      `Could not find a command in target '${targetName}' that matches '${commandGrep}'`,\n    )\n  }\n\n  if (commands.length !== 1) {\n    logger.warn(\n      `Found multiple commands in target '${targetName}' that match '${commandGrep}', using first match`,\n    )\n  }\n  return commands[0]\n}\n\nexport default async function runFirebaseServeExecutor(\n  options: FirebaseServeExecutorSchema,\n  context: ExecutorContext,\n) {\n  return new Promise<{ success: boolean }>((resolve) => {\n    const projectName = context.projectName\n    const projects = context.projectsConfigurations\n    const project = projects.projects[projectName]\n\n    // Determine the watch target command\n    const watchCommand = getCommandFromTarget(\n      project,\n      'watch',\n      'nx run-many --targets=build',\n    )\n\n    // Determine the firebase target command, so we get --config & --project\n    const firebaseCommand = getCommandFromTarget(\n      project,\n      'firebase',\n      'firebase',\n    )\n\n    // Determine the emulator target command\n    const emulateCommand = getCommandFromTarget(\n      project,\n      'emulate',\n      'emulators:',\n    ).replace(`nx run ${projectName}:firebase`, '')\n\n    // Run the watch process\n    // eslint-disable-next-line\n    const watchProcess = spawn(watchCommand, [], {\n      shell: true,\n      stdio: 'inherit',\n      detached: false,\n    }).on('exit', (code) => {\n      if (!code) {\n        logger.warn(`serve: watch process finished successfully`)\n      } else {\n        logger.error(`serve: watch process finished with error '${code}'`)\n      }\n    })\n\n    // determine any extra commands passed on the command line\n    const extraArgs =\n      options.__unparsed__.length > 0\n        ? ' ' + options.__unparsed__.join(' ')\n        : ''\n\n    // Run the firebase emulator process\n    const emulatorProcess = spawn(\n      `${firebaseCommand} ${emulateCommand} ${extraArgs}`,\n      [],\n      {\n        shell: true,\n        stdio: 'inherit',\n        detached: false,\n      },\n    )\n    emulatorProcess.on('exit', (code) => {\n      if (!code) {\n        logger.warn(`serve: Firebase emulator finished successfully`)\n        resolve({ success: true }) // not sure what difference this makes\n      } else {\n        logger.error(`serve: Firebase emulator finished with error '${code}'`)\n        resolve({ success: false })\n      }\n    })\n\n    // Handle signals for serve executor process\n    const processExitListener = (signal) => {\n      // logger.error(`\\nserve: executor received signal '${signal}'`)\n      if (signal === 'SIGINT') {\n        logger.warn(`\\nserve: terminating`)\n        // no need for these it seems\n        // emulatorProcess.kill()\n        // watchProcess.kill()\n      }\n      if (signal === 0) {\n        logger.warn(`serve: finished successfully`)\n      }\n      if (signal === 1) {\n        logger.error(`serve: finished with errors`)\n      }\n    }\n    process.on('exit', processExitListener)\n    process.on('SIGTERM', processExitListener)\n    process.on('SIGINT', processExitListener)\n  })\n}\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/application/application.spec.ts",
    "content": "import { getProjects, readProjectConfiguration, Tree } from '@nx/devkit'\nimport { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'\nimport { applicationGenerator } from './application'\n\ndescribe('application generator', () => {\n  let tree: Tree\n\n  beforeEach(() => {\n    tree = createTreeWithEmptyWorkspace()\n    jest.clearAllMocks()\n  })\n\n  it('should generate workspace', () => {\n    expect(tree.exists(`firebase.json`)).toBeFalsy()\n    expect(tree.exists(`.firebaserc`)).toBeFalsy()\n    expect(tree.isFile(`package.json`)).toBeTruthy()\n  })\n\n  it('should update project config', async () => {\n    await applicationGenerator(tree, {\n      name: 'myFirebaseApp',\n    })\n    const project = readProjectConfiguration(tree, 'myFirebaseApp')\n    expect(project.root).toEqual('myFirebaseApp')\n    expect(project.targets).toEqual(\n      expect.objectContaining({\n        build: {\n          executor: 'nx:run-commands',\n          dependsOn: ['^build'],\n          options: {\n            command: `echo Build succeeded.`,\n          },\n        },\n        watch: {\n          executor: 'nx:run-commands',\n          options: {\n            command: `nx run-many --targets=build --projects=tag:firebase:dep:myFirebaseApp --parallel=100 --watch`,\n          },\n        },\n        lint: {\n          executor: 'nx:run-commands',\n          options: {\n            command: `nx run-many --targets=lint --projects=tag:firebase:dep:myFirebaseApp --parallel=100`,\n          },\n        },\n        test: {\n          executor: 'nx:run-commands',\n          options: {\n            command: `nx run-many --targets=test --projects=tag:firebase:dep:myFirebaseApp --parallel=100`,\n          },\n        },\n        firebase: {\n          executor: 'nx:run-commands',\n          options: {\n            command: `firebase --config=firebase.json`,\n          },\n          configurations: {\n            production: {\n              command: `firebase --config=firebase.json`,\n            },\n          },\n        },\n        killports: {\n          executor: 'nx:run-commands',\n          options: {\n            command: `kill-port --port 9099,5001,8080,9000,5000,8085,9199,9299,4000,4400,4500`,\n          },\n        },\n        getconfig: {\n          executor: 'nx:run-commands',\n          options: {\n            command: `nx run myFirebaseApp:firebase functions:config:get > myFirebaseApp/environment/.runtimeconfig.json`,\n          },\n        },\n        emulate: {\n          executor: 'nx:run-commands',\n          options: {\n            commands: [\n              `nx run myFirebaseApp:killports`,\n              `nx run myFirebaseApp:firebase emulators:start --import=myFirebaseApp/.emulators --export-on-exit`,\n            ],\n            parallel: false,\n          },\n        },\n        serve: {\n          executor: '@simondotm/nx-firebase:serve',\n          options: {\n            commands: [\n              `nx run myFirebaseApp:watch`,\n              `nx run myFirebaseApp:emulate`,\n            ],\n          },\n        },\n        deploy: {\n          executor: 'nx:run-commands',\n          dependsOn: ['build'],\n          options: {\n            command: `nx run myFirebaseApp:firebase deploy`,\n          },\n        },\n      }),\n    )\n  })\n\n  it('should update tags', async () => {\n    await applicationGenerator(tree, {\n      name: 'myFirebaseApp',\n      tags: 'one,two',\n    })\n    const projects = Object.fromEntries(getProjects(tree))\n    expect(projects).toMatchObject({\n      myFirebaseApp: {\n        tags: ['firebase:app', 'firebase:name:myFirebaseApp', 'one', 'two'],\n      },\n    })\n  })\n\n  it('should generate files', async () => {\n    await applicationGenerator(tree, {\n      name: 'myFirebaseApp',\n    })\n    const root = 'myFirebaseApp'\n    // default firebase project files\n    expect(tree.exists(`${root}/public/index.html`)).toBeTruthy()\n    expect(tree.exists(`${root}/readme.md`)).toBeTruthy()\n    // rules & indexes\n    expect(tree.exists(`${root}/database.rules.json`)).toBeTruthy()\n    expect(tree.exists(`${root}/firestore.indexes.json`)).toBeTruthy()\n    expect(tree.exists(`${root}/firestore.rules`)).toBeTruthy()\n    expect(tree.exists(`${root}/storage.rules`)).toBeTruthy()\n    // workspace firebase configs\n    expect(tree.isFile(`package.json`)).toBeTruthy()\n    expect(tree.isFile(`firebase.json`)).toBeTruthy()\n    expect(tree.isFile(`.firebaserc`)).toBeTruthy()\n    // environment files\n    expect(tree.isFile(`${root}/environment/.secret.local`)).toBeTruthy()\n    expect(tree.isFile(`${root}/environment/.env`)).toBeTruthy()\n    expect(tree.isFile(`${root}/environment/.env.local`)).toBeTruthy()\n  })\n\n  it('should generate multiple firebase configurations', async () => {\n    await applicationGenerator(tree, { name: 'myFirebaseApp1' })\n    await applicationGenerator(tree, { name: 'myFirebaseApp2' })\n    expect(tree.isFile(`firebase.json`)).toBeTruthy()\n    expect(tree.isFile(`firebase.myFirebaseApp2.json`)).toBeTruthy()\n  })\n\n  it('should generate app in subdirectory', async () => {\n    await applicationGenerator(tree, {\n      name: 'myFirebaseApp',\n      directory: 'subDir',\n    })\n\n    // default firebase project files\n    const root = `subDir`\n    expect(tree.exists(`${root}/public/index.html`)).toBeTruthy()\n    expect(tree.exists(`${root}/readme.md`)).toBeTruthy()\n    // rules & indexes\n    expect(tree.exists(`${root}/database.rules.json`)).toBeTruthy()\n    expect(tree.exists(`${root}/firestore.indexes.json`)).toBeTruthy()\n    expect(tree.exists(`${root}/firestore.rules`)).toBeTruthy()\n    expect(tree.exists(`${root}/storage.rules`)).toBeTruthy()\n  })\n})\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/application/application.ts",
    "content": "import {\n  GeneratorCallback,\n  Tree,\n  convertNxGenerator,\n  runTasksInSerial,\n  addProjectConfiguration,\n} from '@nx/devkit'\n\nimport { createFiles } from './lib'\n\n// import { getProjectName } from '../../utils'\nimport type { Schema, NormalizedSchema } from './schema'\nimport initGenerator from '../init/init'\nimport { determineProjectNameAndRootOptions } from '@nx/devkit/src/generators/project-name-and-root-utils'\n\nexport async function normalizeOptions(\n  host: Tree,\n  options: Schema,\n): Promise<NormalizedSchema> {\n  // In Nx 20+, directory is required. If not provided, use name as directory.\n  const directory = options.directory ?? options.name\n\n  const { projectName: appProjectName, projectRoot } =\n    await determineProjectNameAndRootOptions(host, {\n      name: options.name,\n      projectType: 'application',\n      directory,\n    })\n\n  const parsedTags = options.tags\n    ? options.tags.split(',').map((s) => s.trim())\n    : []\n\n  // const { projectName, projectRoot } = getProjectName(\n  //   host,\n  //   options.name,\n  //   options.directory,\n  // )\n\n  /**\n   * Plugin filename naming convention for firebase.json config is:\n   *  firebase config will be `firebase.json` for the first firebase app\n   *  additional apps will use `firebase.<projectname>.json`\n   *  this makes the config filename deterministic for the plugin\n   *\n   * - plugin can try `firebase.<projectname>.json` and use if exists\n   * - otherwise fallback is `firebase.json`\n   */\n  const firebaseConfigName = host.exists('firebase.json')\n    ? `firebase.${appProjectName}.json`\n    : 'firebase.json'\n\n  // firebase config name has to be unique.\n  if (host.exists(firebaseConfigName)) {\n    throw Error(\n      `There is already a firebase configuration called '${firebaseConfigName}' in this workspace. Please use a different project name.`,\n    )\n  }\n\n  return {\n    ...options,\n    projectName: appProjectName,\n    projectRoot,\n    parsedTags,\n    firebaseConfigName,\n  }\n\n  // return {\n  //   ...options,\n  //   projectRoot,\n  //   projectName,\n  //   firebaseConfigName,\n  // }\n}\n\n/**\n * Firebase application generator\n *\n * @param host\n * @param schema\n * @returns\n */\nexport async function applicationGenerator(\n  host: Tree,\n  schema: Schema,\n): Promise<GeneratorCallback> {\n  const options = await normalizeOptions(host, schema)\n  const initTask = await initGenerator(host, {})\n\n  const firebaseCliProject = options.project\n    ? ` --project=${options.project}`\n    : ''\n\n  const tags = [\n    `firebase:app`,\n    `firebase:name:${options.projectName}`,\n    ...options.parsedTags,\n  ]\n  // if (options.tags) {\n  //   options.tags.split(',').map((s) => {\n  //     s.trim()\n  //     tags.push(s)\n  //   })\n  // }\n\n  addProjectConfiguration(host, options.projectName, {\n    root: options.projectRoot,\n    projectType: 'application',\n    targets: {\n      build: {\n        executor: 'nx:run-commands',\n        // Build all implicit dependencies (firebase functions) first\n        dependsOn: ['^build'],\n        options: {\n          command: `echo Build succeeded.`,\n        },\n      },\n      watch: {\n        executor: 'nx:run-commands',\n        options: {\n          command: `nx run-many --targets=build --projects=tag:firebase:dep:${options.projectName} --parallel=100 --watch`,\n        },\n      },\n      lint: {\n        executor: 'nx:run-commands',\n        options: {\n          command: `nx run-many --targets=lint --projects=tag:firebase:dep:${options.projectName} --parallel=100`,\n        },\n      },\n      test: {\n        executor: 'nx:run-commands',\n        options: {\n          command: `nx run-many --targets=test --projects=tag:firebase:dep:${options.projectName} --parallel=100`,\n        },\n      },\n      firebase: {\n        executor: 'nx:run-commands',\n        options: {\n          command: `firebase --config=${options.firebaseConfigName}${firebaseCliProject}`,\n        },\n        configurations: {\n          production: {\n            command: `firebase --config=${options.firebaseConfigName}${firebaseCliProject}`,\n          },\n        },\n      },\n      killports: {\n        executor: 'nx:run-commands',\n        options: {\n          command: `kill-port --port 9099,5001,8080,9000,5000,8085,9199,9299,4000,4400,4500`,\n        },\n      },\n      getconfig: {\n        executor: 'nx:run-commands',\n        options: {\n          command: `nx run ${options.projectName}:firebase functions:config:get > ${options.projectRoot}/environment/.runtimeconfig.json`,\n        },\n      },\n      emulate: {\n        executor: 'nx:run-commands',\n        options: {\n          commands: [\n            `nx run ${options.projectName}:killports`,\n            `nx run ${options.projectName}:firebase emulators:start --import=${options.projectRoot}/.emulators --export-on-exit`,\n          ],\n          parallel: false,\n        },\n      },\n      serve: {\n        executor: '@simondotm/nx-firebase:serve',\n        options: {\n          commands: [\n            `nx run ${options.projectName}:watch`,\n            `nx run ${options.projectName}:emulate`,\n          ],\n        },\n      },\n      deploy: {\n        executor: 'nx:run-commands',\n        dependsOn: ['build'],\n        options: {\n          command: `nx run ${options.projectName}:firebase deploy`,\n        },\n      },\n    },\n    tags,\n  })\n\n  createFiles(host, options)\n\n  return runTasksInSerial(initTask)\n}\n\nexport default applicationGenerator\nexport const applicationSchematic = convertNxGenerator(applicationGenerator)\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/application/files/database.rules.json",
    "content": "{\n  \"rules\": {\n    \".read\": false,\n    \".write\": false\n  }\n}\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/application/files/environment/.secret.local",
    "content": "# Firebase function emulator secret environment variables\n# https://firebase.google.com/docs/functions/config-env?gen=2nd#secrets_and_credentials_in_the_emulator\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/application/files/firestore.indexes.json",
    "content": "{\n  // Example (Standard Edition):\n  //\n  // \"indexes\": [\n  //   {\n  //     \"collectionGroup\": \"widgets\",\n  //     \"queryScope\": \"COLLECTION\",\n  //     \"fields\": [\n  //       { \"fieldPath\": \"foo\", \"arrayConfig\": \"CONTAINS\" },\n  //       { \"fieldPath\": \"bar\", \"mode\": \"DESCENDING\" }\n  //     ]\n  //   },\n  //\n  //  \"fieldOverrides\": [\n  //    {\n  //      \"collectionGroup\": \"widgets\",\n  //      \"fieldPath\": \"baz\",\n  //      \"indexes\": [\n  //        { \"order\": \"ASCENDING\", \"queryScope\": \"COLLECTION\" }\n  //      ]\n  //    },\n  //   ]\n  // ]\n  //\n  // Example (Enterprise Edition):\n  //\n  // \"indexes\": [\n  //   {\n  //     \"collectionGroup\": \"reviews\",\n  //     \"queryScope\": \"COLLECTION_GROUP\",\n  //     \"apiScope\": \"MONGODB_COMPATIBLE_API\",\n  //     \"density\": \"DENSE\",\n  //     \"multikey\": false,\n  //     \"fields\": [\n  //       { \"fieldPath\": \"baz\", \"mode\": \"ASCENDING\" }\n  //     ]\n  //   },\n  //   {\n  //     \"collectionGroup\": \"items\",\n  //     \"queryScope\": \"COLLECTION_GROUP\",\n  //     \"apiScope\": \"MONGODB_COMPATIBLE_API\",\n  //     \"density\": \"SPARSE_ANY\",\n  //     \"multikey\": true,\n  //     \"fields\": [\n  //       { \"fieldPath\": \"baz\", \"mode\": \"ASCENDING\" }\n  //     ]\n  //   },\n  // ]\n  \"indexes\": [],\n  \"fieldOverrides\": []\n}"
  },
  {
    "path": "packages/nx-firebase/src/generators/application/files/firestore.rules",
    "content": "rules_version='2'\n\nservice cloud.firestore {\n  match /databases/{database}/documents {\n    match /{document=**} {\n      // This rule allows anyone with your database reference to view, edit,\n      // and delete all data in your database. It is useful for getting\n      // started, but it is configured to expire after 30 days because it\n      // leaves your app open to attackers. At that time, all client\n      // requests to your database will be denied.\n      //\n      // Make sure to write security rules for your app before that time, or\n      // else all client requests to your database will be denied until you\n      // update your rules.\n      allow read, write: if request.time < timestamp.date(<%= IN_30_DAYS %>);\n    }\n  }\n}\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/application/files/public/404.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <title>Page Not Found</title>\n\n    <style media=\"screen\">\n      body { background: #ECEFF1; color: rgba(0,0,0,0.87); font-family: Roboto, Helvetica, Arial, sans-serif; margin: 0; padding: 0; }\n      #message { background: white; max-width: 360px; margin: 100px auto 16px; padding: 32px 24px 16px; border-radius: 3px; }\n      #message h3 { color: #888; font-weight: normal; font-size: 16px; margin: 16px 0 12px; }\n      #message h2 { color: #ffa100; font-weight: bold; font-size: 16px; margin: 0 0 8px; }\n      #message h1 { font-size: 22px; font-weight: 300; color: rgba(0,0,0,0.6); margin: 0 0 16px;}\n      #message p { line-height: 140%; margin: 16px 0 24px; font-size: 14px; }\n      #message a { display: block; text-align: center; background: #039be5; text-transform: uppercase; text-decoration: none; color: white; padding: 16px; border-radius: 4px; }\n      #message, #message a { box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); }\n      #load { color: rgba(0,0,0,0.4); text-align: center; font-size: 13px; }\n      @media (max-width: 600px) {\n        body, #message { margin-top: 0; background: white; box-shadow: none; }\n        body { border-top: 16px solid #ffa100; }\n      }\n    </style>\n  </head>\n  <body>\n    <div id=\"message\">\n      <h2>404</h2>\n      <h1>Page Not Found</h1>\n      <p>The specified file was not found on this website. Please check the URL for mistakes and try again.</p>\n      <h3>Why am I seeing this?</h3>\n      <p>This page was generated by the Firebase Command-Line Interface. To modify it, edit the <code>404.html</code> file in your project's configured <code>public</code> directory.</p>\n    </div>\n  </body>\n</html>\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/application/files/public/index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <title>Welcome to Firebase Hosting</title>\n\n    <!-- update the version number as needed -->\n    <script defer src=\"/__/firebase/12.8.0/firebase-app-compat.js\"></script>\n    <!-- include only the Firebase features as you need -->\n    <script defer src=\"/__/firebase/12.8.0/firebase-auth-compat.js\"></script>\n    <script defer src=\"/__/firebase/12.8.0/firebase-database-compat.js\"></script>\n    <script defer src=\"/__/firebase/12.8.0/firebase-firestore-compat.js\"></script>\n    <script defer src=\"/__/firebase/12.8.0/firebase-functions-compat.js\"></script>\n    <script defer src=\"/__/firebase/12.8.0/firebase-messaging-compat.js\"></script>\n    <script defer src=\"/__/firebase/12.8.0/firebase-storage-compat.js\"></script>\n    <script defer src=\"/__/firebase/12.8.0/firebase-analytics-compat.js\"></script>\n    <script defer src=\"/__/firebase/12.8.0/firebase-remote-config-compat.js\"></script>\n    <script defer src=\"/__/firebase/12.8.0/firebase-performance-compat.js\"></script>\n    <!-- \n      initialize the SDK after all desired features are loaded, set useEmulator to false\n      to avoid connecting the SDK to running emulators.\n    -->\n    <script defer src=\"/__/firebase/init.js?useEmulator=true\"></script>\n\n    <style media=\"screen\">\n      body { background: #ECEFF1; color: rgba(0,0,0,0.87); font-family: Roboto, Helvetica, Arial, sans-serif; margin: 0; padding: 0; }\n      #message { background: white; max-width: 360px; margin: 100px auto 16px; padding: 32px 24px; border-radius: 3px; }\n      #message h2 { color: #ffa100; font-weight: bold; font-size: 16px; margin: 0 0 8px; }\n      #message h1 { font-size: 22px; font-weight: 300; color: rgba(0,0,0,0.6); margin: 0 0 16px;}\n      #message p { line-height: 140%; margin: 16px 0 24px; font-size: 14px; }\n      #message a { display: block; text-align: center; background: #039be5; text-transform: uppercase; text-decoration: none; color: white; padding: 16px; border-radius: 4px; }\n      #message, #message a { box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); }\n      #load { color: rgba(0,0,0,0.4); text-align: center; font-size: 13px; }\n      @media (max-width: 600px) {\n        body, #message { margin-top: 0; background: white; box-shadow: none; }\n        body { border-top: 16px solid #ffa100; }\n      }\n    </style>\n  </head>\n  <body>\n    <div id=\"message\">\n      <h2>Welcome</h2>\n      <h1>Firebase Hosting Setup Complete</h1>\n      <p>You're seeing this because you've successfully setup Firebase Hosting. Now it's time to go build something extraordinary!</p>\n      <a target=\"_blank\" href=\"https://firebase.google.com/docs/hosting/\">Open Hosting Documentation</a>\n    </div>\n    <p id=\"load\">Firebase SDK Loading&hellip;</p>\n\n    <script>\n      document.addEventListener('DOMContentLoaded', function() {\n        const loadEl = document.querySelector('#load');\n        // // 🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥\n        // // The Firebase SDK is initialized and available here!\n        //\n        // firebase.auth().onAuthStateChanged(user => { });\n        // firebase.database().ref('/path/to/ref').on('value', snapshot => { });\n        // firebase.firestore().doc('/foo/bar').get().then(() => { });\n        // firebase.functions().httpsCallable('yourFunction')().then(() => { });\n        // firebase.messaging().requestPermission().then(() => { });\n        // firebase.storage().ref('/path/to/ref').getDownloadURL().then(() => { });\n        // firebase.analytics(); // call to activate\n        // firebase.analytics().logEvent('tutorial_completed');\n        // firebase.performance(); // call to activate\n        //\n        // // 🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥\n\n        try {\n          let app = firebase.app();\n          let features = [\n            'auth', \n            'database', \n            'firestore',\n            'functions',\n            'messaging', \n            'storage', \n            'analytics', \n            'remoteConfig',\n            'performance',\n          ].filter(feature => typeof app[feature] === 'function');\n          loadEl.textContent = `Firebase SDK loaded with ${features.join(', ')}`;\n        } catch (e) {\n          console.error(e);\n          loadEl.textContent = 'Error loading the Firebase SDK, check the console.';\n        }\n      });\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/application/files/readme.md__tmpl__",
    "content": "# <%= projectName %>\n\nThis Nx Firebase application was generated by [@simondotm/nx-firebase](https://github.com/simondotm/nx-firebase).\n\n## Generated Application Files\n\n* `database.rules.json` - Default Firebase Realtime Database Rules\n* `firestore.indexes.json` - Default Firebase Firestore Database Rules\n* `storage.rules` - Default Firebase Storage Rules\n* `public/index.ts` - Default Firebase hosting site\n\n## Generated Workspace Root Files\n\n* `<%= firebaseAppConfig %>` - Firebase CLI Configuration for this project\n* `.firebaserc` - Default Firebase CLI Deployment Targets Configuration\n\n## Generated dependencies\n\nNx-Firebase will add `firebase-tools`, `firebase-admin` and `firebase-functions` to your workspace if they do not already exist.\n\n## Next Steps\n\n* Read about the [Firebase CLI here](https://firebase.google.com/docs/cli)\n* `firebase login` - Authenticate the Firebase CLI\n* `firebase use --add` - Add your Firebase Project as a target to `.firebaserc`\n* `nx g @simondotm/nx-firebase:function my-function --firebaseApp <%= projectName %>` - Add a firebase function to this project\n\nSee the plugin [README](https://github.com/simondotm/nx-firebase/blob/main/README.md) for more information.\n\n## Commands\n\n* `nx run <%= projectName %>:deploy` - Deploy this app to firebase\n* `nx run <%= projectName %>:serve` - Serve this app using the firebase emulator\n* `nx run <%= projectName %>:build` - Build all functions associated with this app\n* `nx run <%= projectName %>:test` - Test all functions associated with this app\n* `nx run <%= projectName %>:lint` - Lint all functions associated with this app\n\n\n\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/application/files/storage.rules",
    "content": "rules_version = '2';\n\n// Craft rules based on data in your Firestore database\n// allow write: if firestore.get(\n//    /databases/(default)/documents/users/$(request.auth.uid)).data.isAdmin;\nservice firebase.storage {\n  match /b/{bucket}/o {\n    match /{allPaths=**} {\n      allow read, write: if false;\n    }\n  }\n}\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/application/files_firebase/firebase.json__tmpl__",
    "content": "{\n  \"database\": {\n    \"rules\": \"<%= projectRoot %>/database.rules.json\"\n  },\n  \"firestore\": {\n    \"rules\": \"<%= projectRoot %>/firestore.rules\",\n    \"indexes\": \"<%= projectRoot %>/firestore.indexes.json\"\n  },    \n  \"hosting\": {\n    \"public\": \"<%= projectRoot %>/public\",\n    \"ignore\": [\n      \"firebase.json\",\n      \"**/.*\",\n      \"**/node_modules/**\"\n    ],\n    \"rewrites\": [\n      {\n        \"source\": \"**\",\n        \"destination\": \"/index.html\"\n      }\n    ]\n  },\n  \"storage\": {\n      \"rules\": \"<%= projectRoot %>/storage.rules\"\n  },\n  \"functions\": [],\n  \"emulators\": {\n    \"auth\": {\n      \"port\": 9099\n    },\n    \"functions\": {\n      \"port\": 5001\n    },\n    \"firestore\": {\n      \"port\": 8080\n    },\n    \"database\": {\n      \"port\": 9000\n    },\n    \"hosting\": {\n      \"port\": 5000\n    },\n    \"pubsub\": {\n      \"port\": 8085\n    },\n    \"storage\": {\n      \"port\": 9199\n    },\n    \"eventarc\": {\n      \"port\": 9299\n    },\n    \"ui\": {\n      \"enabled\": true\n    },\n    \"singleProjectMode\": true\n  }\n}\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/application/files_firebaserc/.firebaserc__tmpl__",
    "content": "{\n  \"targets\": {\n  },\n  \"projects\": {\n  }\n}"
  },
  {
    "path": "packages/nx-firebase/src/generators/application/files_workspace/firebase.__projectName__.json__tmpl__",
    "content": "{\n  \"database\": {\n    \"rules\": \"<%= projectRoot %>/database.rules.json\"\n  },\n  \"firestore\": {\n    \"rules\": \"<%= projectRoot %>/firestore.rules\",\n    \"indexes\": \"<%= projectRoot %>/firestore.indexes.json\"\n  },    \n  \"hosting\": {\n    \"public\": \"<%= projectRoot %>/public\",\n    \"ignore\": [\n      \"firebase.json\",\n      \"**/.*\",\n      \"**/node_modules/**\"\n    ],\n    \"rewrites\": [\n      {\n        \"source\": \"**\",\n        \"destination\": \"/index.html\"\n      }\n    ]\n  },\n  \"storage\": {\n      \"rules\": \"<%= projectRoot %>/storage.rules\"\n  },\n  \"functions\": [],\n  \"emulators\": {\n    \"auth\": {\n      \"port\": 9099\n    },\n    \"functions\": {\n      \"port\": 5001\n    },\n    \"firestore\": {\n      \"port\": 8080\n    },\n    \"database\": {\n      \"port\": 9000\n    },\n    \"hosting\": {\n      \"port\": 5000\n    },\n    \"pubsub\": {\n      \"port\": 8085\n    },\n    \"storage\": {\n      \"port\": 9199\n    },\n    \"eventarc\": {\n      \"port\": 9299\n    },\n    \"ui\": {\n      \"enabled\": true\n    },\n    \"singleProjectMode\": true\n  }\n}\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/application/lib/create-files.ts",
    "content": "import { generateFiles, joinPathFragments, Tree } from '@nx/devkit'\n\nimport type { NormalizedSchema } from '../schema'\n\n/**\n * Generate the firebase app specific files\n *\n * @param tree\n * @param options\n */\nexport function createFiles(tree: Tree, options: NormalizedSchema): void {\n  const firebaseAppConfig = options.firebaseConfigName\n\n  // Firebase SDK firestore.rules template has a placeholder for the date 30 days from now\n  // so we add that substitution here at the point of app generation\n  const date = new Date()\n  date.setDate(date.getDate() + 30)\n  const dateString = date\n    .toISOString()\n    .split('T')[0]\n    .split('-')\n    .map((v) => parseInt(v).toString())\n    .join(', ')\n\n  const substitutions = {\n    tmpl: '',\n    projectName: options.projectName,\n    projectRoot: options.projectRoot,\n    firebaseAppConfig,\n    IN_30_DAYS: dateString,\n  }\n\n  // The default functions package.json & templated typescript source files are added here\n  // to match the `firebase init` cli setup\n  // if the user isn't using functions, they can just delete and update their firebase config accordingly\n  //\n  // Rules and index files also get generated in the application folder;\n  // 1. so that they dont clutter up the root workspace\n  // 2. so that they are located within the nx firebase application project they relate to\n  generateFiles(\n    tree,\n    joinPathFragments(__dirname, '..', 'files'),\n    options.projectRoot,\n    substitutions,\n  )\n\n  // The first firebase app project in a workspace will always use `firebase.json` as its config file\n  // Subsequent firebase app projects will be assigned a config file based on the project name, so `firebase.<project-name>.json`\n  if (firebaseAppConfig === 'firebase.json') {\n    //  if (!tree.exists('firebase.json')) {\n    generateFiles(\n      tree,\n      joinPathFragments(__dirname, '..', 'files_firebase'),\n      '',\n      substitutions,\n    )\n  } else {\n    generateFiles(\n      tree,\n      joinPathFragments(__dirname, '..', 'files_workspace'),\n      '', // SM: this is a tree path, not a file system path\n      substitutions,\n    )\n  }\n\n  // For a fresh workspace, the firebase CLI needs at least a firebase.json and an empty .firebaserc\n  //  in order to use commands like 'firebase use --add'\n  if (!tree.exists('.firebaserc')) {\n    generateFiles(\n      tree,\n      joinPathFragments(__dirname, '..', 'files_firebaserc'),\n      '',\n      substitutions,\n    )\n  }\n  // else {\n  //   logger.log('✓ .firebaserc already exists in this workspace')\n  // }\n}\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/application/lib/index.ts",
    "content": "export * from './create-files'\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/application/schema.d.ts",
    "content": "export interface Schema {\n  // standard @nx project generator options\n  name: string\n  directory?: string\n  tags?: string\n  // extra options for @simondotm/nx-firebase:app generator\n  project?: string\n}\n\nexport interface NormalizedSchema extends Schema {\n  projectName: string\n  projectRoot: string\n  parsedTags: string[]\n  firebaseConfigName: string\n}\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/application/schema.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/schema\",\n  \"$id\": \"NxFirebaseApplicationGenerator\",\n  \"title\": \"Nx Firebase Application Options Schema\",\n  \"description\": \"Nx Firebase Application Options Schema.\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"name\": {\n      \"description\": \"The name of the firebase application.\",\n      \"type\": \"string\",\n      \"$default\": {\n        \"$source\": \"argv\",\n        \"index\": 0\n      },\n      \"x-prompt\": \"What name would you like to use for the firebase application?\"\n    },\n    \"directory\": {\n      \"description\": \"A directory where the application is placed.\",\n      \"type\": \"string\",\n      \"alias\": \"d\"\n    },\n    \"tags\": {\n      \"type\": \"string\",\n      \"description\": \"Add tags to the project (used for linting)\",\n      \"alias\": \"t\"\n    },\n    \"project\": {\n      \"type\": \"string\",\n      \"description\": \"The firebase CLI project that should be associated with this application\",\n      \"default\": \"\"\n    }\n  },\n  \"additionalProperties\": false,\n  \"required\": [\"name\"]\n}\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/function/files/package.json__tmpl__",
    "content": "{\n  \"name\": \"<%= projectName %>\",\n  \"description\": \"Firebase Function, auto generated by @simondotm/nx-firebase\",\n  \"scripts\": {\n    \"build\": \"nx build <%= projectName %>\",\n    \"deploy\": \"nx deploy <%= projectName %>\",\n    \"lint\": \"nx lint <%= projectName %>\",\n    \"test\": \"nx test <%= projectName %>\"\n  },\n  \"type\": \"<%= moduleType %>\",\n  \"engines\": {\n    \"node\": \"<%= firebaseNodeEngine %>\"\n  },\n  \"dependencies\": {\n  },\n  \"devDependencies\": {\n  },  \n  \"private\": true\n}\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/function/files/readme.md__tmpl__",
    "content": "# <%= projectName %>\n\nThis Nx Firebase function was generated by [@simondotm/nx-firebase](https://github.com/simondotm/nx-firebase).\n\n## Generated Application Files\n\n* `src/main.ts` - Default Firebase Functions entry point\n\n## Generated Workspace Root Files\n\n* `<%= firebaseAppConfig %>` - Firebase CLI Configuration for this project\n* `.firebaserc` - Default Firebase CLI Deployment Targets Configuration\n* `firebase.json` - Intentionally Empty Firebase CLI Configuration (only needed to allow Firebase CLI to run in your workspace)\n\n## Generated modules\n\nNx-Firebase will add `firebase-admin` and `firebase-functions` to your workspace `package.json` at the `'latest'` version. You may wish to set these to a specific version.\n\n## Next Steps\n\n* `npm install -g firebase-tools` - Install the [Firebase CLI](https://firebase.google.com/docs/cli)\n* `firebase login` - Authenticate the Firebase CLI\n* `firebase use --add` - Add your Firebase Project as a target to `.firebaserc`\n* You do not need to `npm install` in the app project directory, but can still add and run custom npm scripts to the app `package.json` if you wish\n\n\n\nSee the plugin [README](https://github.com/simondotm/nx-firebase/blob/main/README.md) for more information.\n\n\n\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/function/files/src/main.ts__tmpl__",
    "content": "/**\n * Import function triggers from their respective submodules:\n *\n * import {onCall} from \"firebase-functions/v2/https\";\n * import {onDocumentWritten} from \"firebase-functions/v2/firestore\";\n *\n * See a full list of supported triggers at https://firebase.google.com/docs/functions\n */\n\nimport {setGlobalOptions} from \"firebase-functions\";\nimport {onRequest} from \"firebase-functions/https\";\nimport * as logger from \"firebase-functions/logger\";\n\n// Start writing functions\n// https://firebase.google.com/docs/functions/typescript\n\n// For cost control, you can set the maximum number of containers that can be\n// running at the same time. This helps mitigate the impact of unexpected\n// traffic spikes by instead downgrading performance. This limit is a\n// per-function limit. You can override the limit for each function using the\n// `maxInstances` option in the function's options, e.g.\n// `onRequest({ maxInstances: 5 }, (req, res) => { ... })`.\n// NOTE: setGlobalOptions does not apply to functions using the v1 API. V1\n// functions should each use functions.runWith({ maxInstances: 10 }) instead.\n// In the v1 API, each function can only serve one request per container, so\n// this will be the maximum concurrent request count.\nsetGlobalOptions({ maxInstances: 10 });\n\n// export const helloWorld = onRequest((request, response) => {\n//   logger.info(\"Hello logs!\", {structuredData: true});\n//   response.send(\"Hello from Firebase!\");\n// });\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/function/function.spec.ts",
    "content": "import {\n  getProjects,\n  joinPathFragments,\n  readJson,\n  readProjectConfiguration,\n  Tree,\n  writeJson,\n} from '@nx/devkit'\nimport { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'\nimport { functionGenerator } from './function'\nimport applicationGenerator from '../application/application'\n\ndescribe('function generator', () => {\n  let tree: Tree\n\n  beforeEach(() => {\n    tree = createTreeWithEmptyWorkspace()\n    jest.clearAllMocks()\n  })\n\n  it('should generate workspace', () => {\n    expect(tree.exists(`firebase.json`)).toBeFalsy()\n    expect(tree.exists(`.firebaserc`)).toBeFalsy()\n    expect(tree.isFile(`package.json`)).toBeTruthy()\n  })\n\n  it('should require a valid firebase app project', async () => {\n    // generator should throw if there is no valid firebase app project in the workspace\n    await expect(\n      functionGenerator(tree, {\n        name: 'myFirebaseFunction',\n        app: 'myFirebaseApp',\n      }),\n    ).rejects.toThrow(\n      \"A firebase application project called 'myFirebaseApp' was not found in this workspace.\",\n    )\n  })\n\n  describe('function generator', () => {\n    beforeEach(async () => {\n      await applicationGenerator(tree, {\n        name: 'myFirebaseApp',\n      })\n    })\n\n    describe('application setup', () => {\n      it('should create a firebase config with no functions', () => {\n        const firebaseConfig = readJson(tree, 'firebase.json')\n        expect(firebaseConfig.functions.length).toEqual(0)\n      })\n    })\n\n    describe('function creation', () => {\n      it('should generate the correct Nx project config', async () => {\n        await functionGenerator(tree, {\n          name: 'myFirebaseFunction',\n          app: 'myFirebaseApp',\n        })\n        const project = readProjectConfiguration(tree, 'myFirebaseFunction')\n        expect(project.root).toEqual('myFirebaseFunction')\n        expect(project.targets).toEqual(\n          expect.objectContaining({\n            build: {\n              executor: '@nx/esbuild:esbuild',\n              outputs: ['{options.outputPath}'],\n              options: {\n                outputPath: 'dist/myFirebaseFunction',\n                main: 'myFirebaseFunction/src/main.ts',\n                tsConfig: 'myFirebaseFunction/tsconfig.app.json',\n                assets: [\n                  'myFirebaseFunction/src/assets',\n                  {\n                    glob: '**/*',\n                    input: 'myFirebaseApp/environment',\n                    output: '.',\n                  },\n                ],\n                generatePackageJson: true,\n                platform: 'node',\n                bundle: true,\n                thirdParty: false,\n                dependenciesFieldType: 'dependencies',\n                target: 'node20',\n                format: ['esm'],\n                esbuildOptions: {\n                  logLevel: 'info',\n                },\n              },\n            },\n            deploy: {\n              executor: 'nx:run-commands',\n              options: {\n                command: `nx run myFirebaseApp:deploy --only functions:myFirebaseFunction`,\n              },\n              dependsOn: ['build'],\n            },\n            test: {\n              executor: '@nx/jest:jest',\n              outputs: ['{workspaceRoot}/coverage/{projectRoot}'],\n              options: {\n                jestConfig: expect.stringMatching(\n                  /^myFirebaseFunction\\/jest\\.config\\.c?ts$/,\n                ),\n                passWithNoTests: true,\n              },\n            },\n          }),\n        )\n        expect(project.targets.serve).toBeUndefined()\n        expect(project.targets.lint).toEqual({\n          executor: '@nx/eslint:lint',\n        })\n      })\n\n      it('should update tags', async () => {\n        await functionGenerator(tree, {\n          name: 'myFirebaseFunction',\n          app: 'myFirebaseApp',\n          tags: 'one,two',\n        })\n        const projects = Object.fromEntries(getProjects(tree))\n        expect(projects).toMatchObject({\n          myFirebaseFunction: {\n            tags: [\n              'firebase:function',\n              'firebase:name:myFirebaseFunction',\n              'firebase:dep:myFirebaseApp',\n              'one',\n              'two',\n            ],\n          },\n        })\n      })\n\n      it('should generate files', async () => {\n        await functionGenerator(tree, {\n          name: 'myFirebaseFunction',\n          app: 'myFirebaseApp',\n        })\n        const root = 'myFirebaseFunction'\n        expect(tree.exists(`${root}/src/main.ts`)).toBeTruthy()\n        expect(tree.exists(`${root}/src/assets/.gitkeep`)).toBeTruthy()\n        expect(tree.exists(`${root}/package.json`)).toBeTruthy()\n        expect(tree.exists(`${root}/readme.md`)).toBeTruthy()\n        expect(tree.exists(`${root}/project.json`)).toBeTruthy()\n        expect(tree.exists(`${root}/tsconfig.app.json`)).toBeTruthy()\n        expect(tree.exists(`${root}/tsconfig.json`)).toBeTruthy()\n        expect(tree.exists(`${root}/tsconfig.spec.json`)).toBeTruthy()\n        // do not want, from node generator\n        expect(tree.exists(`${root}/webpack.config.js`)).toBeFalsy()\n        expect(tree.exists(`${root}/src/test-setup.ts`)).toBeFalsy()\n      })\n\n      it('should generate function in subdirectory', async () => {\n        await functionGenerator(tree, {\n          name: 'myFirebaseFunction',\n          app: 'myFirebaseApp',\n          directory: 'subDir',\n        })\n\n        const projects = Object.fromEntries(getProjects(tree))\n        expect(projects).toMatchObject({\n          myFirebaseFunction: {\n            tags: [\n              'firebase:function',\n              'firebase:name:myFirebaseFunction',\n              'firebase:dep:myFirebaseApp',\n            ],\n          },\n        })\n\n        const root = 'subDir'\n        expect(tree.exists(`${root}/src/main.ts`)).toBeTruthy()\n        expect(tree.exists(`${root}/src/assets/.gitkeep`)).toBeTruthy()\n        expect(tree.exists(`${root}/package.json`)).toBeTruthy()\n        expect(tree.exists(`${root}/readme.md`)).toBeTruthy()\n        expect(tree.exists(`${root}/project.json`)).toBeTruthy()\n        expect(tree.exists(`${root}/tsconfig.app.json`)).toBeTruthy()\n        expect(tree.exists(`${root}/tsconfig.json`)).toBeTruthy()\n        expect(tree.exists(`${root}/tsconfig.spec.json`)).toBeTruthy()\n        // do not want, from node generator\n        expect(tree.exists(`${root}/webpack.config.js`)).toBeFalsy()\n        expect(tree.exists(`${root}/src/test-setup.ts`)).toBeFalsy()\n      })\n    })\n\n    describe('firebase.json functions config', () => {\n      it('should add the function to the firebase config', async () => {\n        await functionGenerator(tree, {\n          name: 'myFirebaseFunction',\n          app: 'myFirebaseApp',\n        })\n        const firebaseConfig = readJson(tree, 'firebase.json')\n        expect(firebaseConfig.functions.length).toEqual(1)\n        expect(firebaseConfig.functions[0]).toEqual({\n          codebase: 'myFirebaseFunction',\n          source: 'dist/myFirebaseFunction',\n          runtime: `nodejs20`,\n          ignore: ['*.local'],\n        })\n      })\n\n      it('should handle functions being empty object', async () => {\n        const firebaseConfigInitial = readJson(tree, 'firebase.json')\n        firebaseConfigInitial.functions = {}\n        writeJson(tree, 'firebase.json', firebaseConfigInitial)\n\n        await functionGenerator(tree, {\n          name: 'myFirebaseFunction',\n          app: 'myFirebaseApp',\n        })\n\n        const firebaseConfig = readJson(tree, 'firebase.json')\n        expect(firebaseConfig.functions.length).toEqual(1)\n        expect(firebaseConfig.functions[0]).toEqual({\n          codebase: 'myFirebaseFunction',\n          source: 'dist/myFirebaseFunction',\n          runtime: `nodejs20`,\n          ignore: ['*.local'],\n        })\n      })\n\n      it('should handle functions being already present', async () => {\n        const firebaseConfigInitial = readJson(tree, 'firebase.json')\n        const testFunction = {\n          codebase: 'test',\n          source: 'dist/apps/test',\n          runtime: `nodejs20`,\n          ignore: ['*.local'],\n        }\n        firebaseConfigInitial.functions = [testFunction]\n        writeJson(tree, 'firebase.json', firebaseConfigInitial)\n\n        await functionGenerator(tree, {\n          name: 'myFirebaseFunction',\n          app: 'myFirebaseApp',\n        })\n\n        const firebaseConfig = readJson(tree, 'firebase.json')\n        expect(firebaseConfig.functions).toContainEqual(testFunction)\n        expect(firebaseConfig.functions).toContainEqual({\n          codebase: 'myFirebaseFunction',\n          source: 'dist/myFirebaseFunction',\n          runtime: `nodejs20`,\n          ignore: ['*.local'],\n        })\n      })\n    })\n\n    describe('function options', () => {\n      describe('function option --format', () => {\n        it('should generate function configured for --format=esm', async () => {\n          await functionGenerator(tree, {\n            name: 'myFirebaseFunction',\n            app: 'myFirebaseApp',\n            format: 'esm',\n          })\n          const project = readProjectConfiguration(tree, 'myFirebaseFunction')\n          expect(project.targets.build.options.format).toEqual(['esm'])\n\n          // check the package has the correct module type\n          const packageJson = readJson(\n            tree,\n            joinPathFragments(project.root, 'package.json'),\n          )\n          expect(packageJson.type).toEqual('module')\n\n          // check the tsconfig\n          const tsConfig = readJson(\n            tree,\n            joinPathFragments(project.root, 'tsconfig.app.json'),\n          )\n          expect(tsConfig.compilerOptions.module).toEqual('es2020')\n        })\n\n        it('should generate function configured for --format=commonjs output', async () => {\n          await functionGenerator(tree, {\n            name: 'myFirebaseFunction',\n            app: 'myFirebaseApp',\n            format: 'cjs',\n          })\n          const project = readProjectConfiguration(tree, 'myFirebaseFunction')\n          expect(project.targets.build.options.format).toEqual(['cjs'])\n\n          // check the package has the correct module type\n          const packageJson = readJson(\n            tree,\n            joinPathFragments(project.root, 'package.json'),\n          )\n          expect(packageJson.type).toEqual('commonjs')\n\n          // check the tsconfig\n          const tsConfig = readJson(\n            tree,\n            joinPathFragments(project.root, 'tsconfig.app.json'),\n          )\n          expect(tsConfig.compilerOptions.module).toEqual('commonjs')\n        })\n      })\n      describe('function option --runTime', () => {\n        it('should generate function with correct node runtime', async () => {\n          await functionGenerator(tree, {\n            name: 'myFirebaseFunction',\n            app: 'myFirebaseApp',\n            runTime: '22',\n          })\n          const project = readProjectConfiguration(tree, 'myFirebaseFunction')\n\n          // check the package has the correct module type\n          const packageJson = readJson(\n            tree,\n            joinPathFragments(project.root, 'package.json'),\n          )\n          expect(packageJson.engines.node).toEqual('22')\n\n          // check the function firebase config\n          const firebaseConfig = readJson(tree, 'firebase.json')\n          expect(firebaseConfig.functions[0].runtime).toEqual('nodejs22')\n        })\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/function/function.ts",
    "content": "import {\n  GeneratorCallback,\n  readProjectConfiguration,\n  Tree,\n  convertNxGenerator,\n  formatFiles,\n  runTasksInSerial,\n  getProjects,\n} from '@nx/devkit'\nimport { applicationGenerator as nodeApplicationGenerator } from '@nx/node'\n\nimport { initGenerator } from '../init/init'\nimport { getFirebaseConfigFromProject, updateTsConfig } from '../../utils'\n\nimport { addFunctionConfig, createFiles, updateProject } from './lib'\nimport type { Schema, NormalizedSchema } from './schema'\nimport { determineProjectNameAndRootOptions } from '@nx/devkit/src/generators/project-name-and-root-utils'\nimport { packageVersions } from '../../__generated__/nx-firebase-versions'\n\nexport async function normalizeOptions(\n  host: Tree,\n  options: Schema,\n): Promise<NormalizedSchema> {\n  // In Nx 20+, directory is required. If not provided, use name as directory.\n  const directory = options.directory ?? options.name\n\n  const { projectName: appProjectName, projectRoot } =\n    await determineProjectNameAndRootOptions(host, {\n      name: options.name,\n      projectType: 'application',\n      directory,\n    })\n\n  const parsedTags = options.tags\n    ? options.tags.split(',').map((s) => s.trim())\n    : []\n\n  // const { projectName, projectRoot } = getProjectName(\n  //   host,\n  //   options.name,\n  //   options.directory,\n  // )\n\n  // get & validate the firebase app project this function will be attached to\n  const projects = getProjects(host)\n  if (!projects.has(options.app)) {\n    throw new Error(\n      `A firebase application project called '${options.app}' was not found in this workspace.`,\n    )\n  }\n\n  const firebaseAppProject = readProjectConfiguration(host, options.app)\n\n  // read the firebase config used by the parent app project\n  const firebaseConfigName = getFirebaseConfigFromProject(\n    host,\n    firebaseAppProject,\n  )\n\n  return {\n    ...options,\n    projectName: appProjectName,\n    projectRoot,\n    parsedTags,\n    firebaseConfigName,\n    firebaseAppProject,\n  }\n\n  // return {\n  //   ...options,\n  //   runTime: options.runTime || '16',\n  //   format: options.format || 'esm',\n  //   projectRoot,\n  //   projectName,\n  //   firebaseConfigName,\n  //   firebaseAppProject,\n  // }\n}\n\n/**\n * Firebase 'functions' application generator\n * Uses the `@nx/node` application generator as a base implementation\n *\n * @param host\n * @param schema\n * @returns\n */\nexport async function functionGenerator(\n  host: Tree,\n  schema: Schema,\n): Promise<GeneratorCallback> {\n  const tasks: GeneratorCallback[] = []\n\n  const options = await normalizeOptions(host, {\n    // set default options\n    runTime: packageVersions.nodeEngine as typeof schema.runTime, // we can be sure that our firebaseNodeEngine value satisfies the type\n    // apply overrides from user\n    ...schema,\n  })\n\n  if (!options.runTime) {\n    throw new Error('No runtime specified for the function app')\n  }\n\n  // const options = normalizeOptions(host, schema)\n\n  // initialise plugin\n  const initTask = await initGenerator(host, {})\n  tasks.push(initTask)\n\n  // We use @nx/node:app to scaffold our function application, then modify as required\n  // `nx g @nx/node:app function-name --directory functions/dir --e2eTestRunner=none --framework=none --unitTestRunner=jest --bundler=esbuild --tags=firebase:firebase-app`\n\n  // Function apps are tagged so that they can built/watched with run-many\n  const tags =\n    `firebase:function,firebase:name:${options.projectName},firebase:dep:${options.firebaseAppProject.name}` +\n    (options.tags ? `,${options.tags}` : '')\n\n  const nodeApplicationTask = await nodeApplicationGenerator(host, {\n    name: options.name,\n    directory: options.projectRoot,\n    tags,\n    setParserOptionsProject: options.setParserOptionsProject,\n    skipFormat: options.skipFormat,\n    e2eTestRunner: 'none',\n    bundler: 'esbuild',\n    framework: 'none',\n    unitTestRunner: 'jest',\n  })\n  tasks.push(nodeApplicationTask)\n\n  // generate function app specific files\n  createFiles(host, options)\n\n  // update TS config for esm or cjs\n  updateTsConfig(host, options.projectRoot, options.runTime, options.format)\n\n  // reconfigure the @nx/node:app to suit firebase functions\n  updateProject(host, options)\n\n  // update firebase functions config\n  addFunctionConfig(host, options)\n\n  // ensures newly added files are formatted to match workspace style\n  if (!options.skipFormat) {\n    await formatFiles(host)\n  }\n\n  return runTasksInSerial(...tasks)\n}\n\nexport default functionGenerator\nexport const functionSchematic = convertNxGenerator(functionGenerator)\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/function/lib/add-function-config.ts",
    "content": "import { Tree, joinPathFragments, updateJson } from '@nx/devkit'\nimport type { FirebaseConfig, FirebaseFunction } from '../../../types'\nimport type { NormalizedSchema } from '../schema'\n\nexport function addFunctionConfig(host: Tree, options: NormalizedSchema) {\n  updateJson(host, options.firebaseConfigName, (json: FirebaseConfig) => {\n    const functionConfig = {\n      codebase: options.projectName,\n      source: joinPathFragments('dist', options.projectRoot),\n      runtime: `nodejs${options.runTime}`,\n      ignore: ['*.local'],\n    }\n\n    // console.log(`json.functions=${JSON.stringify(json.functions)}`)\n    if (!Array.isArray(json.functions)) {\n      const existingFunction = Object.assign({}, json.functions)\n      json.functions = []\n      if (Object.keys(existingFunction).length) {\n        json.functions.push(existingFunction)\n      }\n    }\n    json.functions.push(functionConfig)\n    // sort the codebases to be neat & tidy\n    json.functions.sort((a: FirebaseFunction, b: FirebaseFunction) => {\n      return a.codebase < b.codebase ? -1 : a.codebase > b.codebase ? 1 : 0\n    })\n    return json\n  })\n}\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/function/lib/create-files.ts",
    "content": "import {\n  detectPackageManager,\n  offsetFromRoot,\n  Tree,\n  updateJson,\n  generateFiles,\n  joinPathFragments,\n} from '@nx/devkit'\nimport type { NormalizedSchema } from '../schema'\nimport { packageVersions } from '../../../__generated__/nx-firebase-versions'\n\n/**\n * Generate the firebase app specific files\n *\n * @param host\n * @param options\n */\nexport function createFiles(host: Tree, options: NormalizedSchema): void {\n  const firebaseAppConfig = options.firebaseConfigName\n  const firebaseAppConfigPath = joinPathFragments(\n    offsetFromRoot(options.projectRoot),\n    firebaseAppConfig,\n  )\n\n  const substitutions = {\n    tmpl: '',\n    projectName: options.projectName,\n    projectRoot: options.projectRoot,\n\n    firebaseAppName: options.app,\n    firebaseAppConfig,\n    firebaseAppConfigPath,\n\n    firebaseNodeEngine: options.runTime,\n\n    // firebaseNodeRuntime,\n    // firebaseNodeEngine,\n\n    moduleType: options.format === 'esm' ? 'module' : 'commonjs',\n  }\n\n  // The default functions package.json & templated typescript source files are added here\n  // to match the `firebase init` cli setup\n  // if the user isn't using functions, they can just delete and update their firebase config accordingly\n  //\n  // Rules and index files also get generated in the application folder;\n  // 1. so that they dont clutter up the root workspace\n  // 2. so that they are located within the nx firebase application project they relate to\n  generateFiles(\n    host,\n    joinPathFragments(__dirname, '..', 'files'),\n    options.projectRoot,\n    substitutions,\n  )\n\n  // set dependencies for the firebase function\n  const firebasePackageDependencies = {}\n  if (detectPackageManager() === 'pnpm') {\n    firebasePackageDependencies[\n      '@google-cloud/functions-framework'\n    ] = `^${packageVersions.googleCloudFunctionsFramework}`\n  }\n\n  if (Object.keys(firebasePackageDependencies).length > 0) {\n    updateJson(\n      host,\n      joinPathFragments(options.projectRoot, 'package.json'),\n      (json) => {\n        json.dependencies = firebasePackageDependencies\n        return json\n      },\n    )\n  }\n}\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/function/lib/delete-files.ts",
    "content": "import { type Tree, joinPathFragments } from '@nx/devkit'\nimport type { NormalizedSchema } from '../schema'\n\n/**\n * Delete unwanted files created by the node generator\n * @param host\n * @param options\n */\nexport function deleteFiles(host: Tree, options: NormalizedSchema): void {\n  const nodeFilesToDelete = [\n    joinPathFragments(options.projectRoot, 'src', 'main.ts'),\n    joinPathFragments(options.projectRoot, 'src', 'app', '.gitkeep'),\n    joinPathFragments(options.projectRoot, 'src', 'assets', '.gitkeep'),\n    joinPathFragments(\n      options.projectRoot,\n      'src',\n      'environments',\n      'environment.prod.ts',\n    ),\n    joinPathFragments(\n      options.projectRoot,\n      'src',\n      'environments',\n      'environment.ts',\n    ),\n  ]\n\n  for (const path of nodeFilesToDelete) {\n    host.delete(path)\n  }\n}\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/function/lib/index.ts",
    "content": "export * from './add-function-config'\nexport * from './create-files'\nexport * from './update-project'\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/function/lib/update-project.ts",
    "content": "import {\n  Tree,\n  readProjectConfiguration,\n  updateProjectConfiguration,\n} from '@nx/devkit'\nimport type { NormalizedSchema } from '../schema'\nimport type { FunctionAssetsEntry, FunctionAssetsGlob } from '../../../types'\n\nexport function updateProject(host: Tree, options: NormalizedSchema): void {\n  const project = readProjectConfiguration(host, options.projectName)\n\n  const firebaseAppProject = options.firebaseAppProject\n\n  // replace the default node build target with a simplified version\n  // we dont need dev/production build configurations for firebase functions since its a confined secure environment\n  project.targets.build = {\n    executor: '@nx/esbuild:esbuild',\n    outputs: ['{options.outputPath}'],\n    options: {\n      outputPath: project.targets.build.options.outputPath,\n      main: project.targets.build.options.main,\n      tsConfig: project.targets.build.options.tsConfig,\n      assets: project.targets.build.options.assets,\n      generatePackageJson: true,\n      // these are the defaults for esbuild, but let's set them anyway\n      platform: 'node',\n      bundle: true,\n      thirdParty: false,\n      dependenciesFieldType: 'dependencies',\n      target: `node${options.runTime}`,\n      format: [options.format || 'esm'], // default for esbuild is esm\n      esbuildOptions: {\n        logLevel: 'info',\n      },\n    },\n  }\n\n  // add reference to firebase app environment assets\n  const firebaseAppRoot = firebaseAppProject.root\n  const assets: FunctionAssetsEntry[] = project.targets.build.options.assets\n  const glob: FunctionAssetsGlob = {\n    glob: '**/*',\n    input: `${firebaseAppRoot}/environment`,\n    output: '.',\n  }\n  assets.push(glob)\n\n  // add deploy target\n  project.targets.deploy = {\n    executor: 'nx:run-commands',\n    options: {\n      // command: `firebase deploy${firebaseProject} --config=${firebaseConfig}`,\n      // use the firebase app to deploy, this way the function does not need to know the project or config\n      command: `nx run ${firebaseAppProject.name}:deploy --only functions:${options.projectName}`,\n    },\n    dependsOn: ['build'],\n  }\n\n  // Remove default node app serve target\n  // No serve target for functions, since we may have multiple functions in a firebase project\n  // Instead we serve at the firebase app project\n  delete project.targets.serve\n\n  // Create explicit lint target to ensure consistent behavior regardless of\n  // workspace inference settings or @nx/eslint/plugin configuration\n  project.targets.lint = {\n    executor: '@nx/eslint:lint',\n  }\n\n  // Create explicit test target with passWithNoTests so functions without tests don't fail\n  // when running the firebase app's test target. passWithNoTests is a CLI option,\n  // not a jest.config option, so we need to set it in the target.\n  // In Nx 18+, the test target may not exist (inferred by plugin), so we create it.\n  // Nx 22+ creates jest.config.cts for ESM projects, so we check which file exists\n  const jestConfigFile = host.exists(`${options.projectRoot}/jest.config.cts`)\n    ? 'jest.config.cts'\n    : 'jest.config.ts'\n  project.targets.test = {\n    executor: '@nx/jest:jest',\n    outputs: ['{workspaceRoot}/coverage/{projectRoot}'],\n    options: {\n      jestConfig: `${options.projectRoot}/${jestConfigFile}`,\n      passWithNoTests: true,\n    },\n  }\n\n  updateProjectConfiguration(host, options.projectName, project)\n\n  // Add function project as implicit dep of firebase app project\n  firebaseAppProject.implicitDependencies ||= []\n  firebaseAppProject.implicitDependencies.push(options.projectName)\n  firebaseAppProject.implicitDependencies.sort()\n  updateProjectConfiguration(host, options.app, firebaseAppProject)\n}\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/function/schema.d.ts",
    "content": "import { ProjectConfiguration } from '@nx/devkit'\n\nexport interface Schema {\n  // standard nx generator options\n  name: string\n  directory?: string\n  tags?: string\n\n  // subset of @nx/node:application options that we forward to node app generator\n  setParserOptionsProject?: boolean\n  skipFormat?: boolean\n  // unitTestRunner is always jest\n  // bundler is always esbuild\n  // linter is always eslint\n\n  // nx-firebase:function generator specific options\n  app: string\n  runTime?: '20' | '22' | '24'\n  format?: 'esm' | 'cjs'\n}\n\ninterface NormalizedSchema extends Schema {\n  projectName: string\n  projectRoot: string\n  parsedTags: string[]\n\n  firebaseConfigName: string\n  firebaseAppProject: ProjectConfiguration\n}\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/function/schema.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/schema\",\n  \"$id\": \"NxFirebaseFunctionGenerator\",\n  \"title\": \"Nx Firebase Function Options Schema\",\n  \"description\": \"Nx Firebase Function Options Schema.\",\n  \"cli\": \"nx\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"name\": {\n      \"description\": \"The name of the firebase function project.\",\n      \"type\": \"string\",\n      \"$default\": {\n        \"$source\": \"argv\",\n        \"index\": 0\n      },\n      \"x-prompt\": \"What name would you like to use for the firebase function project?\"\n    },\n    \"directory\": {\n      \"description\": \"A directory where the function is placed.\",\n      \"type\": \"string\",\n      \"alias\": \"d\"\n    },\n    \"tags\": {\n      \"type\": \"string\",\n      \"description\": \"Add tags to the project (used for linting)\",\n      \"alias\": \"t\"\n    },\n    \"setParserOptionsProject\": {\n      \"type\": \"boolean\",\n      \"description\": \"Whether or not to configure the ESLint `parserOptions.project` option. We do not do this by default for lint performance reasons.\",\n      \"default\": false\n    },\n    \"skipFormat\": {\n      \"description\": \"Skip formatting files.\",\n      \"type\": \"boolean\",\n      \"default\": false\n    },\n    \"format\": {\n      \"description\": \"The module format for this function (esm or cjs).\",\n      \"type\": \"string\",\n      \"enum\": [\"esm\", \"cjs\"],\n      \"default\": \"esm\"\n    },\n    \"app\": {\n      \"description\": \"The name of the parent Nx firebase application project this function will be added to.\",\n      \"type\": \"string\",\n      \"x-prompt\": \"Which firebase Nx application project will this function be created for?\"\n    },\n    \"runTime\": {\n      \"description\": \"The node runtime target for this function.\",\n      \"type\": \"string\",\n      \"enum\": [\"20\", \"22\", \"24\"],\n      \"default\": \"20\"\n    }\n  },\n  \"additionalProperties\": false,\n  \"required\": [\"name\", \"app\"]\n}\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/init/init.spec.ts",
    "content": "import type { Tree } from '@nx/devkit'\nimport * as devkit from '@nx/devkit'\nimport { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'\nimport { packageVersions } from '../../__generated__/nx-firebase-versions'\nimport { initGenerator } from './init'\nimport { gitIgnoreRules, nxIgnoreRules } from './lib'\nimport { workspaceNxVersion } from '../../utils'\n\ndescribe('init generator', () => {\n  let tree: Tree\n\n  beforeEach(() => {\n    tree = createTreeWithEmptyWorkspace({\n      layout: 'apps-libs',\n    })\n    jest.clearAllMocks()\n  })\n\n  it('should add .gitignore rules', async () => {\n    await initGenerator(tree, {})\n    const gitIgnore = tree.read('.gitignore')\n    expect(gitIgnore.toString('utf-8')).toContain(\n      `${gitIgnoreRules.join('\\n')}\\n`,\n    )\n  })\n\n  it('should add missing .gitignore rules', async () => {\n    // replace .gitignore with a partial list to check plugin adds missing rules individually\n    tree.write('.gitignore', `${gitIgnoreRules.slice(0, 3).join('\\n')}\\n`)\n    await initGenerator(tree, {})\n    const gitIgnore = tree.read('.gitignore')\n    expect(gitIgnore.toString('utf-8')).toContain(\n      `${gitIgnoreRules.join('\\n')}\\n`,\n    )\n  })\n\n  it('should add .nxignore rules', async () => {\n    await initGenerator(tree, {})\n    const nxIgnore = tree.read('.nxignore')\n    expect(nxIgnore.toString('utf-8')).toContain(\n      `${nxIgnoreRules.join('\\n')}\\n`,\n    )\n  })\n\n  it('should add missing .nxignore rules', async () => {\n    // replace .nxignore with a partial list to check plugin adds missing rules individually\n    tree.write('.nxignore', `${nxIgnoreRules.slice(0, 1).join('\\n')}\\n`)\n    await initGenerator(tree, {})\n    const nxIgnore = tree.read('.nxignore')\n    expect(nxIgnore.toString('utf-8')).toContain(\n      `${nxIgnoreRules.join('\\n')}\\n`,\n    )\n  })\n\n  it('should not have dependencies', () => {\n    const packageJson = devkit.readJson(tree, 'package.json')\n    expect(packageJson.dependencies['firebase']).toBeUndefined()\n    expect(packageJson.dependencies['firebase-admin']).toBeUndefined()\n    expect(packageJson.dependencies['firebase-functions']).toBeUndefined()\n    expect(packageJson.dependencies['tslib']).toBeUndefined()\n\n    expect(\n      packageJson.devDependencies['firebase-functions-test'],\n    ).toBeUndefined()\n    expect(packageJson.devDependencies['firebase-tools']).toBeUndefined()\n    expect(packageJson.devDependencies['kill-port']).toBeUndefined()\n  })\n\n  it('should add dependencies', async () => {\n    await initGenerator(tree, {})\n\n    const packageJson = devkit.readJson(tree, 'package.json')\n    expect(packageJson.dependencies['firebase']).toBe(\n      `^${packageVersions.firebase}`,\n    )\n    expect(packageJson.dependencies['firebase-admin']).toBe(\n      `^${packageVersions.firebaseAdmin}`,\n    )\n    expect(packageJson.dependencies['firebase-functions']).toBe(\n      `^${packageVersions.firebaseFunctions}`,\n    )\n\n    expect(packageJson.devDependencies['firebase-functions-test']).toBe(\n      `^${packageVersions.firebaseFunctionsTest}`,\n    )\n    expect(packageJson.devDependencies['firebase-tools']).toBe(\n      `^${packageVersions.firebaseTools}`,\n    )\n    expect(packageJson.devDependencies['kill-port']).toBe(\n      `^${packageVersions.killPort}`,\n    )\n\n    // check that plugin init generator adds @google-cloud/functions-framework if pnpm is being used\n    if (devkit.detectPackageManager() === 'pnpm') {\n      expect(\n        packageJson.dependencies['@google-cloud/functions-framework'],\n      ).toBe(`^${packageVersions.googleCloudFunctionsFramework}`)\n    } else {\n      expect(\n        packageJson.dependencies['@google-cloud/functions-framework'],\n      ).not.toBeDefined()\n    }\n\n    expect(packageJson.dependencies['tslib']).not.toBeDefined()\n  })\n\n  it('should only add dependencies if not already present', async () => {\n    const packageJsonDefault = devkit.readJson(tree, 'package.json')\n\n    const testVersion = '0.0.1'\n    packageJsonDefault.dependencies['firebase'] = testVersion\n    packageJsonDefault.dependencies['firebase-admin'] = testVersion\n    packageJsonDefault.dependencies['firebase-functions'] = testVersion\n    // packageJsonDefault.dependencies['tslib'] = testVersion\n\n    packageJsonDefault.devDependencies['firebase-tools'] = testVersion\n    packageJsonDefault.devDependencies['kill-port'] = testVersion\n    packageJsonDefault.devDependencies['firebase-functions-test'] = testVersion\n\n    devkit.writeJson(tree, 'package.json', packageJsonDefault)\n\n    await initGenerator(tree, {})\n\n    const packageJson = devkit.readJson(tree, 'package.json')\n\n    expect(packageJson.dependencies['firebase']).toBe(testVersion)\n    expect(packageJson.dependencies['firebase-admin']).toBe(testVersion)\n    expect(packageJson.dependencies['firebase-functions']).toBe(testVersion)\n    expect(packageJson.devDependencies['firebase-functions-test']).toBe(\n      testVersion,\n    )\n    expect(packageJson.devDependencies['firebase-tools']).toBe(testVersion)\n    expect(packageJson.devDependencies['kill-port']).toBe(testVersion)\n\n    const nxVersion = workspaceNxVersion.version\n    expect(packageJson.devDependencies['@nx/node']).toBe(nxVersion)\n  })\n\n  // describe('--skipFormat', () => {\n  //   it('should format files by default', async () => {\n  //     jest.spyOn(devkit, 'formatFiles')\n\n  //     await initGenerator(tree, {})\n\n  //     expect(devkit.formatFiles).toHaveBeenCalled()\n  //   })\n\n  //   it('should not format files when --skipFormat=true', async () => {\n  //     jest.spyOn(devkit, 'formatFiles')\n\n  //     await initGenerator(tree, { skipFormat: true })\n\n  //     expect(devkit.formatFiles).not.toHaveBeenCalled()\n  //   })\n  // })\n})\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/init/init.ts",
    "content": "import {\n  runTasksInSerial,\n  type GeneratorCallback,\n  type Tree,\n  formatFiles,\n} from '@nx/devkit'\nimport { addDependencies } from './lib'\nimport { addGitIgnore, addNxIgnore } from './lib/add-git-ignore-entry'\nimport type { InitGeneratorOptions } from './schema'\n\n/**\n * `nx g @simondotm/nx-firebase:init`\n *\n * Ensures the necessary firebase packages are installed in the nx workspace\n * It also adds `@nx/node` if it is not already installed\n *\n */\nexport async function initGenerator(\n  host: Tree,\n  schema: InitGeneratorOptions,\n): Promise<GeneratorCallback> {\n  const tasks: GeneratorCallback[] = []\n  addGitIgnore(host)\n  addNxIgnore(host)\n  if (!schema.skipFormat) {\n    await formatFiles(host)\n  }\n  tasks.push(addDependencies(host))\n  return runTasksInSerial(...tasks)\n}\n\nexport default initGenerator\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/init/lib/add-dependencies.ts",
    "content": "import {\n  GeneratorCallback,\n  readJson,\n  Tree,\n  addDependenciesToPackageJson,\n  detectPackageManager,\n  logger,\n} from '@nx/devkit'\nimport { workspaceNxVersion } from '../../../utils'\nimport { packageVersions } from '../../../__generated__/nx-firebase-versions'\n\nexport function addDependencies(tree: Tree): GeneratorCallback {\n  const dependencies: Record<string, string> = {}\n  const devDependencies: Record<string, string> = {}\n\n  // SM: only add firebase related dependencies if they do not already exist\n  // This is atypical for Nx plugins that usually migrate versions automatically\n  //  however the nx-firebase plugin is not (currently) opinionated about which version is needed,\n  //  so this ensures workspaces retain control over their firebase versions.\n  const packageJson = readJson(tree, 'package.json')\n\n  function addDependencyIfNotPresent(\n    packageName: string,\n    packageVersion: string,\n  ): boolean {\n    if (!packageJson.dependencies || !packageJson.dependencies[packageName]) {\n      dependencies[packageName] = packageVersion\n      return true\n    }\n    return false\n  }\n  function addDevDependencyIfNotPresent(\n    packageName: string,\n    packageVersion: string,\n  ) {\n    if (\n      !packageJson.devDependencies ||\n      !packageJson.devDependencies[packageName]\n    ) {\n      devDependencies[packageName] = packageVersion\n      return true\n    }\n    return false\n  }\n\n  // Firebase packages are not managed by the plugin\n  // but they are added if they do not exist already in the workspace\n  addDependencyIfNotPresent('firebase', `^${packageVersions.firebase}`)\n  addDependencyIfNotPresent(\n    'firebase-admin',\n    `^${packageVersions.firebaseAdmin}`,\n  )\n  addDependencyIfNotPresent(\n    'firebase-functions',\n    `^${packageVersions.firebaseFunctions}`,\n  )\n\n  // if the workspace uses pnpm, we need to add the @google-cloud/functions-framework package\n  if (detectPackageManager() === 'pnpm') {\n    if (\n      addDependencyIfNotPresent(\n        '@google-cloud/functions-framework',\n        `^${packageVersions.googleCloudFunctionsFramework}`,\n      )\n    ) {\n      logger.info(\n        `This workspace is using pnpm, adding '@google-cloud/functions-framework' dependency for firebase functions compatibility\\nSee https://github.com/firebase/firebase-tools/issues/5911#issuecomment-1730263400\\n\\n`,\n      )\n    }\n  }\n\n  // firebase dev dependencies\n  addDevDependencyIfNotPresent(\n    'firebase-tools',\n    `^${packageVersions.firebaseTools}`,\n  )\n  addDevDependencyIfNotPresent(\n    'firebase-functions-test',\n    `^${packageVersions.firebaseFunctionsTest}`,\n  )\n\n  // kill-port is used by the emulate target to ensure clean emulator startup\n  // since Nx doesn't kill processes cleanly atm\n  addDevDependencyIfNotPresent('kill-port', `^${packageVersions.killPort}`)\n\n  // @nx/node is used by the plugin function generator as a proxy for creating a typescript app\n  // since users have to create a firebase app before they generate a function, we can be sure\n  // this plugin init will have been run before the function generator that requires @nx/node is used\n  // we defer to @nx/node to install its own plugins such as @nx/eslint, @nx/jest, @nx/js, @nx/esbuild, @nx/webpack etc.\n  addDevDependencyIfNotPresent('@nx/node', workspaceNxVersion.version)\n  return addDependenciesToPackageJson(tree, dependencies, devDependencies)\n}\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/init/lib/add-git-ignore-entry.ts",
    "content": "import { Tree } from '@nx/devkit'\n\nexport const gitIgnoreRules = [\n  '# Nx-Firebase',\n  '.runtimeconfig.json',\n  '**/.emulators/*',\n  '**/.firebase/*',\n  'database-debug.log',\n  'firebase-debug.log',\n  'firestore-debug.log',\n  'pubsub-debug.log',\n  'ui-debug.log',\n  'firebase-export*',\n  '.secret.local',\n]\n\n// these rules tell nx to override .gitignore and consider these files as dependencies\nexport const nxIgnoreRules = [\n  '# Nx-Firebase',\n  '!.secret.local',\n  '!.runtimeconfig.json',\n]\n\nfunction addIgnoreRules(host: Tree, ignoreRules: string[], ignoreFile: string) {\n  if (!host.exists(ignoreFile)) {\n    host.write(ignoreFile, `${ignoreRules.join('\\n')}\\n`)\n    return\n  }\n\n  let content = host.read(ignoreFile)?.toString('utf-8')\n  let updated = false\n  for (const entry of ignoreRules) {\n    if (!content.includes(entry)) {\n      content += `${entry}\\n`\n      updated = true\n    }\n  }\n  if (updated) {\n    host.write(ignoreFile, content)\n  }\n}\n\nexport function addGitIgnore(host: Tree) {\n  addIgnoreRules(host, gitIgnoreRules, '.gitignore')\n}\n\nexport function addNxIgnore(host: Tree) {\n  addIgnoreRules(host, nxIgnoreRules, '.nxignore')\n}\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/init/lib/index.ts",
    "content": "export * from './add-dependencies'\nexport * from './add-git-ignore-entry'\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/init/schema.d.ts",
    "content": "export type InitGeneratorOptions = {\n  skipFormat?: boolean\n}\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/init/schema.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/schema\",\n  \"$id\": \"NxFirebaseInitGenerator\",\n  \"title\": \"Init Firebase Plugin\",\n  \"description\": \"Init Firebase Plugin.\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"skipFormat\": {\n      \"description\": \"Skip formatting files.\",\n      \"type\": \"boolean\",\n      \"default\": false\n    }\n  },\n  \"required\": []\n}\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/migrate/migrate.spec.ts",
    "content": "import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'\nimport { Tree } from '@nx/devkit'\n\nimport generator from './migrate'\n// import { MigrateGeneratorSchema } from './schema'\n\n// migrate is tested in e2e.\n\ndescribe('migrate generator', () => {\n  let tree: Tree\n  // const options: MigrateGeneratorSchema = {}\n\n  beforeEach(() => {\n    tree = createTreeWithEmptyWorkspace()\n  })\n\n  it('should run successfully', async () => {\n    await generator(tree, {})\n    // const config = readProjectConfiguration(tree, 'test')\n    // expect(config).toBeDefined()\n  })\n})\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/migrate/migrate.ts",
    "content": "import {\n  GeneratorCallback,\n  logger,\n  runTasksInSerial,\n  Tree,\n  joinPathFragments,\n  updateProjectConfiguration,\n  writeJson,\n} from '@nx/devkit'\nimport { MigrateGeneratorSchema } from './schema'\nimport initGenerator from '../init/init'\nimport {\n  debugInfo,\n  getFirebaseScopeFromTag,\n  getFirebaseWorkspace,\n} from '../sync/lib'\nimport { readFileSync } from 'fs'\nimport { FunctionAssetsEntry, FirebaseFunction } from '../../types'\n\n/**\n * Migrate firebase workspace\n *\n */\nexport default async function migrateGenerator(\n  tree: Tree,\n  // eslint-disable-next-line\n  options: MigrateGeneratorSchema,\n): Promise<GeneratorCallback> {\n  const tasks: GeneratorCallback[] = []\n\n  // initialise plugin\n  const initTask = await initGenerator(tree, {})\n  tasks.push(initTask)\n\n  // otherwise, sync the workspace.\n  // build lists of firebase apps & functions that have been deleted or renamed\n  debugInfo('- Migrating workspace')\n\n  const workspace = getFirebaseWorkspace(tree)\n\n  logger.info(\n    `This workspace has ${workspace.firebaseAppProjects.size} firebase apps and ${workspace.firebaseFunctionProjects.size} firebase functions\\n\\n`,\n  )\n\n  logger.info(`Running plugin migrations for workspace`)\n\n  // ensure ignores in .gitignore\n  // ensure ignores in .nxignore\n  // init generator takes care of this\n\n  // [2.0.0 -> 2.1.0] ensure environment files are present in apps dir\n  workspace.firebaseAppProjects.forEach((project, name) => {\n    const envPath = `${project.root}/environment`\n    const envFiles = [`.env`, `.env.local`, `.secret.local`]\n    for (const envFile of envFiles) {\n      const fullPath = `${envPath}/${envFile}`\n      if (!tree.exists(fullPath)) {\n        const src = readFileSync(\n          joinPathFragments(\n            __dirname,\n            '..',\n            'application',\n            'files',\n            'environment',\n            envFile,\n          ),\n        )\n        tree.write(joinPathFragments(project.root, 'environment', envFile), src)\n        logger.info(\n          `MIGRATE Added default environment file 'environment/${envFile}' for firebase app '${name}'`,\n        )\n      }\n    }\n  })\n\n  // [2.0.0 -> 2.1.0] ensure getconfig path is environment\n  workspace.firebaseAppProjects.forEach((project, name) => {\n    const getconfig = project.targets['getconfig']\n    const command = getconfig?.options.command as string\n    const legacyPath = joinPathFragments(project.root, '.runtimeconfig.json')\n    if (command.includes(legacyPath)) {\n      getconfig.options.command = command.replace(\n        legacyPath,\n        joinPathFragments(project.root, 'environment', '.runtimeconfig.json'),\n      )\n      logger.info(\n        `MIGRATE Updated getconfig target to use ignore environment directory for firebase app '${name}'`,\n      )\n\n      updateProjectConfiguration(tree, project.name, project)\n    }\n  })\n\n  // [2.0.0 -> 2.1.0] ensure globs in function projects\n  workspace.firebaseFunctionProjects.forEach((project, name) => {\n    const appName = getFirebaseScopeFromTag(project, 'firebase:dep')\n    const appProject = workspace.firebaseAppProjects.get(appName.tagValue)\n    const assets = project.targets['build']?.options\n      .assets as FunctionAssetsEntry[]\n    const globs = {\n      glob: '**/*',\n      input: `${appProject.root}/environment`, // TODO: must be path of parent firebase app\n      output: '.',\n    }\n\n    let hasAssetsGlob = false\n    assets.forEach((asset) => {\n      if (typeof asset === 'object') {\n        if (\n          asset.glob === globs.glob &&\n          asset.input === globs.input &&\n          asset.output === globs.output\n        ) {\n          hasAssetsGlob = true\n        }\n      }\n    })\n    if (!hasAssetsGlob) {\n      assets.push(globs)\n      logger.info(\n        `MIGRATE Added assets glob for firebase function app '${name}'`,\n      )\n      updateProjectConfiguration(tree, project.name, project)\n    }\n  })\n\n  // [2.0.0 -> 2.1.0] ensure ignores to function firebase.json\n  workspace.firebaseConfigs.forEach((config, configFilename) => {\n    if (!Array.isArray(config.functions)) {\n      // promote to array if single function object\n      config.functions = [config.functions as FirebaseFunction]\n    }\n    config.functions.map((func: FirebaseFunction) => {\n      const ignoreRule = '*.local'\n      const ignore = func.ignore || []\n      if (!ignore.includes(ignoreRule)) {\n        logger.info(\n          `MIGRATE Added ignore rule to firebase config '${configFilename}' for firebase function codebase '${func.codebase}'`,\n        )\n        ignore.push(ignoreRule)\n      }\n      func.ignore = ignore\n    })\n    writeJson(tree, configFilename, config)\n  })\n\n  // [2.0.0 -> 2.1.0] change firebase serve target\n  workspace.firebaseAppProjects.forEach((project, name) => {\n    const serve = project.targets['serve']\n    const serveExecutor = '@simondotm/nx-firebase:serve'\n    if (serve.executor !== serveExecutor) {\n      serve.executor = serveExecutor\n      logger.info(`MIGRATE Updated serve target for firebase app '${name}'`)\n\n      updateProjectConfiguration(tree, project.name, project)\n    }\n  })\n\n  return runTasksInSerial(...tasks)\n}\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/migrate/schema.d.ts",
    "content": "// eslint-disable-next-line @typescript-eslint/no-empty-interface\nexport interface MigrateGeneratorSchema {}\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/migrate/schema.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/schema\",\n  \"$id\": \"Migrate\",\n  \"title\": \"\",\n  \"type\": \"object\",\n  \"properties\": {},\n  \"required\": []\n}\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/sync/lib/firebase-changes.ts",
    "content": "import { ProjectConfiguration, Tree } from '@nx/devkit'\n\nimport {\n  FirebaseProjects,\n  FirebaseChanges,\n  FirebaseConfigs,\n  FirebaseFunction,\n} from '../../../types'\n\nimport { debugInfo, mapKeys } from '../../../utils'\nimport { getFirebaseScopeFromTag } from './tags'\n\n/**\n * Check if the given firebase project has been renamed by checking its `firebase:name` tag\n * @param project - Firebase project to be checked\n * @returns the previous project name if renamed, otherwise undefined/falsy\n */\nfunction isRenamed(project: ProjectConfiguration): string | undefined {\n  const { tagValue } = getFirebaseScopeFromTag(project, 'firebase:name')\n  debugInfo(\n    `- checking if project ${project.name} is renamed, will be different to ${tagValue}`,\n  )\n  if (tagValue !== project.name) {\n    debugInfo(\n      `- firebase project ${tagValue} has been renamed to ${project.name} `,\n    )\n    return tagValue\n  }\n  return undefined\n}\n\nexport function getFirebaseChanges(\n  tree: Tree,\n  projects: FirebaseProjects,\n  configs: FirebaseConfigs,\n): FirebaseChanges {\n  // map of project name -> deletion status\n  const deletedApps = new Map<string, boolean>()\n  const deletedFunctions = new Map<string, boolean>()\n  // map of previous name -> new name\n  const renamedApps = new Map<string, ProjectConfiguration>()\n  const renamedFunctions = new Map<string, ProjectConfiguration>()\n\n  // 1. determine renamed apps using the firebase:name tag\n  function checkRenamedProject(\n    project: ProjectConfiguration,\n    renamedCollection: Map<string, ProjectConfiguration>,\n  ) {\n    const renamedProject = isRenamed(project)\n    if (renamedProject) {\n      debugInfo(`- ${renamedProject} has been renamed to ${project.name} `)\n      renamedCollection.set(renamedProject, project)\n    }\n  }\n  debugInfo(`- checking for renamed apps & functions`)\n  projects.firebaseAppProjects.forEach((project) =>\n    checkRenamedProject(project, renamedApps),\n  )\n  projects.firebaseFunctionProjects.forEach((project) =>\n    checkRenamedProject(project, renamedFunctions),\n  )\n\n  // 2. determine deleted functions\n  // do this either by detecting a dependency\n  debugInfo(`- checking apps for deleted function deps`)\n  projects.firebaseAppProjects.forEach(\n    (firebaseAppProject, firebaseAppProjectName) => {\n      // check the implicitDependencies for each app first\n      firebaseAppProject.implicitDependencies?.map((dep) => {\n        if (\n          !projects.firebaseFunctionProjects.has(dep) &&\n          !renamedFunctions.has(dep)\n        ) {\n          debugInfo(\n            `- function ${dep} is a dep, but cannot be located so function is deleted`,\n          )\n          deletedFunctions.set(dep, true)\n        }\n      })\n\n      // functions may also be removed using nx g rm <function> --forceRemove\n      // which removes the implicitDep from the app, but doesnt update the firebase config\n      // so we need to check the firebase config too, and determine any projects that are in there\n      // but not in the workspace and mark them as deleted\n\n      const firebaseConfigName = configs.firebaseAppConfigs.get(\n        firebaseAppProjectName,\n      )\n\n      debugInfo(\n        `- checking config ${firebaseConfigName} to see if it contains a function that doesnt exist`,\n      )\n      const config = configs.firebaseConfigs.get(firebaseConfigName)\n      if (!config) {\n        throw new Error(`Could not get firebase config '${firebaseConfigName}'`)\n      }\n\n      // ensure config functions is always an array, even if only 1 function\n      // just in case user has modified this.\n      if (!Array.isArray(config.functions)) {\n        config.functions = [config.functions]\n      }\n\n      // remove deleted functions\n      config.functions.forEach((func: FirebaseFunction) => {\n        const funcName = func.codebase\n        if (\n          !projects.firebaseFunctionProjects.has(funcName) &&\n          !renamedFunctions.has(funcName)\n        ) {\n          debugInfo(\n            `- function ${funcName} is in config ${firebaseConfigName}, but cannot be located so function must be deleted`,\n          )\n          deletedFunctions.set(funcName, true)\n        }\n      })\n    },\n  )\n\n  // nx g mv --project=source --destination=dstname\n  // DOES update implicitDeps\n  // Again, this means implicitDep wont be wrong, but we cant use it to detect renames\n  // This is ok though because we detect renamed functions via tags\n\n  // determine deleted apps\n  debugInfo(`- checking functions for deleted apps`)\n  projects.firebaseFunctionProjects.forEach(\n    (firebaseFunctionProject, firebaseFunctionName) => {\n      const { tagValue } = getFirebaseScopeFromTag(\n        firebaseFunctionProject,\n        'firebase:dep',\n      )\n      if (\n        !projects.firebaseAppProjects.has(tagValue) &&\n        !renamedApps.has(tagValue)\n      ) {\n        debugInfo(\n          `- function ${firebaseFunctionName} points to app ${tagValue} which doesnt exist, so app must be deleted`,\n        )\n        deletedApps.set(tagValue, true)\n      }\n    },\n  )\n\n  debugInfo(`- deletedApps=${mapKeys(deletedApps)}`)\n  debugInfo(`- deletedFunctions=${mapKeys(deletedFunctions)}`)\n  debugInfo(`- renamedApps=${mapKeys(renamedApps)}`)\n  debugInfo(`- renamedFunctions=${mapKeys(renamedFunctions)}`)\n\n  return {\n    deletedApps,\n    deletedFunctions,\n    renamedApps,\n    renamedFunctions,\n  }\n}\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/sync/lib/firebase-configs.ts",
    "content": "import { Tree, readJson } from '@nx/devkit'\n\nimport {\n  getFirebaseConfigFromCommand,\n  getFirebaseConfigFromProject,\n  debugInfo,\n  mapEntries,\n  mapKeys,\n} from '../../../utils'\nimport {\n  CONFIG_NO_APP,\n  FirebaseConfigs,\n  FirebaseProjects,\n  FirebaseConfig,\n} from '../../../types'\n\nconst FIREBASE_CONFIG_FILE_MATCHER = /(firebase)(\\S*)(.json)/\n\nexport function getFirebaseConfigs(\n  tree: Tree,\n  projects: FirebaseProjects,\n): FirebaseConfigs {\n  const firebaseConfigs = new Map<string, FirebaseConfig>()\n  const firebaseAppConfigs = new Map<string, string>()\n  const firebaseConfigProjects = new Map<string, string>()\n\n  debugInfo(`- firebaseAppProjects=${mapKeys(projects.firebaseAppProjects)}`)\n  debugInfo(\n    `- firebaseFunctionProjects=${mapKeys(projects.firebaseFunctionProjects)}`,\n  )\n\n  // collect all firebase[.*].json config files in workspace\n  const rootFiles = tree.children('')\n  rootFiles.map((child) => {\n    if (tree.isFile(child)) {\n      if (child.match(FIREBASE_CONFIG_FILE_MATCHER)) {\n        firebaseConfigs.set(child, readJson<FirebaseConfig>(tree, child))\n        // set an firebaseConfigProjects as null for now, later we will add the project\n        firebaseConfigProjects.set(child, CONFIG_NO_APP)\n      }\n    }\n  })\n  debugInfo(`- firebaseConfigs=${mapKeys(firebaseConfigs)}`)\n\n  // map firebase configs to their apps\n  // we do this by reading the --config setting directly from each firebase app's firebase target\n  // this is more robust & flexible than using filename assumptions for configs\n  // it also means users can freely rename their firebase configs and the plugin will work\n  projects.firebaseAppProjects.forEach((project, name) => {\n    const firebaseTarget = project.targets.firebase\n    if (!firebaseTarget) {\n      throw new Error(\n        `Firebase app project ${name} does not have a 'firebase' target. Sync will no longer work.`,\n      )\n    }\n    const firebaseConfigName = getFirebaseConfigFromProject(tree, project)\n    firebaseAppConfigs.set(name, firebaseConfigName)\n    firebaseConfigProjects.set(firebaseConfigName, name)\n\n    // bit opinionated, but we need to sanity check that\n    // production configurations on this target use the same config file.\n    // since we cant rename configs safely with this scenario\n    // different build configs should only differ in --project\n    const configurations = firebaseTarget.configurations\n    for (const configuration in configurations) {\n      const additionalConfigName = getFirebaseConfigFromCommand(\n        tree,\n        project,\n        configurations[configuration].command,\n      )\n      if (additionalConfigName !== firebaseConfigName) {\n        throw new Error(\n          `Firebase app project ${name} target firebase.configurations.${configuration} has a different --config setting which is unsupported by this plugin.`,\n        )\n      }\n    }\n  })\n\n  debugInfo(`- firebaseAppConfigs=${mapEntries(firebaseAppConfigs)}`)\n  debugInfo(\n    `- firebaseConfigProjects=${mapEntries(firebaseConfigProjects)},\n    )}`,\n  )\n\n  return {\n    firebaseConfigs,\n    firebaseAppConfigs,\n    firebaseConfigProjects,\n  }\n}\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/sync/lib/firebase-projects.ts",
    "content": "import {\n  ProjectConfiguration,\n  TargetConfiguration,\n  Tree,\n  getProjects,\n  logger,\n} from '@nx/devkit'\n\nimport { SyncGeneratorSchema } from '../schema'\nimport { debugInfo } from '../../../utils/debug'\nimport { FirebaseProjects } from '../../../types'\n\nconst FIREBASE_PROJECT_MATCHER = /(--project[ =])([^\\s]+)/\nconst FIREBASE_COMMAND_MATCHER = /(firebase)/\n\nexport function isFirebaseApp(project: ProjectConfiguration) {\n  return project.tags?.includes('firebase:app')\n}\n\nexport function isFirebaseFunction(project: ProjectConfiguration) {\n  return project.tags?.includes('firebase:function')\n}\n\n/**\n * Scan the workspace, identifying firebase projects managed by this plugin by their tags:\n * - firebase:app\n * - firebase:function\n * @param tree - host workspace\n * @returns FirebaseProjects object\n */\nexport function getFirebaseProjects(tree: Tree): FirebaseProjects {\n  const projects = getProjects(tree)\n  const firebaseAppProjects = new Map<string, ProjectConfiguration>()\n  const firebaseFunctionProjects = new Map<string, ProjectConfiguration>()\n\n  debugInfo('- building list of firebase apps & functions')\n  projects.forEach((project, projectName) => {\n    if (isFirebaseApp(project)) {\n      firebaseAppProjects.set(projectName, project)\n    }\n    if (isFirebaseFunction(project)) {\n      firebaseFunctionProjects.set(projectName, project)\n    }\n  })\n\n  return {\n    firebaseAppProjects,\n    firebaseFunctionProjects,\n  }\n}\n\n/**\n * Rewrite the firebase deploy command for the given function deploy target\n *  to either add or update the --project cli parameter\n * @param target - deploy target in the function\n * @param options -\n */\nexport function updateFirebaseAppDeployProject(\n  target: TargetConfiguration,\n  options: SyncGeneratorSchema,\n) {\n  const command: string = target.options.command\n  if (command.includes('firebase')) {\n    debugInfo('- found firebase target command')\n    debugInfo(`- command=${command}`)\n    if (command.includes('--project')) {\n      debugInfo('- found --project in firebase target command')\n      // already set, so update\n      target.options.command = command.replace(\n        FIREBASE_PROJECT_MATCHER,\n        '$1' + options.project,\n      )\n      debugInfo(`- new command: ${target.options.command}`)\n\n      logger.info(\n        `CHANGE updating firebase target --project for '${options.app}' to '--project=${options.project}'`,\n      )\n      return true\n    } else {\n      debugInfo('- did not find --project in deploy command')\n\n      // no set, so add\n      target.options.command = command.replace(\n        FIREBASE_COMMAND_MATCHER,\n        '$1' + ' ' + `--project=${options.project}`,\n      )\n      debugInfo(`- new command: ${target.options.command}`)\n      logger.info(\n        `CHANGE setting firebase target --project for '${options.app}' to '--project=${options.project}'`,\n      )\n      return true\n    }\n  }\n  return false\n}\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/sync/lib/firebase-workspace.ts",
    "content": "import { Tree } from '@nx/devkit'\nimport { getFirebaseChanges } from './firebase-changes'\nimport { getFirebaseProjects } from './firebase-projects'\n\nimport { getFirebaseConfigs } from './firebase-configs'\nimport { FirebaseWorkspace } from '../../../types'\n\nexport function getFirebaseWorkspace(tree: Tree): FirebaseWorkspace {\n  // build list of firebase apps and functions in the workspace\n  const projects = getFirebaseProjects(tree)\n  const configs = getFirebaseConfigs(tree, projects)\n  const changes = getFirebaseChanges(tree, projects, configs)\n\n  return {\n    ...projects,\n    ...changes,\n    ...configs,\n  }\n}\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/sync/lib/index.ts",
    "content": "export * from '../../../utils/debug'\nexport * from './firebase-changes'\nexport * from './firebase-configs'\nexport * from './firebase-projects'\nexport * from './firebase-workspace'\nexport * from './tags'\nexport * from './update-targets'\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/sync/lib/tags.ts",
    "content": "import {\n  ProjectConfiguration,\n  Tree,\n  updateProjectConfiguration,\n} from '@nx/devkit'\nimport { debugInfo } from '../../../utils/debug'\n\n/**\n * Parse a firebase tag value for the given scope\n * Expected to find tag of format: `firebase:name:<projectname>` or `firebase:dep:<appname>`\n * @param project - Project to be checked\n * @param scope - `firebase:name` or `firebase:dep`\n * @returns value part of tag\n * @throws if requested tag is missing, or malformed\n */\nexport function getFirebaseScopeFromTag(\n  project: ProjectConfiguration,\n  scope: 'firebase:name' | 'firebase:dep',\n) {\n  const tags = project.tags\n  if (tags) {\n    for (let i = 0; i < tags.length; ++i) {\n      const tag = tags[i]\n      debugInfo(`- checking tag '${tag}' for scope '${scope}'`)\n      if (tag.includes(scope)) {\n        debugInfo(`- matched tag '${tag}' for scope '${scope}'`)\n        const scopes = tag.split(':')\n        if (scopes.length === 3) {\n          debugInfo(`- returning tagValue '${scopes[2]}' tagIndex '${i}'`)\n          return { tagValue: scopes[2], tagIndex: i }\n        } else {\n          throw new Error(\n            `Malformed '${scope}' tag in project '${project.name}', expected '${scope}:<value>', found '${tag}'`,\n          )\n        }\n      }\n    }\n  }\n  throw new Error(\n    `Project '${project.name}' has a missing '${scope}' tag in project. Ensure this is set correctly.`,\n  )\n}\n\nexport function updateFirebaseProjectNameTag(\n  tree: Tree,\n  project: ProjectConfiguration,\n) {\n  const appNameTag = getFirebaseScopeFromTag(project, 'firebase:name')\n  const newAppNameTag = `firebase:name:${project.name}`\n  project.tags[appNameTag.tagIndex] = newAppNameTag\n  updateProjectConfiguration(tree, project.name, project)\n}\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/sync/lib/update-targets.ts",
    "content": "import {\n  ProjectConfiguration,\n  TargetConfiguration,\n  Tree,\n  updateProjectConfiguration,\n} from '@nx/devkit'\n\ntype NxRunCommandsTargetConfiguration = TargetConfiguration<{\n  command?: string\n  commands?: string[]\n}>\n\ntype ValidTarget =\n  | 'firebase'\n  | 'watch'\n  | 'emulate'\n  | 'lint'\n  | 'test'\n  | 'serve'\n  | 'deploy'\n  | 'getconfig'\n  | 'killports'\n\nexport function renameCommandForTarget(\n  tree: Tree,\n  project: ProjectConfiguration,\n  targetName: ValidTarget,\n  search: string,\n  replace: string,\n) {\n  const target = project.targets[targetName] as NxRunCommandsTargetConfiguration\n  if (!target) {\n    throw new Error(\n      `Could not find target '${targetName}' in project '${project.name}'`,\n    )\n  }\n  if (target.options.commands) {\n    target.options.commands = target.options.commands.map((cmd) => {\n      return cmd.replace(search, replace)\n    })\n  }\n  if (target.options.command) {\n    target.options.command = target.options.command.replace(search, replace)\n  }\n  updateProjectConfiguration(tree, project.name, project)\n}\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/sync/schema.d.ts",
    "content": "export interface SyncGeneratorSchema {\n  app?: string\n  project?: string\n  // functions?: boolean\n}\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/sync/schema.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/schema\",\n  \"$id\": \"Sync\",\n  \"title\": \"\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"app\": {\n      \"type\": \"string\",\n      \"description\": \"The Nx firebase application you would like to sync\",\n      \"default\": \"\"\n    },\n    \"project\": {\n      \"type\": \"string\",\n      \"description\": \"The firebase project that should be associated with this application\",\n      \"default\": \"\"\n    }\n  },\n  \"required\": []\n}\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/sync/sync.spec.ts",
    "content": "import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'\nimport { Tree } from '@nx/devkit'\n\nimport generator from './sync'\n// import { SyncGeneratorSchema } from './schema'\n\n// sync is tested in e2e.\n\ndescribe('sync generator', () => {\n  let tree: Tree\n\n  // const options: SyncGeneratorSchema = { app: 'test' }\n\n  beforeEach(() => {\n    tree = createTreeWithEmptyWorkspace()\n  })\n\n  // check we handle:\n  // sync per project & sync all projects\n  // - function renamed\n  // - app renamed\n  // - function deleted\n  // - app deleted\n  // - function & app renamed\n  // --project option\n\n  it('should run successfully', async () => {\n    await generator(tree, {})\n    // const config = readProjectConfiguration(tree, 'test')\n    // expect(config).toBeDefined()\n  })\n})\n"
  },
  {
    "path": "packages/nx-firebase/src/generators/sync/sync.ts",
    "content": "import {\n  GeneratorCallback,\n  getProjects,\n  joinPathFragments,\n  logger,\n  readProjectConfiguration,\n  runTasksInSerial,\n  Tree,\n  updateProjectConfiguration,\n  writeJson,\n} from '@nx/devkit'\n\nimport { SyncGeneratorSchema } from './schema'\nimport { setFirebaseConfigFromCommand } from '../../utils'\nimport initGenerator from '../init/init'\n\nimport {\n  debugInfo,\n  getFirebaseScopeFromTag,\n  isFirebaseApp,\n  updateFirebaseAppDeployProject,\n  updateFirebaseProjectNameTag,\n  getFirebaseWorkspace,\n  renameCommandForTarget,\n} from './lib'\nimport { CONFIG_NO_APP, FirebaseFunction } from '../../types'\n\nconst FUNCTIONS_DEPLOY_MATCHER = /(--only[ =]functions:)([^\\s]+)/\n\n/**\n * Sync firebase workspace\n *\n */\nexport async function syncGenerator(\n  tree: Tree,\n  options: SyncGeneratorSchema,\n): Promise<GeneratorCallback> {\n  const tasks: GeneratorCallback[] = []\n\n  // initialise plugin\n  const initTask = await initGenerator(tree, {})\n  tasks.push(initTask)\n\n  // otherwise, sync the workspace.\n  // build lists of firebase apps & functions that have been deleted or renamed\n  debugInfo('- Syncing workspace')\n\n  const workspace = getFirebaseWorkspace(tree)\n\n  logger.info(\n    `This workspace has ${workspace.firebaseAppProjects.size} firebase apps and ${workspace.firebaseFunctionProjects.size} firebase functions\\n\\n`,\n  )\n\n  // change the firebase project for an nx firebase app project\n  if (options.project) {\n    // --project option requires --app option to be specified\n    if (!options.app) {\n      throw new Error('--app not specified for --project option')\n    }\n    // validate app parameter\n    const project = readProjectConfiguration(tree, options.app)\n    if (!isFirebaseApp(project)) {\n      throw new Error(`Project '${options.app}' is not a Firebase application.`)\n    }\n    debugInfo(`- changing project for ${options.app} to ${options.project}`)\n    if (updateFirebaseAppDeployProject(project.targets.firebase, options)) {\n      updateProjectConfiguration(tree, options.app, project)\n    }\n    return\n  }\n\n  /**\n   * Nx automatically:\n   * - updates implicitDependencies when projects are renamed with `nx g mv`\n   * - deletes implicitDependencies when projects are deleted with `nx g rm`\n   * So we do not have to consider these scenarios.\n   */\n\n  // 1. remove any orphaned firebase config files that no longer linked to an app\n  workspace.firebaseConfigProjects.forEach((projectName, configName) => {\n    if (projectName === CONFIG_NO_APP) {\n      debugInfo(`- found firebase config ${configName} with no app`)\n      tree.delete(configName)\n      // its ok to delete keys in forEach\n      workspace.firebaseConfigProjects.delete(configName)\n      workspace.firebaseConfigs.delete(configName)\n      // dont need to sync firebaseAppConfigs since this app wont exist in there\n      logger.info(\n        `CHANGE Firebase config '${configName}' is no longer referenced by any firebase app, deleted`,\n      )\n    }\n  })\n\n  // 2. rename firebase config files too if app is renamed\n  workspace.renamedApps.forEach((project, oldProjectName) => {\n    const configFileName = workspace.firebaseAppConfigs.get(project.name)\n    // only rename config file if it is not firebase.json\n    if (configFileName !== 'firebase.json') {\n      const config = workspace.firebaseConfigs.get(configFileName)\n\n      // create a copy of the firebase config with the renamed project name\n      const newConfigFileName = `firebase.${project.name}.json`\n      writeJson(tree, newConfigFileName, config)\n\n      // rewrite the --config=<configFileName> part of the firebase target command\n      setFirebaseConfigFromCommand(project, newConfigFileName)\n\n      // write the updated project\n      updateProjectConfiguration(tree, project.name, project)\n\n      // delete the original config from the workspace\n      tree.delete(configFileName)\n\n      // rewrite the workspace to the newly renamed config file\n      workspace.firebaseConfigs.delete(configFileName)\n      workspace.firebaseAppConfigs.delete(project.name)\n      workspace.firebaseConfigProjects.delete(configFileName)\n\n      workspace.firebaseConfigs.set(newConfigFileName, config)\n      workspace.firebaseAppConfigs.set(project.name, newConfigFileName)\n      workspace.firebaseConfigProjects.set(newConfigFileName, project.name)\n\n      logger.info(\n        `CHANGE Firebase app '${oldProjectName}' was renamed to '${project.name}', renamed config file to '${newConfigFileName}'`,\n      )\n    }\n  })\n\n  // 3. update the firebase:name tag for renamed apps\n  workspace.renamedApps.forEach((project, oldName) => {\n    updateFirebaseProjectNameTag(tree, project)\n    logger.info(\n      `CHANGE Firebase app '${oldName}' was renamed to '${project.name}', updated firebase:name tag`,\n    )\n\n    // we also need to update nx:run-commands in the renamed projects for various targets\n\n    // test target\n    renameCommandForTarget(\n      tree,\n      project,\n      'test',\n      `tag:firebase:dep:${oldName}`,\n      `tag:firebase:dep:${project.name}`,\n    )\n    // lint target\n    renameCommandForTarget(\n      tree,\n      project,\n      'lint',\n      `tag:firebase:dep:${oldName}`,\n      `tag:firebase:dep:${project.name}`,\n    )\n    // watch target\n    renameCommandForTarget(\n      tree,\n      project,\n      'watch',\n      `tag:firebase:dep:${oldName}`,\n      `tag:firebase:dep:${project.name}`,\n    )\n    // serve target\n    renameCommandForTarget(\n      tree,\n      project,\n      'serve',\n      `${oldName}:`,\n      `${project.name}:`,\n    )\n    // deploy target\n    renameCommandForTarget(\n      tree,\n      project,\n      'deploy',\n      `${oldName}:`,\n      `${project.name}:`,\n    )\n    // getconfig target\n    renameCommandForTarget(\n      tree,\n      project,\n      'getconfig',\n      `${oldName}:`,\n      `${project.name}:`,\n    )\n    renameCommandForTarget(\n      tree,\n      project,\n      'getconfig',\n      `/${oldName}/`,\n      `/${project.name}/`,\n    )\n    // killports target\n    renameCommandForTarget(\n      tree,\n      project,\n      'killports',\n      `${oldName}:`,\n      `${project.name}:`,\n    )\n    // emulate target\n    renameCommandForTarget(\n      tree,\n      project,\n      'emulate',\n      `/${oldName}/`,\n      `/${project.name}/`,\n    )\n    renameCommandForTarget(\n      tree,\n      project,\n      'emulate',\n      `${oldName}:`,\n      `${project.name}:`,\n    )\n    logger.info(\n      `CHANGE Firebase app '${oldName}' was renamed to '${project.name}', updated targets`,\n    )\n  })\n\n  // 4. update the firebase:dep tag for functions linked to renamed apps\n  workspace.firebaseFunctionProjects.forEach((project, name) => {\n    const { tagValue, tagIndex } = getFirebaseScopeFromTag(\n      project,\n      'firebase:dep',\n    )\n    if (workspace.renamedApps.has(tagValue)) {\n      // update the firebase:dep tag\n      const renamedApp = workspace.renamedApps.get(tagValue)\n      project.tags[tagIndex] = `firebase:dep:${renamedApp.name}`\n      logger.info(\n        `CHANGE Firebase app '${tagValue}' was renamed to '${renamedApp.name}', updated firebase:dep tag in firebase function '${name}'`,\n      )\n      // update the environment assets glob also\n      const functionAssets = project.targets.build.options.assets\n      for (const asset of functionAssets) {\n        if (typeof asset === 'object') {\n          if (asset.input) {\n            asset.input = joinPathFragments(renamedApp.root, 'environment')\n            logger.info(\n              `CHANGE Firebase app '${tagValue}' was renamed to '${renamedApp.name}', updated environment assets path in firebase function '${name}'`,\n            )\n          }\n        }\n      }\n      // update the deploy command in functions where app has been renamed\n      const deployCommand = project.targets.deploy.options.command\n      project.targets.deploy.options.command = deployCommand.replace(\n        tagValue,\n        renamedApp.name,\n      )\n      logger.info(\n        `CHANGE Firebase app '${tagValue}' was renamed to '${renamedApp.name}', updated firebase deploy command in firebase function '${name}'`,\n      )\n\n      // update the function project config\n      updateProjectConfiguration(tree, project.name, project)\n    } else {\n      if (\n        workspace.deletedApps.has(tagValue) ||\n        !workspace.firebaseAppProjects.has(tagValue)\n      ) {\n        logger.warn(\n          `CHANGE Firebase app '${tagValue}' was deleted, firebase:dep tag for firebase function '${name}' is no longer linked to a Firebase app.`,\n        )\n      }\n    }\n  })\n\n  // 5. update the firebase:name tag for renamed functions\n  workspace.renamedFunctions.forEach((project, oldName) => {\n    updateFirebaseProjectNameTag(tree, project)\n    logger.info(\n      `CHANGE Firebase function '${oldName}' was renamed to '${project.name}', updated firebase:name tag`,\n    )\n  })\n\n  // 6. update the deploy command for renamed functions\n  workspace.renamedFunctions.forEach((project, oldName) => {\n    const deployCommand = project.targets.deploy.options.command\n    project.targets.deploy.options.command = deployCommand.replace(\n      FUNCTIONS_DEPLOY_MATCHER,\n      '$1' + project.name,\n    )\n    logger.info(\n      `CHANGE Firebase function '${oldName}' was renamed to '${project.name}', updated deploy target to '--only=functions:${project.name}'`,\n    )\n    updateProjectConfiguration(tree, project.name, project)\n  })\n\n  // 7. sync firebase configs for deleted & renamed functions\n  workspace.firebaseConfigs.forEach((config, configFileName) => {\n    let configUpdated = false\n\n    const functions = config.functions as FirebaseFunction[]\n    const updatedFunctions: FirebaseFunction[] = []\n    // remove deleted functions\n    functions.forEach((func) => {\n      // remove functions where codebase is linked to a now deleted firebase function project\n      if (workspace.deletedFunctions.has(func.codebase)) {\n        logger.info(\n          `CHANGE Firebase function '${func.codebase}' was deleted, removing function codebase from '${configFileName}'`,\n        )\n        configUpdated = true\n      } else {\n        // rename function codebase if linked to a renamed firebase function project\n        const codebase = func.codebase\n        debugInfo(`- checking if codebase '${codebase}' is renamed`)\n        if (workspace.renamedFunctions.has(codebase)) {\n          // change name of codebase\n          const project = workspace.renamedFunctions.get(codebase)\n\n          debugInfo(`- codebase '${codebase}' is renamed to ${project.name}`)\n\n          func.codebase = project.name\n          // change source dir\n          func.source = project.targets.build.options.outputPath\n          logger.info(\n            `CHANGE Firebase function '${codebase}' was renamed to '${project.name}', updated codebase in '${configFileName}'`,\n          )\n          configUpdated = true\n        }\n        updatedFunctions.push(func)\n      }\n    })\n    if (configUpdated) {\n      config.functions = updatedFunctions\n      config.functions.sort((a: FirebaseFunction, b: FirebaseFunction) => {\n        return a.codebase < b.codebase ? -1 : a.codebase > b.codebase ? 1 : 0\n      })\n      writeJson(tree, configFileName, config)\n    }\n  })\n\n  // 8. sync firebase configs for rules & hosting for renamed apps\n  workspace.renamedApps.forEach((project, oldProjectName) => {\n    const configFileName = workspace.firebaseAppConfigs.get(project.name)\n    const config = workspace.firebaseConfigs.get(configFileName)\n\n    // update config rules that are in the firebase app\n    const databaseRules = joinPathFragments(project.root, 'database.rules.json')\n    if (config.database && config.database.rules !== databaseRules) {\n      config.database.rules = databaseRules\n      logger.info(\n        `CHANGE Firebase app '${oldProjectName}' was renamed to '${project.name}', updated database rules in '${configFileName}'`,\n      )\n    }\n\n    const firestoreRules = joinPathFragments(project.root, 'firestore.rules')\n    if (config.firestore && config.firestore.rules !== firestoreRules) {\n      config.firestore.rules = firestoreRules\n      logger.info(\n        `CHANGE Firebase app '${oldProjectName}' was renamed to '${project.name}', updated firestore rules in '${configFileName}'`,\n      )\n    }\n\n    const firestoreIndexes = joinPathFragments(\n      project.root,\n      'firestore.indexes.json',\n    )\n    if (config.firestore && config.firestore.indexes !== firestoreIndexes) {\n      config.firestore.indexes = firestoreIndexes\n      logger.info(\n        `CHANGE Firebase app '${oldProjectName}' was renamed to '${project.name}', updated firestore indexes in '${configFileName}'`,\n      )\n    }\n\n    const storageRules = joinPathFragments(project.root, 'storage.rules')\n    if (config.storage && config.storage.rules !== storageRules) {\n      config.storage.rules = storageRules\n      logger.info(\n        `CHANGE Firebase app '${oldProjectName}' was renamed to '${project.name}', updated storage rules in '${configFileName}'`,\n      )\n    }\n\n    writeJson(tree, configFileName, config)\n  })\n\n  // 9. validate hosting rules match a project\n  workspace.firebaseConfigs.forEach((config, configFileName) => {\n    const projects = getProjects(tree)\n    if (!Array.isArray(config.hosting)) {\n      config.hosting = [config.hosting]\n    }\n    config.hosting.forEach((host) => {\n      let isValid = false\n      projects.forEach((nxProject) => {\n        if (host.public.includes(nxProject.root)) {\n          isValid = true\n        }\n      })\n      if (!isValid) {\n        logger.warn(\n          `WARNING: Can't match hosting target with public dir '${host.public}' in '${configFileName}' to a project in this workspace. Is it configured correctly?`,\n        )\n      }\n    })\n  })\n\n  // if user deletes a project that was linked to firebase.json config but there\n  // are other firebase apps in the workspace, we need to inform user about\n  // this, since we dont have a way\n  // other firebase apps in the workspace, we'll just advise\n  if (!tree.exists('firebase.json') && workspace.firebaseAppProjects.size) {\n    logger.warn(\n      `None of the Firebase apps in this workspace use 'firebase.json' as their config. Firebase CLI may not work as expected. This can be fixed by renaming the config for one of your firebase projects to 'firebase.json'.`,\n    )\n  }\n\n  return runTasksInSerial(...tasks)\n}\n\nexport default syncGenerator\n"
  },
  {
    "path": "packages/nx-firebase/src/index.ts",
    "content": ""
  },
  {
    "path": "packages/nx-firebase/src/types/index.ts",
    "content": "export * from './lib/firebase-config'\nexport * from './lib/firebase-function'\nexport * from './lib/firebase-workspace'\n"
  },
  {
    "path": "packages/nx-firebase/src/types/lib/firebase-config.ts",
    "content": "export interface FirebaseFunction {\n  predeploy?: string[]\n  source: string\n  codebase: string\n  runtime: string // 'nodejs16' | 'nodejs18' | 'nodejs20'\n  ignore?: string[]\n}\n\nexport interface FirebaseHosting {\n  target?: string\n  public: string\n  ignore?: string[]\n  redirects?: {\n    source: string\n    destination: string\n    type: number\n  }[]\n  rewrites?: {\n    source: string\n    destination: string\n    dynamicLinks?: boolean\n    function?: {\n      functionId: string\n      region?: string\n      pinTag?: boolean\n    }\n    run?: {\n      serviceId: string\n      region: string\n    }\n  }\n  cleanUrls?: boolean\n  trailingSlash?: boolean\n  appAssociation?: 'AUTO'\n  headers?: {\n    source: string\n    headers: {\n      key: string\n      value: string\n    }[]\n  }[]\n}\n\nexport interface FirebaseConfig {\n  database: {\n    rules: string\n  }\n  firestore: {\n    rules: string\n    indexes: string\n  }\n  hosting: FirebaseHosting | FirebaseHosting[]\n  storage: {\n    rules: string\n  }\n  functions: FirebaseFunction | FirebaseFunction[]\n  emulators: {\n    functions: {\n      port: number\n    }\n    firestore: {\n      port: number\n    }\n    hosting: {\n      port: number\n    }\n    auth: {\n      port: number\n    }\n    pubsub: {\n      port: number\n    }\n  }\n}\n"
  },
  {
    "path": "packages/nx-firebase/src/types/lib/firebase-function.ts",
    "content": "export type FunctionAssetsGlob = {\n  glob: string\n  input: string\n  output: string\n}\nexport type FunctionAssetsEntry = string | FunctionAssetsGlob\n"
  },
  {
    "path": "packages/nx-firebase/src/types/lib/firebase-workspace.ts",
    "content": "import { ProjectConfiguration } from '@nx/devkit'\n\nimport { FirebaseConfig } from './firebase-config'\n\nexport const CONFIG_NO_APP = 'CONFIG_NO_APP'\n\nexport interface FirebaseProjects {\n  /**\n   * app projectName -> ProjectConfig\n   */\n  firebaseAppProjects: Map<string, ProjectConfiguration>\n  /**\n   * function projectName -> ProjectConfig\n   */\n  firebaseFunctionProjects: Map<string, ProjectConfiguration>\n}\n\nexport interface FirebaseConfigs {\n  /**\n   * configFilename -> FirebaseConfig\n   */\n  firebaseConfigs: Map<string, FirebaseConfig>\n  /**\n   * projectName -> configFileName\n   */\n  firebaseAppConfigs: Map<string, string>\n  /**\n   * configFilename -> Project Name (or MISSING_CONFIG if config is not referenced by an app)\n   */\n  firebaseConfigProjects: Map<string, string>\n}\n\nexport interface FirebaseChanges {\n  /**\n   * map of app project name -> deletion status\n   */\n  deletedApps: Map<string, boolean>\n  /**\n   * map of function project name -> deletion status\n   */\n  deletedFunctions: Map<string, boolean>\n  /**\n   * map of previous app name -> new app project\n   */\n  renamedApps: Map<string, ProjectConfiguration>\n\n  /**\n   * map of previous function name -> new function project\n   */\n  renamedFunctions: Map<string, ProjectConfiguration>\n}\n\nexport type FirebaseWorkspace = FirebaseProjects &\n  FirebaseChanges &\n  FirebaseConfigs\n"
  },
  {
    "path": "packages/nx-firebase/src/utils/debug.ts",
    "content": "import { logger } from '@nx/devkit'\n\n// debug info just for plugin\nconst ENABLE_DEBUG_INFO = false\n\nexport function debugInfo(info: string) {\n  if (ENABLE_DEBUG_INFO) {\n    logger.info(info)\n  }\n}\n\nexport function mapKeys<T, R>(map: Map<T, R>) {\n  return JSON.stringify([...map.keys()], null, 3)\n}\n\nexport function mapValues<T, R>(map: Map<T, R>) {\n  return JSON.stringify([...map.values()], null, 3)\n}\n\nexport function mapEntries<T, R>(map: Map<T, R>) {\n  return JSON.stringify([...map.entries()], null, 3)\n}\n"
  },
  {
    "path": "packages/nx-firebase/src/utils/firebase-config.ts",
    "content": "import { Tree, ProjectConfiguration } from '@nx/devkit'\n\nconst FIREBASE_TARGET_CONFIG_MATCHER = /(--config[ =])([^\\s]+)/\n\n/**\n * Return the config file from the provided firebase target command\n * This can be used to parse commands in additional configurations\n * @param project\n * @param command\n * @returns\n */\nexport function getFirebaseConfigFromCommand(\n  tree: Tree,\n  project: ProjectConfiguration,\n  command: string,\n) {\n  const match = command.match(FIREBASE_TARGET_CONFIG_MATCHER)\n  if (match && match.length === 3) {\n    const configName = match[2]\n    // check the config we've parsed actually resolves to a firebase config file in the workspace\n    if (!tree.exists(configName)) {\n      throw new Error(\n        `Firebase app project ${project.name} is using a firebase config file ${configName} that does not exist in the workspace.`,\n      )\n    }\n    return configName\n  }\n  throw new Error(\n    `Firebase app project ${project.name} does not have --config set in its 'firebase' target.`,\n  )\n}\n\n/**\n * Return the config file used by the `firebase` target command of the provided firebase app project\n * @param command\n * @param project\n * @param firebaseConfigs\n * @returns\n */\nexport function getFirebaseConfigFromProject(\n  tree: Tree,\n  project: ProjectConfiguration,\n) {\n  return getFirebaseConfigFromCommand(\n    tree,\n    project,\n    project.targets.firebase.options.command,\n  )\n}\n\n/**\n * Modify the config file used by the `firebase` target command of the provided firebase app project\n * @param project\n * @param configFileName\n */\nexport function setFirebaseConfigFromCommand(\n  project: ProjectConfiguration,\n  configFileName: string,\n) {\n  // we've already checked that firebase target exists when setting up workspace\n  const firebaseTarget = project.targets.firebase\n  firebaseTarget.options.command = firebaseTarget.options.command.replace(\n    FIREBASE_TARGET_CONFIG_MATCHER,\n    '$1' + configFileName,\n  )\n  // do this for all other configurations on this target too\n  const configurations = firebaseTarget.configurations\n  for (const configuration in configurations) {\n    configurations[configuration].command = configurations[\n      configuration\n    ].command.replace(FIREBASE_TARGET_CONFIG_MATCHER, '$1' + configFileName)\n  }\n}\n"
  },
  {
    "path": "packages/nx-firebase/src/utils/index.ts",
    "content": "export * from './workspace'\nexport * from './update-tsconfig'\nexport * from './project-name'\nexport * from './firebase-config'\nexport * from './debug'\n"
  },
  {
    "path": "packages/nx-firebase/src/utils/project-name.ts",
    "content": "import {\n  getWorkspaceLayout,\n  joinPathFragments,\n  names,\n  Tree,\n  extractLayoutDirectory,\n} from '@nx/devkit'\n\nexport function getProjectName(tree: Tree, name: string, directory?: string) {\n  // console.log(`name ${name}`)\n  // console.log(`directory ${directory}`)\n\n  const { layoutDirectory, projectDirectory } =\n    extractLayoutDirectory(directory)\n\n  // console.log(`layoutDirectory ${layoutDirectory}`)\n  // console.log(`projectDirectory ${projectDirectory}`)\n\n  const appsDir = layoutDirectory ?? getWorkspaceLayout(tree).appsDir\n\n  // console.log(`appsDir ${appsDir}`)\n\n  const appDirectory = projectDirectory\n    ? `${names(projectDirectory).fileName}/${names(name).fileName}`\n    : names(name).fileName\n\n  // console.log(`appDirectory ${appDirectory}`)\n\n  const appProjectName = appDirectory.replace(new RegExp('/', 'g'), '-')\n\n  // console.log(`appProjectName ${appProjectName}`)\n\n  const appProjectRoot = joinPathFragments(appsDir, appDirectory)\n\n  // console.log(`appProjectRoot ${appProjectRoot}`)\n\n  // // see https://github.com/nrwl/nx/blob/84cbcb7e105cd2b3bf5b3d84a519e5c52951e0f3/packages/js/src/generators/library/library.ts#L332\n  // // for how the project name is derived from options.name and --directory\n  // const fileName = names(name).fileName\n  // const projectDirectory = directory\n  //   ? `${names(directory).fileName}/${fileName}`\n  //   : name\n\n  // const projectRoot = joinPathFragments(\n  //   getWorkspaceLayout(tree).appsDir,\n  //   projectDirectory,\n  // )\n\n  // const projectName = projectDirectory.replace(new RegExp('/', 'g'), '-')\n  return {\n    projectRoot: appProjectRoot,\n    projectName: appProjectName,\n  }\n}\n"
  },
  {
    "path": "packages/nx-firebase/src/utils/update-tsconfig.ts",
    "content": "import { type Tree, joinPathFragments, updateJson } from '@nx/devkit'\nimport { packageVersions } from '../__generated__/nx-firebase-versions'\n\n// https://stackoverflow.com/questions/59787574/typescript-tsconfig-settings-for-node-js-12\n\nexport const nodeEsVersion: Record<string, string> = {\n  '18': 'es2022',\n  '20': 'es2022',\n  '22': 'es2023',\n  '24': 'es2024',\n}\n\n/**\n * With firebase cli > 10.0.1 now compatible with node versions >=14 we can use es modules rather than commonjs\n *\n * @param tree\n * @param options\n */\nexport function updateTsConfig(\n  tree: Tree,\n  projectRoot: string,\n  runTime: string,\n  format: 'esm' | 'cjs',\n): void {\n  const tsConfigTarget =\n    nodeEsVersion[runTime] ?? nodeEsVersion[packageVersions.nodeEngine]\n  updateJson(\n    tree,\n    joinPathFragments(projectRoot, 'tsconfig.app.json'),\n    (json) => {\n      json.compilerOptions.target = tsConfigTarget\n      json.compilerOptions.module = format === 'esm' ? 'es2020' : 'commonjs'\n      return json\n    },\n  )\n}\n"
  },
  {
    "path": "packages/nx-firebase/src/utils/workspace.ts",
    "content": "import { logger, readJsonFile, workspaceRoot } from '@nx/devkit'\nimport { packageVersions } from '../__generated__/nx-firebase-versions'\n\ntype PackageJson = {\n  dependencies: {\n    [packageName: string]: string\n  }\n  devDependencies: {\n    [packageName: string]: string\n  }\n}\n\n// we dont care about patch versions, also they might be alpha or beta type strings\nexport type WorkspaceVersion =\n  | {\n      version: string\n      versionCode: number // major*10000 + minor*100 + patch eg. 151001\n      major: number\n      minor: number\n    }\n  | undefined\n\nfunction readNxWorkspaceVersion(): WorkspaceVersion {\n  // Check the runtime Nx version being used by the current workspace\n  const semVerRegEx = /[~^]?([\\dvx*]+(?:[-.](?:[\\dx*]+|alpha|beta))*)/g\n  const workspacePackage = readJsonFile<PackageJson>(\n    `${workspaceRoot}/package.json`,\n  )\n  const workspaceNxPackageVersion = workspacePackage.devDependencies['nx']\n  if (workspaceNxPackageVersion) {\n    const workspaceNxVersion = workspaceNxPackageVersion.match(semVerRegEx)\n    if (workspaceNxVersion.length) {\n      const semver = workspaceNxVersion[0].split('.')\n      const major = parseInt(semver[0])\n      const minor = parseInt(semver[1])\n      const patch = parseInt(semver[2])\n      return {\n        version: workspaceNxVersion[0],\n        versionCode: major * 10000 + minor * 100 + patch,\n        major,\n        minor,\n      }\n    }\n  }\n  return undefined\n}\n\n// determine the Nx version being used by the host workspace\nexport const workspaceNxVersion = readNxWorkspaceVersion()\n\nexport function checkNxVersion() {\n  // Declare target version of Nx that the plugin is currently compatible with\n  const pluginNxVersionMajor = parseInt(packageVersions.nx.split('.')[0])\n  if (workspaceNxVersion) {\n    if (workspaceNxVersion.major !== pluginNxVersionMajor) {\n      logger.warn(\n        `WARNING: This version of @simondotm/nx-firebase plugin was built for Nx version ^${packageVersions.nx}, and may not be compatible with your version of Nx (${workspaceNxVersion.version})`,\n      )\n    }\n  } else {\n    logger.warn(\n      `@simondotm/nx-firebase plugin could not determine your version of Nx. It may not be compatible.`,\n    )\n  }\n}\n"
  },
  {
    "path": "packages/nx-firebase/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"module\": \"commonjs\"\n  },\n  \"files\": [],\n  \"include\": [],\n  \"references\": [\n    {\n      \"path\": \"./tsconfig.lib.json\"\n    },\n    {\n      \"path\": \"./tsconfig.spec.json\"\n    }\n  ]\n}\n"
  },
  {
    "path": "packages/nx-firebase/tsconfig.lib.json",
    "content": "{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../dist/out-tsc\",\n    \"declaration\": true,\n    \"types\": [\"node\"]\n  },\n  \"include\": [\n    \"**/*.ts\",\n    \"../archived-nx-firebase/src/generators/init/generator.spec.ts\",\n    \"src/generators/application/files/src/index.ts__template__\"\n  ],\n  \"exclude\": [\"jest.config.ts\", \"jest.setup.ts\", \"**/*.spec.ts\", \"**/*.test.ts\"]\n}\n"
  },
  {
    "path": "packages/nx-firebase/tsconfig.spec.json",
    "content": "{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../dist/out-tsc\",\n    \"module\": \"commonjs\",\n    \"types\": [\"jest\", \"node\"]\n  },\n  \"include\": [\n    \"jest.config.ts\",\n    \"jest.setup.ts\",\n    \"**/*.test.ts\",\n    \"**/*.spec.ts\",\n    \"**/*.d.ts\",\n    \"../archived-nx-firebase/src/generators/init/generator.spec.ts\"\n  ]\n}\n"
  },
  {
    "path": "tools/generate-package-versions.js",
    "content": "const path = require('path')\nconst fs = require('fs')\n\nconst packageJson = require('../package.json')\nconst nxVersion = packageJson.devDependencies['nx']\nconst nxMajorVersion = parseInt(nxVersion.split('.')[0])\n\n// // read node version from .nvmrc\n// const nvmVersion = fs.readFileSync(path.join(__dirname, '..', '.nvmrc'), 'utf8')\n// const nodeVersion = nvmVersion.trim().split('.')[0]\n\n// default firebase node version is to be derived from Nx version for now\n// Nx 21+ officially supports Node 20+, Nx 17-20 supports Node 18+\nconst nodeVersion = nxMajorVersion >= 21 ? '20' : nxMajorVersion >= 17 ? '18' : '16'\n\nfunction ensureDirectoryExistence(filePath) {\n  const dirname = path.dirname(filePath)\n  if (fs.existsSync(dirname)) {\n    return true\n  }\n  ensureDirectoryExistence(dirname)\n  fs.mkdirSync(dirname)\n}\n\n\nfunction shouldUpdateFile(file, data) {\n  if (!fs.existsSync(file)) {\n    return true\n  }\n  const existingData = fs.readFileSync(file, 'utf8')\n  return existingData !== data\n}\n\nfunction maybeUpdateFile(file, data) {\n  if (shouldUpdateFile(file, data)) {\n    console.log(`Updating '${file}'`)\n    fs.writeFileSync(file, data)\n  }\n}\n\nfunction updateVersions() {\n  const generatedFile = path.join(\n    __dirname,\n    '..',\n    'packages',\n    'nx-firebase',\n    'src',\n    '__generated__',\n    'nx-firebase-versions.ts',\n  )\n\n  // strip out the ^ and ~ from the versions\n  for (const [key, value] of Object.entries(packageJson.devDependencies)) {\n    packageJson.devDependencies[key] = value.replace('^', '').replace('~', '')\n  }\n\n  const data = `//------------------------------------------------------------------------------\n// This file is automatically generated by tools/generate-package-versions.js\n// Do not edit this file manually\n//------------------------------------------------------------------------------\nexport const packageVersions = {\n  nx: '${nxVersion}',\n  firebase: '${packageJson.devDependencies['firebase']}',\n  firebaseAdmin: '${packageJson.devDependencies['firebase-admin']}',\n  firebaseFunctions: '${packageJson.devDependencies['firebase-functions']}',\n  firebaseFunctionsTest: '${packageJson.devDependencies['firebase-functions-test']}',\n  firebaseTools: '${packageJson.devDependencies['firebase-tools']}',\n  killPort: '${packageJson.devDependencies['kill-port']}',\n  nodeEngine: '${nodeVersion}',\n  googleCloudFunctionsFramework: '${packageJson.devDependencies['@google-cloud/functions-framework']}',\n}\n`\n  ensureDirectoryExistence(generatedFile)\n  maybeUpdateFile(generatedFile, data)\n}\n\nfunction updateTemplates() {\n  const pluginSrcDir = path.join(__dirname, '..', 'packages', 'nx-firebase', 'src', 'generators')\n  const firebaseTemplatesDir = path.join(__dirname, '..', 'node_modules', 'firebase-tools', 'templates', 'init' )\n\n  const templates = [\n    [ path.join(firebaseTemplatesDir, 'hosting', 'index.html'), path.join(pluginSrcDir, 'application', 'files', 'public', 'index.html')],\n    [ path.join(firebaseTemplatesDir, 'hosting', '404.html'), path.join(pluginSrcDir, 'application', 'files', 'public', '404.html')],\n    [ path.join(firebaseTemplatesDir, 'firestore', 'firestore.indexes.json'), path.join(pluginSrcDir, 'application', 'files', 'firestore.indexes.json')],\n    [ path.join(firebaseTemplatesDir, 'firestore', 'firestore.rules'), path.join(pluginSrcDir, 'application', 'files', 'firestore.rules')],\n    [ path.join(firebaseTemplatesDir, 'storage', 'storage.rules'), path.join(pluginSrcDir, 'application', 'files', 'storage.rules')],\n    [ path.join(firebaseTemplatesDir, 'functions', 'typescript', 'index.ts'), path.join(pluginSrcDir, 'function', 'files', 'src', 'main.ts__tmpl__')],\n  ]\n\n  for (const [src, dest] of templates) {\n    let data = fs.readFileSync(src, 'utf8')\n    if (src.includes('index.html')){\n      data = data.replaceAll('{{VERSION}}', packageJson.devDependencies['firebase'])\n    }    \n    if (src.includes('firestore.rules')){\n      data = data.replaceAll('{{IN_30_DAYS}}', '<%= IN_30_DAYS %>')\n    }\n    maybeUpdateFile(dest, data)\n  }\n}\n\nupdateVersions()\nupdateTemplates()\n"
  },
  {
    "path": "tools/tsconfig.tools.json",
    "content": "{\n  \"extends\": \"../tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../dist/out-tsc/tools\",\n    \"rootDir\": \".\",\n    \"module\": \"commonjs\",\n    \"target\": \"es5\",\n    \"types\": [\"node\"],\n    \"importHelpers\": false\n  },\n  \"include\": [\"**/*.ts\"]\n}\n"
  },
  {
    "path": "tsconfig.base.json",
    "content": "{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"rootDir\": \".\",\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"moduleResolution\": \"node\",\n    \"emitDecoratorMetadata\": true,\n    \"experimentalDecorators\": true,\n    \"importHelpers\": true,\n    \"target\": \"es2015\",\n    \"module\": \"esnext\",\n    \"lib\": [\"es2017\", \"dom\"],\n    \"skipLibCheck\": true,\n    \"skipDefaultLibCheck\": true,\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"@simondotm/nx-firebase\": [\"packages/nx-firebase/src/index.ts\"]\n    }\n  },\n  \"exclude\": [\"node_modules\", \"tmp\"]\n}\n"
  }
]