Showing preview only (376K chars total). Download the full file or copy to clipboard to get everything.
Repository: simondotm/nx-firebase
Branch: main
Commit: d02e6243cb27
Files: 162
Total size: 334.3 KB
Directory structure:
gitextract_197jq99g/
├── .editorconfig
├── .github/
│ ├── dependabot.yml
│ └── workflows/
│ ├── ci.yml
│ └── publish.yml
├── .gitignore
├── .nvmrc
├── .prettierignore
├── .prettierrc
├── .vscode/
│ ├── extensions.json
│ ├── settings.json
│ └── tasks.json
├── CHANGELOG.md
├── README.md
├── docs/
│ ├── nx-firebase-applications.md
│ ├── nx-firebase-databases.md
│ ├── nx-firebase-emulators.md
│ ├── nx-firebase-functions-environment.md
│ ├── nx-firebase-functions.md
│ ├── nx-firebase-hosting.md
│ ├── nx-firebase-migrations.md
│ ├── nx-firebase-project-structure.md
│ ├── nx-firebase-projects.md
│ ├── nx-firebase-sync.md
│ ├── nx-libraries.md
│ ├── nx-migration.md
│ ├── nx-plugin-commands.md
│ ├── nx-setup-mac.md
│ ├── nx-workspace-layout.md
│ └── user-guide.md
├── e2e/
│ ├── compat/
│ │ ├── eslint.config.mjs
│ │ ├── jest.config.ts
│ │ ├── project.json
│ │ ├── src/
│ │ │ ├── app/
│ │ │ │ ├── config.ts
│ │ │ │ ├── setup.ts
│ │ │ │ ├── test.ts
│ │ │ │ ├── utils/
│ │ │ │ │ ├── cache.ts
│ │ │ │ │ ├── cwd.ts
│ │ │ │ │ ├── exec.ts
│ │ │ │ │ ├── jest-ish.ts
│ │ │ │ │ ├── log.ts
│ │ │ │ │ └── utils.ts
│ │ │ │ ├── versions.ts
│ │ │ │ └── workspace.ts
│ │ │ ├── assets/
│ │ │ │ └── .gitkeep
│ │ │ ├── environments/
│ │ │ │ ├── environment.prod.ts
│ │ │ │ └── environment.ts
│ │ │ └── main.ts
│ │ ├── tsconfig.app.json
│ │ ├── tsconfig.json
│ │ ├── tsconfig.spec.json
│ │ └── webpack.config.js
│ └── nx-firebase-e2e/
│ ├── jest.config.js
│ ├── jest.globalSetup.js
│ ├── jest.globalTeardown.js
│ ├── jest.testSequencer.js
│ ├── project.json
│ ├── test-utils/
│ │ ├── index.ts
│ │ ├── test-shared-data.ts
│ │ ├── test-utils-apps.ts
│ │ ├── test-utils-commands.ts
│ │ ├── test-utils-functions.ts
│ │ ├── test-utils-helpers.ts
│ │ ├── test-utils-imports.ts
│ │ ├── test-utils-logger.ts
│ │ └── test-utils-project-data.ts
│ ├── tests/
│ │ ├── test-application.spec.ts
│ │ ├── test-bundler.spec.ts
│ │ ├── test-function.spec.ts
│ │ ├── test-libraries.spec.ts
│ │ ├── test-migrate.spec.ts
│ │ ├── test-sync.spec.ts
│ │ ├── test-targets.spec.ts
│ │ └── test-workspace.spec.ts
│ ├── tsconfig.json
│ └── tsconfig.spec.json
├── eslint.config.mjs
├── jest.config.ts
├── jest.preset.js
├── migrations.json
├── nx.json
├── package.json
├── packages/
│ └── nx-firebase/
│ ├── README.md
│ ├── eslint.config.mjs
│ ├── executors.json
│ ├── generators.json
│ ├── jest.config.ts
│ ├── jest.setup.ts
│ ├── package.json
│ ├── project.json
│ ├── src/
│ │ ├── __generated__/
│ │ │ └── nx-firebase-versions.ts
│ │ ├── executors/
│ │ │ └── serve/
│ │ │ ├── schema.d.ts
│ │ │ ├── schema.json
│ │ │ ├── serve.spec.ts
│ │ │ └── serve.ts
│ │ ├── generators/
│ │ │ ├── application/
│ │ │ │ ├── application.spec.ts
│ │ │ │ ├── application.ts
│ │ │ │ ├── files/
│ │ │ │ │ ├── database.rules.json
│ │ │ │ │ ├── environment/
│ │ │ │ │ │ └── .secret.local
│ │ │ │ │ ├── firestore.indexes.json
│ │ │ │ │ ├── firestore.rules
│ │ │ │ │ ├── public/
│ │ │ │ │ │ ├── 404.html
│ │ │ │ │ │ └── index.html
│ │ │ │ │ ├── readme.md__tmpl__
│ │ │ │ │ └── storage.rules
│ │ │ │ ├── files_firebase/
│ │ │ │ │ └── firebase.json__tmpl__
│ │ │ │ ├── files_firebaserc/
│ │ │ │ │ └── .firebaserc__tmpl__
│ │ │ │ ├── files_workspace/
│ │ │ │ │ └── firebase.__projectName__.json__tmpl__
│ │ │ │ ├── lib/
│ │ │ │ │ ├── create-files.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── schema.d.ts
│ │ │ │ └── schema.json
│ │ │ ├── function/
│ │ │ │ ├── files/
│ │ │ │ │ ├── package.json__tmpl__
│ │ │ │ │ ├── readme.md__tmpl__
│ │ │ │ │ └── src/
│ │ │ │ │ └── main.ts__tmpl__
│ │ │ │ ├── function.spec.ts
│ │ │ │ ├── function.ts
│ │ │ │ ├── lib/
│ │ │ │ │ ├── add-function-config.ts
│ │ │ │ │ ├── create-files.ts
│ │ │ │ │ ├── delete-files.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── update-project.ts
│ │ │ │ ├── schema.d.ts
│ │ │ │ └── schema.json
│ │ │ ├── init/
│ │ │ │ ├── init.spec.ts
│ │ │ │ ├── init.ts
│ │ │ │ ├── lib/
│ │ │ │ │ ├── add-dependencies.ts
│ │ │ │ │ ├── add-git-ignore-entry.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── schema.d.ts
│ │ │ │ └── schema.json
│ │ │ ├── migrate/
│ │ │ │ ├── migrate.spec.ts
│ │ │ │ ├── migrate.ts
│ │ │ │ ├── schema.d.ts
│ │ │ │ └── schema.json
│ │ │ └── sync/
│ │ │ ├── lib/
│ │ │ │ ├── firebase-changes.ts
│ │ │ │ ├── firebase-configs.ts
│ │ │ │ ├── firebase-projects.ts
│ │ │ │ ├── firebase-workspace.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── tags.ts
│ │ │ │ └── update-targets.ts
│ │ │ ├── schema.d.ts
│ │ │ ├── schema.json
│ │ │ ├── sync.spec.ts
│ │ │ └── sync.ts
│ │ ├── index.ts
│ │ ├── types/
│ │ │ ├── index.ts
│ │ │ └── lib/
│ │ │ ├── firebase-config.ts
│ │ │ ├── firebase-function.ts
│ │ │ └── firebase-workspace.ts
│ │ └── utils/
│ │ ├── debug.ts
│ │ ├── firebase-config.ts
│ │ ├── index.ts
│ │ ├── project-name.ts
│ │ ├── update-tsconfig.ts
│ │ └── workspace.ts
│ ├── tsconfig.json
│ ├── tsconfig.lib.json
│ └── tsconfig.spec.json
├── tools/
│ ├── generate-package-versions.js
│ └── tsconfig.tools.json
└── tsconfig.base.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
# Editor configuration, see http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false
================================================
FILE: .github/dependabot.yml
================================================
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "npm" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "monthly"
ignore:
# Ignore updates to these packages:
- dependency-name: "@nx*"
- dependency-name: "@swc*"
- dependency-name: "@types/*"
- dependency-name: "@typescript*"
- dependency-name: "eslint*"
- dependency-name: "jest*"
- dependency-name: "nx*"
- dependency-name: "prettier*"
- dependency-name: "ts-jest"
- dependency-name: "ts-node"
- dependency-name: "tslib"
- dependency-name: "typescript"
# For all packages, ignore all patch updates
# EDIT: Actually, we'll take all updates, but this is a good example
# - dependency-name: "*"
# update-types: ["version-update:semver-patch"]
================================================
FILE: .github/workflows/ci.yml
================================================
# Build, Test, Lint & e2e pushes.
name: CI
on:
# support manual trigger of this workflow
workflow_dispatch:
# run this build action for all pushes to main
push:
branches: ['main']
# also run for pull requests that target main
pull_request:
branches:
- main
jobs:
setup:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'pnpm'
- run: pnpm install
build:
needs: [setup]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'pnpm'
- run: pnpm install
- run: pnpm run build
lint:
needs: [setup]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'pnpm'
- run: pnpm install
- run: pnpm run lint
test:
needs: [setup]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'pnpm'
- run: pnpm install
- run: pnpm run test
# e2e might be too much work todo for every push, lets see.
e2e:
needs: [setup]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'pnpm'
- run: pnpm install
- run: pnpm run e2e
================================================
FILE: .github/workflows/publish.yml
================================================
# Publish package to npm when a Github release is published
name: Publish
on:
release:
types: [released]
jobs:
verify_tag:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: |
TAG_VERSION=${GITHUB_REF#refs/tags/v}
PACKAGE_VERSION=$(node -p "require('./packages/nx-firebase/package.json').version")
echo "Tag version: $TAG_VERSION"
echo "Package version: $PACKAGE_VERSION"
if [ "$TAG_VERSION" != "$PACKAGE_VERSION" ]; then
echo "Tag version ($TAG_VERSION) does not match package.json version ($PACKAGE_VERSION)"
exit 1
fi
shell: bash
publish_github:
needs: [verify_tag]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'pnpm'
registry-url: 'https://npm.pkg.github.com/simondotm'
#scope: '@simondotm'
#- run: npm run addscope
- run: pnpm install
- name: Build plugin
run: npx nx build nx-firebase
- name: Create package
run: pnpm pack
working-directory: ./dist/packages/nx-firebase
- name: Publish to GitHub Packages
run: pnpm publish --no-git-checks
working-directory: ./dist/packages/nx-firebase
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
publish_npm:
needs: [verify_tag]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'pnpm'
registry-url: 'https://registry.npmjs.org'
- run: pnpm install
- name: Build plugin
run: npx nx build nx-firebase
- name: Publish to NPM
run: pnpm publish --no-git-checks --access=public
working-directory: ./dist/packages/nx-firebase
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
================================================
FILE: .gitignore
================================================
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
dist
tmp
/out-tsc
# dependencies
node_modules
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings
# System Files
.DS_Store
Thumbs.db
e2e.log
.nx/cache
.nx/workspace-data
.nx-firebase
.cursor/rules/nx-rules.mdc
.github/instructions/nx.instructions.md
================================================
FILE: .nvmrc
================================================
22.21.1
================================================
FILE: .prettierignore
================================================
# Add files here to ignore them from prettier formatting
/dist
/coverage
/.nx/cache
/.nx/workspace-data
================================================
FILE: .prettierrc
================================================
{
"semi": false,
"singleQuote": true,
"trailingComma": "all",
"arrowParens": "always",
"printWidth": 80,
"bracketSpacing": true,
"endOfLine": "auto",
"useTabs": false,
"quoteProps": "as-needed",
"tabWidth": 2
}
================================================
FILE: .vscode/extensions.json
================================================
{
"recommendations": [
"nrwl.angular-console",
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"firsttris.vscode-jest-runner"
]
}
================================================
FILE: .vscode/settings.json
================================================
{
"eslint.validate": ["json"]
}
================================================
FILE: .vscode/tasks.json
================================================
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "build -- nx-firebase",
"group": "build",
"problemMatcher": [],
"label": "Build Nx firebase plugin",
"detail": "nx build nx-firebase"
},
{
"type": "npm",
"script": "test -- nx-firebase",
"group": "test",
"problemMatcher": [],
"label": "Test Nx firebase plugin",
"detail": "nx test nx-firebase"
},
{
"type": "npm",
"script": "e2e -- nx-firebase-e2e",
"group": "test",
"problemMatcher": [],
"label": "Test e2e Nx firebase plugin",
"detail": "nx e2e nx-firebase-e2e"
}
]
}
================================================
FILE: CHANGELOG.md
================================================
# @simondotm/nx-firebase Changelog
* **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.
* **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.
## v17.3.1 Onwards
Versioning of this plugin has been aligned with Nx versioning from v17.3.1 onwards.
Check the [GitHub releases page](https://github.com/simondotm/nx-firebase/releases) for details of each release.
## v2.3.0  
- Updated plugin to be built against Nx 16.8.1
- Updated documentation to reflect latest changes
- App & Function generators now use the same templates as the Firebase tools SDK
- Firebase dependencies used by generators now derived from plugin workspace & dependabot help
- `firebase-functions` -> 4.8.2
- `firebase-functions-test` -> 3.1.1
- `firebase-admin` -> 11.11.1
- `firebase` -> 10.10.0
- `firebase-tools` -> 12.9.1
- Support [--projectNameAndRootFormat](https://nx.dev/nx-api/node/generators/application#projectnameandrootformat) for application and function generators
- Support `pnpm` Nx workspaces for function deployment by adding `@google-cloud/functions-framework` to the function dependencies
## v2.2.0  
- Fix package dependencies for `@nx/node`, plugin init generator now adds this if/when needed
- This prevents scenarios where incorrect Nx plugin versions could be added to the user workspace
- It may also fix an issue with `@nx/esbuild` plugin version being out of sync and bundling external dependencies incorrectly
## v2.1.2  
- Include `@nx/devkit` as a peer dependency for the package
- Listing in the [Nx plugin registry](https://nx.dev/plugin-registry)
## v2.1.1  
- Fix issues in the v2.1.0 release
## v2.1.0  
- Added support for [environment variables](docs/nx-firebase-functions-environment.md)
- Added support for [secrets](docs/nx-firebase-functions-environment.md#environment-file-types)
- Added a custom `serve` executor that exits the Firebase Emulator suite properly
- Fixes to `sync` generator to update firebase app and firebase function project targets when they are renamed
- Added a custom `migrate` generator to ensure workspace configurations match the latest plugin version schemas
- Updated plugin to be built against Nx 16.6.0
Read [here for plugin v2.0.0 -> v2.1.0+ migration instructions](docs/nx-firebase-migrations.md#migrating-from-plugin-v200-to-v210)
## v2.0.0  
Official v2 release
- Updated default function template to match firebase CLI
## v2.0.0-beta.1  
- Fixed issue with dependencies
- Sort codebases
- Updated documents
## v2.0.0-beta.0  
Initial beta of plugin version 2.0.
**Changes**
- Plugin is completely rewritten
- Added `@simondotm/nx-firebase:function` generator
- Firebase functions are now separate application nx projects
- Now uses `esbuild` to compile & bundle firebase functions
- Firebase functions no longer require Nx libraries to be buildable
- Added `@simondotm/nx-firebase:sync` generator to manage Firebase workspaces
- Minimum Nx version is now 16.1.1
- Watch mode build of function code & libraries is now fully supported when running Firebase emulator
# Version 2
This 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).
Users of earlier plugin versions must read [here for plugin v1 -> v2 migration instructions](docs/nx-firebase-migrations.md#migration-from-plugin-v1x-to-v200)
> **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**
# Version 1
## v1.1.0  
No changes from beta.
## v1.1.0-beta.0  
Updated for Nx 16.1.1+
- You will need to migrate your workspace to Nx 16.1.1 before updating the plugin
## v1.0.0  
First Major release. No change since `0.13.0-beta.1`.
Compatible with Nx 13.10.6+
## v0.13.0-beta.1  
**Changes**
- `main` removed from template `package.json` for firebase app generator - this is automatically set by the builder
- Support `nx watch` in compatible Nx workspace hosts
- Support `nx:run-commands` in compatible Nx workspace hosts
- Various small fixes
**Migration Guide**
- Check the [targets documentation](docs/nx-firebase-targets.md) if you already have a workspace that is using nx-firebase
- Remove `main` from `package.json` files in any nx-firebase apps in your workspace
## v0.13.0-beta.0  
Due to the large number of API changes in Nx from version 12 to version 13.10, this plugin has been rewritten from scratch:
- **Improved compatibility**
- To support the latest Nx devkit API's for plugins
- **Refactored build process**
- `build` executor is now entirely based on the `@nrwl/js:tsc` executor, which simplifies maintenance of the plugin
- this also enables `--watch` to work
- _(note that changes to Nx library dependencies are still not yet detectable in `--watch` mode)_
- **Functions Node engine**
- Plugin now defaults to Node 16 runtime engine for firebase functions
- **Improved Firebase configurations**
- `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`
- **Project Alias Support**
- 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`
- **Additional dependencies**
- `nx g @simondotm/nx-firebase:app` will add firebase and [`kill-port`](https://www.npmjs.com/package/kill-port) dependencies
- `kill-port` is used by the `serve` and `emulate` targets to ensure clean startup.
- **Documentation has been updated**
Recommended minimum version of Nx is now 13.10.6. See [Nx Migration](docs/nx-migration.md) documentation for more information.
## v0.3.13  
## v0.3.4  
Interim release fixes for issues introduced in nx version 13.0.2+ where `createProjectGraph` was deprecated.
As of Nx v13.10.x, `copyAssets` was also broken in v0.3.4 of the plugin, but now fixed thanks to contributors.
**Migration Recommendations**
If you are running on older versions of Nx, the following information may be useful:
**No more `--with-deps`**
From Nx version 12.3+, Nx now automatically checks for & builds required dependencies when running build targets.
Therefore `--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.
**No more `--parallel`**
Same applies for `--parallel` since this is now a default setting in `nx.json`
It may be necessary to pass `--parallel=3` in CI scripts however.
**Nx command syntax**
It may also be worth updating commands within any firebase application targets of the format:
- `nx run project:build` to the newer
- `nx build project` syntax
**Update executors**
As of Nx 13.8.8, `@nrwl/node:package` is replaced by `@nrwl/js:tsc`.
Nx 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`.
See [Nx Migration](docs/nx-migration.md) documentation for more information.
## v0.3.3  
**General changes**
- Improved listing of firebase functions dependencies; now ordered by npm module libraries first, then local libraries, sorted alphabetically.
**Enhanced Support for Firebase Emulators**
`nx g @simondotm/nx-firebase:app` generator now additionally:
- Adds default `auth` and `pubsub` settings to `"emulators": {...}` config in `firebase.<appname>.json` so that these services are also emulated by default.
- Adds a new `getconfig` target to firebase functions app, where:
- `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`
- 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.
- Adds `.runtimeconfig.json` to the Nx workspace root `.gitignore` file (if not already added), since these files should not be version controlled
- 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.
**Plugin maintenance**
- Executors use workspace logger routines instead of console
- Fixed minor issues in e2e tests
- Removed redundant/legacy firebase target
- Replaced plugin use of node `join` with workspace `joinPathFragments`
**Migration from v0.3.2**
For 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.
In your `angular.json` or `workspace.json` file, for each `nx-firebase` app project:
1. Add the `.runtimeconfig.json` to your build assets:
```
"targets": {
"build": {
...
"options": {
...
"assets": [
...
"apps/nxfirebase-root-app/.runtimeconfig.json"
]
}
},
```
2. Add the new `emulate` target to your app:
```
"targets": {
...
"emulate": {
"executor": "@nrwl/workspace:run-commands",
"options": {
"command": "firebase emulators:start --config firebase.nxfirebase-root-app.json"
}
},
```
3. Modify the `serve` target to:
```
"targets": {
...
"serve": {
...
"options": {
"commands": [
{
"command": "nx run <appname>:build --with-deps && nx run <appname>:build --watch"
},
{
"command": "nx run <appname>:emulate"
}
],
"parallel": true
}
},
```
4. Add the new `getconfig` target:
```
"targets": {
...
"getconfig": {
"executor": "@nrwl/workspace:run-commands",
"options": {
"command": "firebase functions:config:get --config firebase.<appname>.json > apps/<path-to-app>/.runtimeconfig.json"
}
},
...
```
And in your `firebase.<appname>.json` config settings for `"emulators"` add `"auth"` and `"pubsub"` configs:
```
"emulators": {
...
"auth": {
"port": 9099
},
"pubsub": {
"port": 8085
}
}
```
## v0.3.2  
- Plugin now detects incompatible Nx library dependencies and aborts compilation when found
Incompatible dependencies are as follows:
1. Non `--buildable` libraries
2. Nested libraries that were not created with `--importPath`
If 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.
See the [README](README.md#using-nx-libraries-within-nested-sub-directories) for more information.
## v0.3.1  
- Removed undocumented/unusued `firebase` target in app generator. No longer needed.
- `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.
## v0.3.0  
Project 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!
If you have already generated NxFirebase applications using `@simondotm/nxfirebase` you will need to migrate as follows:
1. `npm uninstall @simondotm/nxfirebase`
2. `npm install @simondotm/nx-firebase --save-dev`
3. 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`
## v0.2.3  
Built against Nx 12.3.4
**Updates**
- `build` executor now supports `--watch` option for incremental builds
- 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
- Added `deploy` target to applications. Supports Nx forwarded command line arguments so commands like `nx deploy <appname> --only functions` work fine
- 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
**Fixes**
- 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
- 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)
- Fixed `predeploy` scripts in `firebase.appname.json` to use `npx nx` so that they work correctly in CI environments
- Fixed default `firestore.rules` file to correct a typo
- Fixed default `storage.rules` file to use version 2 ruleset
- Plugin peer dependencies set so there's some indication of plugin compatibility
**Migrating from apps generated with v0.2.2**
v0.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:
```
"serve": {
"builder": "@nrwl/workspace:run-commands",
"options": {
"commands": [
{
"command": "nx run <appname>:build --with-deps && nx run <appname>:build --watch"
},
{
"command": "firebase emulators:start --config firebase.<appname>.json"
}
],
"parallel": true
}
},
"deploy": {
"builder": "@nrwl/workspace:run-commands",
"options": {
"command": "firebase deploy --config firebase.<appname>.json"
}
},
```
## v0.2.2 - Initial Release  
Built against Nx 12.1.1
================================================
FILE: README.md
================================================
# @simondotm/nx-firebase   
A plugin for [Nx](https://nx.dev) that integrates Firebase workflows in an Nx monorepo workspace.
* Easily generate Firebase applications and functions
* Uses `esbuild` for fast Firebase function builds so you can easily create & import shared Nx libraries with the benefits of tree-shaking
* Supports function environment variables and secrets
* Supports single or multiple firebase projects/apps within an Nx workspace
* Full support for the Firebase Emulator suite for local development, with watch mode for functions
* Keeps your `firebase.json` configurations in sync when renaming or deleting Firebase apps & functions
* Only very lightly opinionated about your Firebase configurations and workspace layouts; you can use Nx or the Firebase CLI
See [CHANGELOG](https://github.com/simondotm/nx-firebase/blob/main/CHANGELOG.md) for release notes.
## Install Plugin
**`npm install @simondotm/nx-firebase --save-dev`**
- Installs this plugin into your Nx workspace
- This will also install `@nx/node` and firebase SDK's to your root workspace `package.json` if they are not already installed
## Generate Firebase Application
**`nx g @simondotm/nx-firebase:app my-new-firebase-app [--directory=dir] [--project=proj]`**
- Generates a new Nx Firebase application project in the workspace
- 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)
- For the first firebase application you create, the project firebase configuration will be `firebase.json`
- If you create additional firebase applications, the project firebase configuration will be `firebase.<app-project-name>.json`
- Use `--project` to link your Firebase App to a Firebase project name in your `.firebaserc` file
## Generate Firebase Function
**`nx g @simondotm/nx-firebase:function my-new-firebase-function --app=my-new-firebase-app [--directory=dir]`**
- Generates a new Nx Firebase function application project in the workspace
- Firebase Function projects must be linked to a Firebase application project with the `--app` option
- Firebase Function projects can contain one or more firebase functions
- You can generate as many Firebase Function projects as you need for your application
## Build
**`nx build my-new-firebase-app`**
- Compiles & builds all Firebase function applications linked to the Nx Firebase application or an individual function
**`nx build my-new-firebase-function`**
- Compiles & builds an individual function
## Serve
**`nx serve my-new-firebase-app`**
- Builds & Watches all Firebase functions apps linked to the Firebase application
- Starts the Firebase emulators
## Deploy
### Firebase Application
**`nx deploy my-new-firebase-app [--only ...]`**
- By default, deploys ALL of your cloud resources associated with your Firebase application (eg. sites, functions, database rules etc.)
- Use the `--only` option to selectively deploy (same as Firebase CLI)
For initial deployment:
- **`firebase login`** if not already authenticated
- **`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.
Note that you can also use the firebase CLI directly if you prefer:
- **`firebase deploy --config=firebase.<appname>.json --only functions`**
### Firebase Function
**`nx deploy my-new-firebase-function`**
- Deploys only a specific Firebase function
## Test
**`nx test my-new-firebase-app`**
- Runs unit tests for all Firebase functions apps linked to the Firebase application
**`nx test my-new-firebase-function`**
- Runs unit tests for an individual function
## Lint
**`nx lint my-new-firebase-app`**
- Runs linter for all Firebase functions apps linked to the Firebase application or an individual function
**`nx lint my-new-firebase-function`**
- Runs linter for an individual function
## Sync Workspace
**`nx g @simondotm/nx-firebase:sync`**
- Ensures that your `firebase.json` configurations are kept up to date with your workspace
- If you rename or move firebase application or firebase function projects
- If you delete firebase function projects
## Further Information
See the full plugin [User Guide](https://github.com/simondotm/nx-firebase/blob/main/docs/user-guide.md) for more details.
================================================
FILE: docs/nx-firebase-applications.md
================================================
# Nx Firebase Applications
An 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_.
You don't have to use all of these features, but the Nx-Firebase plugin ensures they are all there if/when you do.
Generate a new Firebase application using:
**`nx g @simondotm/nx-firebase:application`**
OR
**`nx g @simondotm/nx-firebase:app`**
| Options | Type | Description |
| ----------------- | -------- | ------------------------------------------------------------------ |
| `name` | required | the project name for your firebase app |
| `--directory=dir` | optional | the full path where this app will be located (e.g., `apps/my-app`) |
| `--project=proj` | optional | the `--project` option that will be used for firebase CLI commands |
## About Firebase Apps
Firebase app projects are a customised Nx project.
When a new Nx Firebase application project is generated in the workspace it will create:
**Within the application folder:**
- Default `firestore.indexes` for Firestore database indexes
- Default `firestore.rules` for Firestore database rules
- Default `database.rules.json` for Firebase realtime database
- Default `storage.rules` for Firebase storage rules
- Default `public/index.html` for Firebase hosting - _you can delete this if your firebase configuration for hosting points elsewhere_.
- Default `public/404.html` for Firebase hosting - _you can delete this if your firebase configuration for hosting points elsewhere_.
- Default [environment variables](./nx-firebase-functions-environment.md) for your firebase functions
**And in the workspace root:**
- A `firebase.json` configuration file for the Firebase application
- This is preset with references to the various configuration files in the application folder
- 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`
**It will also generate:**
- A default/empty `.firebaserc` in the root of the workspace (if it doesn't already exist)
You should use `npx firebase --add` to register your [projects & aliases](nx-firebase-projects.md) in the `.firebaserc`.
## Nx-Firebase Application Project Targets
These targets will be generated in `project.json` for your new Firebase application:
- `build` - Build all Firebase function applications linked to this Firebase application (if any)
- `serve` - Build all functions in `watch` mode and start the Firebase Emulators
- `deploy` - Run the Firebase CLI `deploy` command with the application's Firebase configuration. This target accepts forwarded command line options.
- `lint` - Lint all Firebase function applications linked to this Firebase application
- `test` - Run Jest unit tests for all Firebase function applications linked to this Firebase application
- `getconfig` - Fetch the firebase remote config
- `firebase` - Run the firebase CLI with the appropriate firebase `--config` and firebase `--project` parameters automatically provided
================================================
FILE: docs/nx-firebase-databases.md
================================================
# Firebase Databases
`nx-firebase` assumes you may wish to use either realtime and/or firestore database.
## Rules & Indexes
To 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.
Again, this works just fine with the usual Firebase CLI command, eg:
**`firebase deploy --only firestore:rules --config firebase.appname.json`**
Or
**`nx deploy appname --only firestore:rules`**
This is also useful for cleaner separation if you have multiple Firebase projects in your Nx workspace.
Again, 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.
================================================
FILE: docs/nx-firebase-emulators.md
================================================
# Using Firebase Emulators
The Firebase emulators work well within Nx and `nx-firebase`.
To locally develop with the Firebase emulator suite:
- **`nx serve <firebase-app-name>`**
This will launch the Firebase emulators using the app's firebase configuration
It 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.
## Nx Issue with Firebase Emulator
Due 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.
For this reason, `serve` uses the `kill-port` npm package to ensure the emulator is properly shutdown before re-running `serve`.
Unfortunately this means the emulator will not properly export data on exit either.
The Nx issue is discussed [here](https://github.com/simondotm/nx-firebase/issues/40).
Hopefully Nx will address this issue in future releases.
## Emulator workaround
Until Nx fix this problem, we are using an experimental executor in the plugin as a interim workaround:
The `serve` target in Firebase app `project.json` configurations is using:
- `@simondotm/nx-firebase:serve`
instead of
- `nx:run-commands`
This custom executor handles CTRL+C in a way that ensures the Firebase Emulator shuts down cleanly.
With this executor you can pass extra CLI parameters for example:
- `nx serve myfirebaseapp --only functions` - only serve functions in the Emulator
================================================
FILE: docs/nx-firebase-functions-environment.md
================================================
# Firebase Function Environment Variables
## Overview
Firebase functions can make use of environment variables in their runtime.
The Nx-firebase [application generator](./nx-firebase-applications.md) will automatically create default function environment files for you to use:
- `environment/.env`
- `environment/.env.local`
- `environment/.secret.local`
If you run the `getconfig` target, it will place `.runtimeconfig.json` file in the `environment` folder also.
These 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.
This ensures they are available for deployment and emulation.
All functions share the same environment variable files.
## Environment file types
| File | Description | Git Ignored | Deployed |
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -------- |
| `.env` | [General environment variables for functions](https://firebase.google.com/docs/functions/config-env?gen=2nd#env-variables) | - | Yes |
| `.env.local` | [Environment variable overrides for function emulation](https://firebase.google.com/docs/functions/config-env?gen=2nd#emulator_support) | - | - |
| `.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 |
| `.secret.local` | [Secrets only for function emulation](https://firebase.google.com/docs/functions/config-env?gen=2nd#secrets_and_credentials_in_the_emulator) | Yes | - |
| `.runtimeconfig.json` | [Function configurations](https://firebase.google.com/docs/cli#functions-commands) | Yes | - |
> _Note that the Firebase team appear to be deprecating the use of `.runtimeconfig.json` function configs and recommending migration to dotenv environment variables._
## Deployed Files
The firebase CLI will deploy `.env` and/or `.env.<project-id>` files along with function code, and they can be version controlled.
## Non-deployed files
`.env.local` and `.secret.local` files are excluded from deployment by using an `functions.ignore` rule in `firebase.json`.
## Local Files
`.secret.local` and `.env.local` are only used by the Firebase emulator suite.
`.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.
================================================
FILE: docs/nx-firebase-functions.md
================================================
# Firebase Functions
- [Firebase Functions](#firebase-functions)
- [Nx-Firebase Functions](#nx-firebase-functions)
- [Generating a new function](#generating-a-new-function)
- [Building a Firebase function](#building-a-firebase-function)
- [Firebase Function Dependencies \& Package Managers](#firebase-function-dependencies--package-managers)
- [Deploying a Firebase function](#deploying-a-firebase-function)
- [Testing \& Linting your firebase function](#testing--linting-your-firebase-function)
- [Managing Functions in your Workspace](#managing-functions-in-your-workspace)
- [Functions \& Nx-Firebase Applications](#functions--nx-firebase-applications)
- [How Nx-Firebase function apps are linked to Nx-Firebase apps](#how-nx-firebase-function-apps-are-linked-to-nx-firebase-apps)
- [Functions \& Firebase Config Codebases](#functions--firebase-config-codebases)
- [Functions \& ESBuild](#functions--esbuild)
- [Using ES Modules output](#using-es-modules-output)
- [Using CommonJS output](#using-commonjs-output)
- [Why ESBuild?](#why-esbuild)
- [Why not minify?](#why-not-minify)
- [Node Runtimes for Firebase Functions](#node-runtimes-for-firebase-functions)
## Nx-Firebase Functions
Nx-Firebase functions are generated as individual Nx application projects, separately to the Nx-Firebase application.
* 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.
* 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.
* Each function project is a buildable Typescript node-based application, which is compiled and bundled using [esbuild](#functions--esbuild).
* Each function project can export one or more firebase cloud functions.
* 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.
* 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.
* 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.
* 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
[package dependencies](#firebase-function-dependencies-package-managers)
* Functions support deployment with `npm`, `pnpm` or `yarn` package managers.
## Generating a new function
Generate a new Firebase function using:
- **`nx g @simondotm/nx-firebase:function`**
OR
- **`nx g @simondotm/nx-firebase:func`**
| Options | Type | Description |
| --------------------------- | ------------- | ---------------------------------------------------------- |
| `name` | required | the project name for your function |
| `--app=<app-project-name>` | required | the firebase app this function will be a dependency of |
| `--directory=dir` | optional | the full path where this function will be located (e.g., `apps/my-function`) |
| `--format=<'cjs' or 'esm'>` | default 'esm' | specify if esbuild should generated commonJs or ES6 output |
| `--runTime=<node versions>` | optional | the nodejs runtime you wish to use for this function - 18, 20, 22 |
| `--tags` | optional | tags to set on the new project |
| `--setParserOptionsProject` | optional | set the parserOptions.project in the tsconfig.json file |
## Building a Firebase function
To build your firebase function use this command:
- **`nx build your-firebase-function-project-name`**
This will use `esbuild` to compile & bundle the input function Typescript source code to:
- `dist/apps/your-firebase-function-project-name/main.js` - The bundled function code, in a single ESM format output file
- `dist/apps/your-firebase-function-project-name/package.json` - The ESM format package file for firebase CLI to process and deploy
## Firebase Function Dependencies & Package Managers
When 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.
Nx 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.
## Deploying a Firebase function
To deploy all of your firebase function projects use:
- **`nx deploy your-firebase-app-name --only:functions`**
To deploy one of your firebase function projects use this command:
- **`nx deploy your-firebase-function-name`**
To deploy a single function from within a firebase function project that exports multiple functions, use this command:
- **`nx deploy <codebase> --only functions:<codebase>:function-name`**
Where `<codebase>` is whatever name you gave your nx-firebase:function project.
> **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:**
```
import { initializeApp } from "firebase-admin/app";
initializeApp()
```
## Testing & Linting your firebase function
- **`nx test your-firebase-function-name`**
- **`nx lint your-firebase-function-name`**
## Managing Functions in your Workspace
You can use the parent Firebase app to build, test, lint, watch and deploy all of your functions at once.
- **`nx build your-firebase-app-name`**
- **`nx test your-firebase-app-name`**
- **`nx lint your-firebase-app-name`**
- **`nx watch your-firebase-app-name`**
- **`nx deploy your-firebase-app-name`**
Note 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:
- **`nx serve your-firebase-app-name`**
See [Firebase Application Targets](./nx-firebase-applications.md#nx-firebase-application-project-targets) for more details.
## Functions & Nx-Firebase Applications
### How Nx-Firebase function apps are linked to Nx-Firebase apps
The Nx-firebase plugin requires that Firebase function projects must always be a dependency of a single Firebase application project:
- This approach allows for multiple firebase projects in a single Nx workspace
- It ensures all functions can be [managed by the nx-firebase plugin](./nx-firebase-sync.md)
- 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
- All functions share the same Firebase `--config` CLI option as the parent Firebase Application
- All functions share the same Firebase `--project` CLI option as the parent Firebase Application
- You can create as many Firebase function projects as you like
- Firebase function apps can export either just one or multiple firebase cloud functions
- When running the Firebase emulator using `serve`, **all** firebase function applications are built using `watch` mode, so local development is much more convenient
### Functions & Firebase Config Codebases
When new Firebase function applications are generated in the workspace:
- They are automatically added to the `functions[]` declaration in the project's `firebase.json` config file using the firebase CLI's `codebase` feature
- The `codebase` name assigned to the function in the config is the function applications project name.
- When using firebase `deploy`, the CLI will deploy all `codebase`'s declared in the firebase config file
### Functions & ESBuild
`esbuild` is configured in the function's `project.json` to only bundle 'internal' source local to the workspace:
- Import paths using TS aliases to `@nx/js` libraries will be resolved as internal imports.
- 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.
### Using ES Modules output
`esbuild` is also configured by default to always output bundled code as `esm` format modules:
- This ensures tree-shaking is activated in the bundling process
- Firebase functions with Node 16 or higher runtime all support ES modules
- The bundled output code in `dist` is _much_ cleaner to review
- 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
- **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.**
### Using CommonJS output
If you still use Node `require()` in your Typescript function code, the default `esm` output setting for `esbuild` may not work. Your options are:
1. Refactor your code to use `import` instead of `require`
2. Modify the function `project.json` to set esbuild `format` to `['cjs']`
3. Generate your function applications with the `--format=cjs` option
Note that using `cjs` output may prevent tree-shaking optimizations.
### Why ESBuild?
While Webpack and Rollup are viable options for bundling node applications:
- `esbuild` is designed for node,
- it is _very_ fast
- it optimizes the output using tree-shaking, which is great for fast cold starts
- and it works very simply out of the box with Nx without any need for additional configuration files.
If you want to try Webpack or Rollup, just change your `build` target in the function's `project.json` accordingly.
> The Nx Webpack bundler may be required for projects that require Typescript decorators such as NextJS.
### Why not minify?
This plugin does not set or recommend the minify option for esbuild.
- It is not really necessary for cloud function node runtime environments
- Obfuscation of the code is not necessary since it executes in a private server-side environment
- There is minimal (if any) performance benefit to be had
- If exceptions occur in the cloud run, stack traces will be readable if code is not minified
## Node Runtimes for Firebase Functions
Firebase Functions are deployed by the Firebase CLI to specific Nodejs runtime environments.
The 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.):
```
"functions": [
{
"codebase": "firebase-project1",
"runtime": "nodejs16",
...
}
],
```
Runtimes are recommended to be set to the same value for all functions in a project.
================================================
FILE: docs/nx-firebase-hosting.md
================================================
# Firebase Hosting
If 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.
Then 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.
You can then run the Firebase CLI as usual to deploy the site:
**`firebase deploy --only hosting --config firebase.appname.json`**
Or
**`nx deploy appname --only hosting`**
## Static Sites
If 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.
The firebase CLI hosting deploy command above will just upload the static content as required.
An application generated by Nx-Firebase is by default configured to host content in the `apps/appname/public` directory.
================================================
FILE: docs/nx-firebase-migrations.md
================================================
# Nx-Firebase Plugin Migrations
Newer versions of the plugin occasionally need to update the workspace configurations, and this page documents the strategies available across these versions.
Please note that these migrations are provided on a 'best effort' basis, due to the fact that workspaces are quite complex and often customised.
- [Nx-Firebase Plugin Migrations](#nx-firebase-plugin-migrations)
- [Migrating to plugin v2.1 from v2.0](#migrating-to-plugin-v21-from-v20)
- [Migration to plugin v2.x from v1.x](#migration-to-plugin-v2x-from-v1x)
- [1. Workspace Migration](#1-workspace-migration)
- [2. Firebase Project Migration](#2-firebase-project-migration)
- [3. Firebase Application Migration](#3-firebase-application-migration)
- [4. Firebase Functions Migration](#4-firebase-functions-migration)
- [5. Library updates](#5-library-updates)
- [6. Check Migration](#6-check-migration)
## Migrating from plugin v2.0.0 to v2.1.0
Plugin version 2.1 [added some new features](../CHANGELOG.md#v210) that required changes to the project configurations.
To help with this & future updates, an automatic migration generator has been added:
- Update to the latest plugin using `npm i @simondotm/nx-firebase@latest --save-dev`
- Run **npx nx g @simondotm/nx-firebase:migrate**
This 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.
> 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.
## Migration from plugin v1.x to v2.0.0
Version 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.
### 1. Workspace Migration
- First of all, your workspace will need to be migrated to at least Nx 16.1.1.
- Next, update the `@simondotm/nx-firebase` plugin package to the latest v2.x version.
### 2. Firebase Project Migration
Run the following steps 3-5 in order, for each separate Firebase application project in your workspace.
### 3. Firebase Application Migration
- 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
OR
- you can manually modify your existing Firebase `project.json` to be structured [as shown here](./nx-firebase-project-structure.md#firebase-applications).
### 4. Firebase Functions Migration
If you are using Firebase functions in your project, the migration process is as follows:
- 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": []`
- 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
- Move the source code from your old Nx Firebase application `functions/src` folder to the `src` folder in the newly created Firebase function application
- You will need to rename your entry point source file from `index.ts` to `main.ts`
- If you migrated your Firebase application in place (as described above), you will need to manually delete the following from your Firebase application folder:
- `src` folder
- All `tsconfig.*.json` files
- `package.json` file
- OR, if you migrated by creating a new firebase application, you can now simply delete the old v1 Firebase application project
Check the [Nx-Firebase project schemas](./nx-firebase-project-structure.md) document for more information about the v2 plugin generators project layouts.
### 5. Library updates
The previous version of the plugin required that all Nx libraries imported by firebase functions were buildable.
With 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.
Nx Typescript libraries can be converted to non-buildable by simply removing the `build` target from their `project.json` files.
### 6. Check Migration
Run `nx build your-firebase-project-name` to compile & bundle your functions.
Run `nx deploy your-firebase-project-name` to deploy your project.
================================================
FILE: docs/nx-firebase-project-structure.md
================================================
# Nx-Firebase schemas (plugin v2)
- [Nx-Firebase schemas (plugin v2)](#nx-firebase-schemas-plugin-v2)
- [Overview](#overview)
- [Firebase Application Projects](#firebase-application-projects)
- [Firebase Function Projects](#firebase-function-projects)
- [Firebase Function Configs](#firebase-function-configs)
## Overview
Whilst 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.
The `project.json` and `firebase.json` config schemas are below.
## Firebase Application Projects
Firebase Application projects are a useful way to group firebase resources and provide common functionality such as deploy, or build.
We add all function app projects as implicit dependencies, so that building the app project will automatically build function dependents.
We tag all dependent projects so that we can use Nx `run-many` with tag specifiers for buid actions like `watch`, `test` and `lint`.
```
{
"name": "your-firebase-app-project-name",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/your-firebase-app-project-name",
"projectType": "application",
"implicitDependencies": [
"your-firebase-function-project-name",
],
"targets": {
"build": {
"executor": "nx:run-commands",
"options": {
"command": "echo Build succeeded."
}
},
"watch": {
"executor": "nx:run-commands",
"options": {
"command": "nx run-many --targets=build --projects=tag:firebase:dep:your-firebase-app-project-name --parallel=100 --watch"
}
},
"lint": {
"executor": "nx:run-commands",
"options": {
"command": "nx run-many --targets=lint --projects=tag:firebase:dep:your-firebase-app-project-name --parallel=100"
}
},
"test": {
"executor": "nx:run-commands",
"options": {
"command": "nx run-many --targets=test --projects=tag:firebase:dep:your-firebase-app-project-name --parallel=100"
}
},
"firebase": {
"executor": "nx:run-commands",
"options": {
"command": "firebase --config=<your-firebase-project-config> --project=<your-firebase-projectid>"
},
"configurations": {
"production": {
"command": "firebase --config=<your-firebase-project-config> --project=<your-firebase-production-projectid>"
}
}
},
"killports": {
"executor": "nx:run-commands",
"options": {
"command": "kill-port --port 9099,5001,8080,9000,5000,8085,9199,9299,4000,4400,4500"
}
},
"getconfig": {
"executor": "nx:run-commands",
"options": {
"command": "nx run your-firebase-app-project-name:firebase functions:config:get > apps/your-firebase-app-project-name/environment/.runtimeconfig.json"
}
},
"emulate": {
"executor": "nx:run-commands",
"options": {
"commands": [
"nx run your-firebase-app-project-name:killports",
"nx run your-firebase-app-project-name:firebase emulators:start --import=apps/your-firebase-app-project-name/.emulators --export-on-exit"
],
"parallel": false
}
},
"serve": {
"executor": "@simondotm/nx-firebase:serve",
"options": {
"commands": [
"nx run your-firebase-app-project-name:watch",
"nx run your-firebase-app-project-name:emulate"
]
}
},
"deploy": {
"executor": "nx:run-commands",
"dependsOn": [
"build"
],
"options": {
"command": "nx run your-firebase-app-project-name:firebase deploy"
}
}
},
"tags": [
"firebase:app",
"firebase:name:your-firebase-app-project-name"
]
}
```
## Firebase Function Projects
Function projects can export as many firebase functions as you like.
Functions use `esbuild` to compile & bundle the code.
```
{
"name": "your-firebase-function-project-name",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/your-firebase-function-project-name/src",
"projectType": "application",
"targets": {
"build": {
"executor": "@nx/esbuild:esbuild",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/apps/your-firebase-function-project-name",
"main": "apps/your-firebase-function-project-name/src/main.ts",
"tsConfig": "apps/your-firebase-function-project-name/tsconfig.app.json",
"assets": [
"apps/your-firebase-function-project-name/src/assets",
{ "glob": "**/*", "input": "apps/your-firebase-app-project-name/environment", "output": "."},
],
"generatePackageJson": true,
"platform": "node",
"bundle": true,
"thirdParty": false,
"dependenciesFieldType": "dependencies",
"target": "node16",
"format": ["esm"],
"esbuildOptions": {
"logLevel": "info"
}
}
},
"lint": {
"executor": "@nx/eslint:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["apps/your-firebase-function-project-name/**/*.ts"]
}
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "apps/your-firebase-function-project-name/jest.config.ts",
"passWithNoTests": true
},
"configurations": {
"ci": {
"ci": true,
"codeCoverage": true
}
}
},
"deploy": {
"executor": "nx:run-commands",
"options": {
"command": "nx run your-firebase-app-project-name:deploy --only functions:your-firebase-function-project-name"
},
"dependsOn": ["build"]
}
},
"tags": [
"firebase:function",
"firebase:name:your-firebase-function-project-name",
"firebase:dep:your-firebase-app-project-name"
]
}
```
## Firebase Function Configs
We 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.
For each function project, `firebase.json` will need the following entries in the `functions` config array.
```
functions: [
{
"codebase": "your-function-project-name",
"source": "dist/apps/your-function-project-name",
"runtime": "nodejs16",
"ignore": [ "*.local" ]
}
]
```
================================================
FILE: docs/nx-firebase-projects.md
================================================
# Firebase CLI Projects
- [Firebase CLI Projects](#firebase-cli-projects)
- [Introduction](#introduction)
- [Nx Workspaces With Single Firebase Projects](#nx-workspaces-with-single-firebase-projects)
- [Nx Workspaces With Multiple Firebase Projects](#nx-workspaces-with-multiple-firebase-projects)
- [Updating Firebase Configurations](#updating-firebase-configurations)
- [Binding Nx projects to Firebase projects](#binding-nx-projects-to-firebase-projects)
- [Deployment Environments](#deployment-environments)
## Introduction
[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.
`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).
## Nx Workspaces With Single Firebase Projects
The 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.
The Firebase CLI will use this configuration file by default, and in this scenario there's no need to pass the additional `--config` CLI option.
## Nx Workspaces With Multiple Firebase Projects
This plugin supports multiple Firebase Applications/Projects inside one Nx workspace.
If 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).
## Updating Firebase Configurations
Once your Nx Firebase application has been initially generated you are free to change the firebase configurations however you like.
The Firebase CLI usually warns you anyway if you try to deploy a feature that isn't yet enabled on your Firebase Project console.
## Binding Nx projects to Firebase projects
When using multiple Firebase projects in a workspace, remember that there is only one `.firebaserc` file to contain aliases for all of your deployment targets.
You can add projects using `firebase use --add` as normal.
It'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!
You 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.
See also: [Changing Firebase CLI Project](./nx-firebase-sync.md#changing-firebase-cli-project)
## Deployment Environments
A common practice with Firebase is to generate different Firebase projects for each deployment environments (such as dev / staging / production etc.)
This can be manually achieved with `nx-firebase` by adding `production` configurations to the `firebase` target in your Nx firebase application `project.json` file eg.:
```
firebase: {
executor: 'nx:run-commands',
options: {
command: `firebase --config firebase.json --project your-dev-firebase-project`,
},
configurations: {
production: {
command: `firebase --config firebase.json --project your-prod-firebase-project`,
},
},
},
```
You can now run:
- `nx deploy my-firebase-app` for dev deployment
- `nx deploy my-firebase-app --prod` for production deployment
Note 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.
================================================
FILE: docs/nx-firebase-sync.md
================================================
# Firebase Sync
- [Firebase Sync](#firebase-sync)
- [Nx-Firebase Sync Generator](#nx-firebase-sync-generator)
- [Renaming Nx-Firebase Projects](#renaming-nx-firebase-projects)
- [Renamed firebase application projects](#renamed-firebase-application-projects)
- [Renamed firebase function projects](#renamed-firebase-function-projects)
- [Deleting Nx-Firebase Projects](#deleting-nx-firebase-projects)
- [Deleted firebase applications](#deleted-firebase-applications)
- [Deleted firebase functions](#deleted-firebase-functions)
- [Changing Firebase CLI Project](#changing-firebase-cli-project)
- [Nx-Firebase Project Tags Reference](#nx-firebase-project-tags-reference)
- [Tag Descriptions](#tag-descriptions)
## Nx-Firebase Sync Generator
Within an Nx workspace, Nx-firebase supports:
- Multiple firebase application projects
- Multiple firebase function projects attached to the firebase applications
Deleting or renaming any of these projects requires various project & firebase configurations to be updated to ensure that targets & deployments still work.
To help manage these scenarios, Nx-firebase has a `sync` generator to automate this work:
**`nx g @simondotm/nx-firebase:sync`**
Run this command as soon as possible after any of these operations to ensure your Nx Firebase workspace is kept in sync:
- When Firebase application or function projects have been renamed
- When Firebase application or function projects have been deleted
The 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.
You can also use the `sync` generator to [change the Firebase project](#changing-firebase-cli-project) for an Nx Firebase App.
## Renaming Nx-Firebase Projects
Nx projects such as Firebase apps and Firebase function apps can be renamed using the `nx g move` generator.
Run the `nx g @simondotm/nx-firebase:sync` generator after the rename.
### Renamed firebase application projects
- 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
- 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.
- The `firebase:name:<old-project-name>` tag on the Firebase application's `project.json` will be updated to `firebase:name:<new-project-name>`
- Paths to rules and indexes for Firebase database, storage and firestore are updated in the `firebase.json` config
### Renamed firebase function projects
- Nx automatically updates `implicitDependencies` for renamed dependency projects (Firebase function apps are dependencies of Firebase applications)
- The `codebase` for the function in the firebase config will be renamed to match the new function name
- The `firebase:name:<old-project-name>` tag on the Firebase function's `project.json` will be updated to `firebase:name:<new-project-name>`
## Deleting Nx-Firebase Projects
Nx projects such as Firebase apps and Firebase function apps can be deleted using the `nx g remove` generator.
Run the `nx g @simondotm/nx-firebase:sync` generator after the deletion.
> 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.
### Deleted firebase applications
- The Firebase config file linked to the deleted Firebase application project will be deleted
- Any Functions that were dependencies of the deleted firebase app will be reported as orphaned
- Orphaned functions can be either deleted, or they can be attached to another Firebase application project by:
- Changing the `firebase:dep:<old-project>` tag to `firebase:dep:<new-project>`
- Adding `<new-project>` to the `implicitDependencies` array in the new firebase app `project.json` file
### Deleted firebase functions
- Nx automatically updates `implicitDependencies` for deleted dependency projects (Firebase function apps are dependencies of Firebase applications)
- The deleted firebase function will also be deleted from the `firebase.json` config file
## Changing Firebase CLI Project
Nx 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`).
You 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:
**`nx g @simondotm/nx-firebase:sync --app=<firebase-app-project-name> --project=<firebase-cli-project-name>`**
All firebase commands used by the Nx project targets such as `deploy` will now send `--project=<firebase-project-name>` to the firebase CLI.
## Nx-Firebase Project Tags Reference
Various 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.
### Tag Descriptions
- `firebase:app` - All firebase application projects have this tag
- `firebase:function` - All firebase function projects have this tag
- `firebase:name:<project-name>` - All firebase application & function projects have this tag
- `firebase:dep:<app-project-name>` - All firebase function projects have this tag
More details:
- 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.
- The `firebase:name` tag allows the `sync` generator to detect when projects are renamed.
- The `firebase:app` and `firebase:function` tags simply allow us to easily identify Firebase projects in the workspace.
These tags work alongside any additional tags you might want to add to your Nx projects.
================================================
FILE: docs/nx-libraries.md
================================================
# Using Nx Libraries with Firebase Functions
Nx-Firebase supports use of Nx Libraries within functions code.
## Creating a library
To use a shared library with an Nx Firebase Function Application, simply create a Typescript Nx node library in your workspace:
**`nx g @nx/js:lib mylib --importPath="@myorg/mylib [--bundler=<bundler>]`**
> _Note: The `--importPath` option is highly recommended to ensure the correct typescript aliases and npm package configurations for your library._
## Importing a library
You can now:
`import { stuff } from '@myorg/mynodelib'`
in your Firebase functions code as you'd normally expect.
## Building with libraries
You can then build your Firebase application or function with:
**`nx build <firebase-app-name>`**
OR
**`nx build <firebase-function-name>`**
## Nx Library Notes
[Firebase functions](./nx-firebase-functions.md) use `esbuild` to bundle function code, we gain a few benefits:
- Nx takes care of ensuring all necessary dependencies will be also built.
- 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.
- We do not have to worry about how we structure libraries anymore for optimal function runtime.
- For instance, we can use barrel imports freely, since `esbuild` will treeshake unused code and inline imports into the bundled output `main.js`
- 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
- For cleaner code sharing, Firebase function applications can simply import a library module containing the firebase function export/implementation
================================================
FILE: docs/nx-migration.md
================================================
# Migrating an existing Firebase project to Nx
To 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..
See also [Nx Versions](nx-versions.md) for further information on specific Nx version migrations.
For your Firebase functions, [generate a function application](./nx-firebase-functions.md) and copy your existing source code to this new project `src` directory.
From here, you can simply `nx build` and `nx deploy` your firebase application & functions.
## Firebase SDK versions
Which 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.
The `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.
================================================
FILE: docs/nx-plugin-commands.md
================================================
# Nx Plugin Development
Notes mainly for my own benefit/reminder here.
## To create the plugin workspace
- `npx create-nx-plugin simondotm --pluginName nx-firebase`
## To build the plugin
- `nx run nx-firebase:build`
## To test the plugin
- `nx run nx-firebase:test`
## To run end-to-end tests for the plugin
- `nx run nx-firebase-e2e:e2e`
- This creates a temporary workspace in `/tmp/nx-e2e/...`
- After the e2e test has completed, the plugin can be further manually tested in this temporary workspace.
## To reformat the project
For example, after changing `.prettierrc` settings
- `nx format:write --all`
**Unit Tests**
I've not implemented a full set of unit tests yet, but the e2e tests do perform a few standard tests.
> **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!
================================================
FILE: docs/nx-setup-mac.md
================================================
# Nx Development Setup Guide (Mac)
Clean installation steps for the whole setup of node/npm/nvm/nx, assuming mac with `zsh` shell.
Recommendations:
- Remove any pre-existing installations of node/npm/nvm/nx for a clean setup
- 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
## 1. Install `antigen`
- Install [antigen](https://github.com/zsh-users/antigen) via [brew](https://formulae.brew.sh/formula/antigen) - `brew install antigen`
- Add `antigen` to your `~/.zshrc` file:
```
source $(brew --prefix)/share/antigen/antigen.zsh
```
- Restart your shell/terminal
- 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
## 2. Install `nvm` using the `zsh-nvm` plugin
- Use the excellent [zsh-nvm plugin](https://github.com/lukechilds/zsh-nvm) for nvm to make life easier (do not install `nvm` using brew)
- Add the following to your `~/.zshrc` file:
```
export NVM_DIR="$HOME/.nvm"
antigen bundle lukechilds/zsh-nvm
antigen apply
```
- Add any extra `zsh-nvm` options to your `~/.zshrc` file, such as:
```
export NVM_LAZY_LOAD=true
export NVM_AUTO_USE=true
```
- The finished `~/.zshrc` file looks something like this:
```
source $(brew --prefix)/share/antigen/antigen.zsh
export NVM_DIR="$HOME/.nvm"
export NVM_LAZY_LOAD=true
export NVM_AUTO_USE=true
antigen bundle lukechilds/zsh-nvm
antigen apply
```
- Restart your shell/terminal and verify installation using the `nvm --version`
- Upgrade `nvm` using `nvm upgrade`
## 3. Install `node`
- `nvm install <nodeversion>` eg. `nvm install 19.1.0`
- This will install node and a compatible version of `npm`/`npx` to `~/.nvm/versions/node/19.1.0/...`
- `echo $path` should now show `~/.nvm/versions/node/19.1.0/bin`
- `node`, `npm` and `npx` should now be accessible from the terminal/shell prompt
## 4. Installing multiple `node` versions (optional)
- Multiple node versions can be installed with `nvm install x.y.z`
- `nvm install node` will install the latest version
- Switch between node versions using `nvm use <version>`
- If your project requires a specific node version, add a `.nvmrc` file to the root of your project containing the text version required
- 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
- `nvm list` will show all installed versions
- `nvm current` will show the currently selected version
- `nvm default <version>` will select a version that you like to be the default when you run `nvm use default`
## 5. Install `nx`
- Select the node version you want to install `nx` to: `nvm use <version>`
- `npm i -g nx`
- The previous command ensures that `nx` command line can be run without `npx`
- Not sure when this became a thing, nor sure if the nx cli is still required as a global install, but it works.
- `nx` should now be accessible from the terminal/shell prompt
## 6. Visual Studio Code
Recommended extensions for Nx development:
- [VS Code ESLint extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)
- [Prettier Formatter for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
- [Nx Console](https://marketplace.visualstudio.com/items?itemName=nrwl.angular-console)
- [vscode-jest-runner](https://marketplace.visualstudio.com/items?itemName=firsttris.vscode-jest-runner)
================================================
FILE: docs/nx-workspace-layout.md
================================================
# Nx-Firebase Workspace Layout
Firebase applications and functions can be generated in whichever directories you like.
While there are plenty of ways to organise your workspace layout, one suggestion is:
```
/apps
/project1
/firebase
/functions
/function1
/function2
/web
/site1-app
/site2-app
/mobile
/app
/project2
/firebase
/functions
/web
...
firebase.rc
firebase.json
firebase.project2.json
...
```
## Organising functions
Since [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.
1. Just have one function Nx app project that exports all of the cloud functions you need
2. Have multiple Nx function app projects which group and export multiple cloud functions by common functionality
3. Have one Nx function app project per cloud function
Option 1 is the simplest approach.
However, 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).
Firebase 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.
================================================
FILE: docs/user-guide.md
================================================
# User Guide
**Nx Firebase Generators**
- [Firebase Applications](./nx-firebase-applications.md)
- [Firebase Functions](./nx-firebase-functions.md)
- [Firebase Functions - Environment Variables](./nx-firebase-functions-environment.md)
**Nx Firebase**
- [Firebase Hosting](./nx-firebase-hosting.md)
- [Firebase Emulators](./nx-firebase-emulators.md)
- [Firebase Databases](./nx-firebase-databases.md)
- [Firebase Projects](./nx-firebase-projects.md)
**Nx Firebase Workspace Management**
- [Nx-Firebase Sync](./nx-firebase-sync.md)
- [Nx-Firebase Project Schemas](./nx-firebase-project-structure.md)
- [Migrating to new plugin versions](./nx-firebase-migrations.md)
**Nx Workspace**
- [Nx Workspace Layout Ideas](./nx-workspace-layout.md)
- [Using Nx Libraries with Firebase Functions](./nx-libraries.md)
- [Migrating an existing Firebase project to Nx](./nx-migration.md)
**Notes**
- [Plugin Development Notes](./nx-plugin-commands.md)
- [Nx Development Setup for Mac](./nx-setup-mac.md)
================================================
FILE: e2e/compat/eslint.config.mjs
================================================
import baseConfig from '../../eslint.config.mjs'
export default [
{
ignores: ['**/dist'],
},
...baseConfig,
{
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
// Override or add rules here
rules: {},
languageOptions: {
parserOptions: {
project: ['e2e/compat/tsconfig.*?.json'],
},
},
},
{
files: ['**/*.ts', '**/*.tsx'],
// Override or add rules here
rules: {},
},
{
files: ['**/*.js', '**/*.jsx'],
// Override or add rules here
rules: {},
},
]
================================================
FILE: e2e/compat/jest.config.ts
================================================
/* eslint-disable */
export default {
displayName: 'compat',
preset: '../../jest.preset.js',
globals: {},
testEnvironment: 'node',
transform: {
'^.+\\.[tj]s$': [
'ts-jest',
{
tsconfig: '<rootDir>/tsconfig.spec.json',
},
],
},
moduleFileExtensions: ['ts', 'js', 'html'],
}
================================================
FILE: e2e/compat/project.json
================================================
{
"name": "compat",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "e2e/compat/src",
"projectType": "application",
"tags": [],
"targets": {
"build": {
"executor": "@nx/webpack:webpack",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/e2e/compat",
"main": "e2e/compat/src/main.ts",
"tsConfig": "e2e/compat/tsconfig.app.json",
"assets": ["e2e/compat/src/assets"],
"target": "node",
"compiler": "tsc",
"webpackConfig": "e2e/compat/webpack.config.js"
},
"configurations": {
"production": {
"optimization": true,
"extractLicenses": true,
"inspect": false,
"fileReplacements": [
{
"replace": "e2e/compat/src/environments/environment.ts",
"with": "e2e/compat/src/environments/environment.prod.ts"
}
]
}
}
},
"serve": {
"executor": "@nx/js:node",
"options": {
"buildTarget": "compat:build"
}
},
"lint": {
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"]
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/e2e/compat"],
"options": {
"jestConfig": "e2e/compat/jest.config.ts"
}
}
}
}
================================================
FILE: e2e/compat/src/app/config.ts
================================================
import { defaultCwd } from './utils/cwd'
// this is the package manager that will be used for the test Nx workspace
export type PackageManager = 'npm' | 'yarn' | 'pnpm'
export const PACKAGE_MANAGER: PackageManager = 'pnpm'
// const CACHE_DIR = `${defaultCwd}/node_modules/.cache/nx-firebase`
export const CACHE_DIR = `${defaultCwd}/.nx-firebase`
// const CACHE_DIR = `${defaultCwd}/../.nx-firebase`
================================================
FILE: e2e/compat/src/app/setup.ts
================================================
// setup re-usable workspaces for e2e testbed
import { customExec } from './utils/exec'
import { green, info, log, red } from './utils/log'
import {
deleteDir,
deleteFile,
ensureDir,
fileExists,
setCwd,
} from './utils/utils'
import { createTestDir, createWorkspace } from './workspace'
import { Cache } from './utils/cache'
/**
* Generate an NxWorkspace with the given versions and gzip it
* unless the gzip archive of a version already exists
* @param nxVersion - target nx version eg. '13.10.6'
* @param pluginVersion - target nx-firebase version eg. '0.3.4'
* @param force - always recreate the workspace
*/
export async function setupNxWorkspace(cache: Cache, force = false) {
try {
// setup the target Nx workspace
const archiveExists = fileExists(cache.archiveFile) && !force
info(
`SETUP NX VERSION '${cache.nxVersion}' WITH PLUGIN VERSION '${
cache.pluginVersion
}' ${archiveExists ? '[CACHED]' : 'INSTALLING'}\n`,
)
ensureDir(cache.rootDir)
info(
`Creating new Nx workspace version ${cache.nxVersion} in directory '${cache.testDir}'`,
)
// create workspace & archive if it doesn't already exist
if (!archiveExists) {
deleteDir(cache.testDir)
createTestDir(cache.testDir)
await createWorkspace(cache)
// delete any existing archive file so we do not accidentally append to archive
if (fileExists(cache.archiveFile)) {
deleteFile(cache.archiveFile)
}
// cwd is workspaceDir
setCwd(cache.rootDir)
// archive the workspace
await customExec(`tar -zcf ${cache.archiveFile} ./${cache.nxVersion}`) // add -v for verbose
deleteDir(cache.testDir)
} else {
log(
`Workspace archive '${cache.archiveFile}' already exists for '${cache.workspaceDir}', no setup required`,
)
}
info(green(`SETUP VERSION '${cache.nxVersion}' SUCCEEDED\n`))
} catch (err) {
info(err.message)
info(red(`SETUP VERSION '${cache.nxVersion}' FAILED\n`))
// escalate, this is a show stopper
throw err
}
}
================================================
FILE: e2e/compat/src/app/test.ts
================================================
import { Cache, getCache, isNxVersionSince } from './utils/cache'
import { customExec, runNxCommandAsync } from './utils/exec'
import { expectToContain, it } from './utils/jest-ish'
import { green, info, log, red, setLogFile, time } from './utils/log'
import { deleteDir, getFileSize, setCwd } from './utils/utils'
import { installPlugin } from './workspace'
// const npmContent = [
// `Added 'npm' dependency 'firebase-admin'`,
// `Added 'npm' dependency 'firebase-functions'`,
// ]
// const libContent = [`Copied 'lib' dependency '@myorg/lib1'`]
// const importMatch = `import * as functions from "firebase-functions";`
// const notCachedMatch = `[existing outputs match the cache, left as is]`
const DELETE_AFTER_TEST = false
/**
* A basic e2e test suite for the plugin to check compatibility with different Nx versions
* We just want to check that the plugin can generate and build firebase apps and functions in each nx workspace version
* @param cache
*/
export async function testPlugin(cache: Cache) {
const workspaceDir = cache.workspaceDir
// const indexTsPath = `${workspaceDir}/apps/functions/src/index.ts`
// from nx 16.8.0, apps and libs dirs need to be specified in the commandline
let appsDirectory = ''
let libsDirectory = ''
if (isNxVersionSince(cache, '16.8.0')) {
appsDirectory = '--directory=apps'
libsDirectory = '--directory=libs'
}
// the function generator imports @nx/node, which is only installed to the workspace if the app is generated first
// so this test checks that the workspace is setup correctly
await it('should throw if function is generated before app', async () => {
let failed = false
try {
await runNxCommandAsync(
`g @simondotm/nx-firebase:func functions ${appsDirectory} --app=firebase`,
)
} catch (err) {
failed = true
}
expectToContain(failed ? 'failed' : 'succeeded', 'failed')
})
// generate a test firebase app
await runNxCommandAsync(
`g @simondotm/nx-firebase:app firebase ${appsDirectory}`,
)
// generate a test firebase function
await runNxCommandAsync(
`g @simondotm/nx-firebase:func functions ${appsDirectory} --app=firebase`,
)
// generate a test js library
await runNxCommandAsync(
`g @nx/js:lib lib1 ${libsDirectory} --importPath="@myorg/lib1"`,
)
// await it('should build the lib', async () => {
// await runNxCommandAsync('build lib1')
// })
// build the firebase app
await it('should build the firebase app', async () => {
await runNxCommandAsync('build firebase')
// const { stdout } = await runNxCommandAsync('build firebase')
// expectToNotContain(stdout, npmContent)
// expectToNotContain(stdout, libContent)
})
// build the firebase functions
await it('should build the functions app', async () => {
const { stdout } = await runNxCommandAsync('build functions')
log(stdout)
})
// check that sync runs
await it('should sync the workspace', async () => {
const { stdout } = await runNxCommandAsync('g @simondotm/nx-firebase:sync')
expectToContain(
stdout,
`This workspace has 1 firebase apps and 1 firebase functions`,
)
log(stdout)
})
// await it('should update index.ts so that deps are updated after creation', async () => {
// addContentToTextFile(indexTsPath, importMatch, '// comment added')
// const { stdout } = await runNxCommandAsync('build functions')
// expectToContain(stdout, npmContent)
// expectToNotContain(stdout, libContent)
// })
// await it('should add a lib dependency', async () => {
// const importAddition = `import { lib1 } from '@myorg/lib1'\nconsole.log(lib1())\n`
// addContentToTextFile(indexTsPath, importMatch, importAddition)
// const { stdout } = await runNxCommandAsync('build functions')
// expectToContain(stdout, npmContent)
// expectToContain(stdout, libContent)
// })
// some early 16.x versions of nx seem to have a flaky esbuild implementation
// that intermittently fails to exclude external deps from the bundle
// we check for this by testing the bundle size is not >1kb
// eslint-disable-next-line @typescript-eslint/require-await
await it('should not bundle external deps', async () => {
const fileSize = getFileSize(`${workspaceDir}/dist/apps/functions/main.js`)
if (fileSize > 1024)
throw new Error(
`TEST FAILED: esbuild bundle size is >1kb (${fileSize / 1024}kb)`,
)
})
// TODO: other checks
// - check package.json contains the deps
// - check package.json has the right node engine
// - check all the files exist
// - check the firebase config looks legit
// - if possible, run a test deploy?
// - check the init generator installs the firebase deps
// - check the plugin peerdeps installs the @nx/js and @nx/devkit and @nx/node deps
}
export function clean() {
const cache = getCache('', '')
info(red(`Cleaning compat test cache dir '${cache.rootDir}'`))
deleteDir(cache.rootDir)
}
export async function testNxVersion(cache: Cache) {
let error: string | undefined
const t = Date.now()
setLogFile(`${cache.rootDir}/${cache.nxVersion}.e2e.txt`)
try {
info(
`TESTING NX VERSION '${cache.nxVersion}' AGAINST PLUGIN VERSION '${cache.pluginVersion}'\n`,
)
// cleanup
setCwd(cache.rootDir)
deleteDir(cache.testDir)
// unpack the archive
setCwd(cache.rootDir)
await customExec(`tar -xzf ${cache.archiveFile}`) // add -v for verbose
setCwd(cache.workspaceDir)
if (cache.deferPluginInstall) {
// lets see if installing the plugin in the test suite
// makes things more stable...
await installPlugin(cache)
}
// run the plugin test suite
await testPlugin(cache)
info(green(`TESTING VERSION '${cache.nxVersion}' SUCCEEDED\n`))
} catch (err) {
info(err.message)
info(
red(
`TESTING VERSION '${cache.nxVersion}' FAILED - INCOMPATIBILITY DETECTED\n`,
),
)
error = err.message
}
// pretty sure there's nothing but trouble doing this
// if (cache.disableDaemon) {
// stop nx daemon after the test to stop connection in use errors
// await runNxCommandAsync(`reset`)
// }
// cleanup
setCwd(cache.rootDir)
if (DELETE_AFTER_TEST) {
deleteDir(cache.testDir)
}
const dt = Date.now() - t
info(`Completed in ${time(dt)}\n`)
return error
}
================================================
FILE: e2e/compat/src/app/utils/cache.ts
================================================
import { CACHE_DIR } from '../config'
import { defaultCwd } from './cwd'
import { info } from './log'
import { satisfies } from 'semver'
export const localPluginVersion = 'local'
export type Cache = {
nxVersion: string
pluginVersion: string
rootDir: string
testDir: string
workspaceDir: string
archiveFile: string
pluginWorkspace: string
disableDaemon: boolean
isLocalPlugin: boolean
deferPluginInstall: boolean // defer plugin installs during each test suite rather than in the workspace setup
nodeVersion: number // major node version
}
/**
* compat tests are run in these directories
* @param nxVersion
* @param pluginVersion
* @returns
*/
export function getCache(nxVersion: string, pluginVersion: string): Cache {
info(
`getting Cache for nxVersion=${nxVersion} pluginVersion=${pluginVersion}, using cache dir '${CACHE_DIR}'`,
)
const rootDir = `${CACHE_DIR}/${pluginVersion}`
const testDir = `${rootDir}/${nxVersion}`
const archiveFile = `${rootDir}/${nxVersion}.tar.gz`
const workspaceDir = `${testDir}/myorg`
const pluginWorkspace = defaultCwd
const isLocalPlugin = pluginVersion === localPluginVersion
return {
nxVersion,
pluginVersion,
rootDir,
testDir,
workspaceDir,
archiveFile,
pluginWorkspace,
isLocalPlugin,
deferPluginInstall: true, // dont think this is needed after all, was introduced because we had an issue from not installing @nx/js plugin
disableDaemon: false,
// disableDaemon: isLocalPlugin,
// deferPluginInstall: isLocalPlugin, // for local plugin tests, install them for tests so that code changes are present in tests
nodeVersion: parseInt(process.versions.node.split('.')[0]),
}
}
export function isNxVersionSince(cache: Cache, nxVersion: string) {
console.log(
`checking isNxVersionSince satisfies ${cache.nxVersion} >= ${nxVersion}`,
)
const isOk = satisfies(cache.nxVersion, `>=${nxVersion}`)
console.log('isNxVersionSince check returned ', isOk)
return isOk
}
================================================
FILE: e2e/compat/src/app/utils/cwd.ts
================================================
export const defaultCwd = process.cwd()
console.log(`cwd=${defaultCwd}`)
================================================
FILE: e2e/compat/src/app/utils/exec.ts
================================================
import { PACKAGE_MANAGER } from '../config'
import { info, log } from './log'
import { exec } from 'child_process'
/**
* Promisify node `exec`, with stdout & stderr piped to console
* @param command
* @param dir - defaults to cwd if not specified
* @returns
*/
export async function customExec(
command: string,
dir?: string,
): Promise<{ stdout: string; stderr: string }> {
const cwd = dir ? dir : process.cwd()
return new Promise((resolve, reject) => {
info(`Executing command '${command}' in '${cwd}'`)
const process = exec(
command,
{ cwd: cwd },
// { cwd: cwd, env: { NX_DAEMON: 'false' } }, // force CI type environment so Nx Daemon doesn't act up with multiple instances
// { cwd: cwd, env: { CI: 'true' } }, // force CI type environment so Nx Daemon doesn't act up with multiple instances
(error, stdout, stderr) => {
if (error) {
console.warn(error.message)
reject(error)
}
resolve({ stdout, stderr })
},
)
process.stdout.on('data', (data) => {
log(data.toString())
})
process.stderr.on('data', (data) => {
log(data.toString())
})
process.on('exit', (code) => {
if (code) {
log('child process exited with code ' + code.toString())
}
})
})
}
export async function runNxCommandAsync(command: string, dir?: string) {
const exec = PACKAGE_MANAGER === 'npm' ? 'npx' : 'pnpm exec'
const cmd = `${exec} nx ${command} --verbose`
const result = await customExec(cmd, dir)
return result
}
================================================
FILE: e2e/compat/src/app/utils/jest-ish.ts
================================================
import { info } from 'console'
import { log, red } from './log'
/**
* Test helper function approximating the Jest style of expect().toContain()
* @param content
* @param expected
* @returns true if content contains expected string
*/
function etc(content: string, expected: string) {
const pass = content.includes(expected)
return pass
}
function expectToContainInner(content: string, expected: string | string[]) {
if (Array.isArray(expected)) {
for (const e of expected) {
if (!etc(content, e)) {
return false
}
}
return true
} else {
return etc(content, expected)
}
}
export function expectToContain(content: string, expected: string | string[]) {
// log(`- expectToContain`)
// log(`- content='${content}'`)
// log(`- expected='${expected}'`)
const pass = expectToContainInner(content, expected)
if (!pass) {
throw new Error(
`TEST FAILED: expected '${expected}', received '${content}'`,
)
}
return pass
}
export function expectToNotContain(
content: string,
expected: string | string[],
) {
// log(`- expectToNotContain`)
// log(`- content='${content}'`)
// log(`- not expected='${expected}'`)
const pass = !expectToContainInner(content, expected)
if (!pass) {
throw new Error(
`TEST FAILED: not expected '${expected}', received '${content}'`,
)
}
return pass
}
// hacky jest-like tester
export async function it(testName: string, testFunc: () => Promise<void>) {
info(` - it ${testName}`)
log(` - it ${testName}`)
try {
await testFunc()
} catch (err) {
info(red(err))
throw err
}
}
================================================
FILE: e2e/compat/src/app/utils/log.ts
================================================
const ENABLE_LOG = false
const DEFAULT_LOG_FILE = `${process.cwd()}/e2e.log`
import * as fs from 'fs'
import { ensureDir } from './utils'
import { dirname } from 'path'
let LOG_FILE: string | undefined
function writeLog(msg: string) {
ensureDir(dirname(LOG_FILE))
fs.appendFileSync(LOG_FILE, `${msg}\n`)
}
export function setLogFile(path?: string) {
LOG_FILE = path || DEFAULT_LOG_FILE
console.log(`Logging to '${LOG_FILE}'`)
ensureDir(dirname(LOG_FILE))
fs.writeFileSync(LOG_FILE, '') // reset log file
}
setLogFile()
export function log(msg: string) {
if (ENABLE_LOG) {
console.log(msg)
}
writeLog(msg)
}
export function info(msg: string) {
console.log(msg)
writeLog(msg)
// fs.appendFileSync(LOG_FILE, `${msg}\n`)
}
export function time(ms: number) {
return `${(ms / 1000.0).toFixed(1)}s`
}
// https://stackoverflow.com/questions/9781218/how-to-change-node-jss-console-font-color
const GREEN_FG = '\x1b[32m'
const RED_FG = '\x1b[31m'
const RESET_FG = '\x1b[0m'
export function green(text: string) {
return `${GREEN_FG}${text}${RESET_FG}`
}
export function red(text: string) {
return `${RED_FG}${text}${RESET_FG}`
}
================================================
FILE: e2e/compat/src/app/utils/utils.ts
================================================
import * as fs from 'fs'
// import { readJsonFile, writeJsonFile } from '@nx/devkit'
// import { exit } from 'process'
import { log } from './log'
/**
* Set current working directory
* @param dir
*/
export function setCwd(dir: string) {
log(`Switching cwd to '${dir}'`)
process.chdir(dir)
log(`Switched cwd to '${process.cwd()}'`)
}
/**
* Ensure given directory path exists, create if it doesn't
* @param dir - directory path
* @returns true if path already exists
*/
export function ensureDir(dir: string) {
const pathExists = fs.existsSync(dir)
if (!pathExists) {
console.log(` - Creating dir '${dir}'...`)
fs.mkdirSync(dir, { recursive: true })
}
return pathExists
}
export function fileExists(path: string) {
return fs.existsSync(path)
}
export function deleteFile(path: string) {
log(`deleting file '${path}'`)
fs.rmSync(path)
}
export function deleteDir(path: string) {
log(`deleting dir '${path}'`)
fs.rmSync(path, { recursive: true, force: true })
}
/**
* Replace content in file `path` that matches `match` with `addition`
* @param path - path to the target text file
* @param match - string to match in the index.ts
* @param addition - string to add after the matched line in the index.ts
*/
export function addContentToTextFile(
path: string,
match: string,
addition: string,
) {
const content = fs.readFileSync(path, 'utf8')
if (!content.includes(match)) {
throw Error(
`ERROR: addContentToTextFile: Could not find '${match}' in '${path}'`,
)
}
const replaced = content.replace(match, `${match}\n${addition}`)
fs.writeFileSync(path, replaced)
}
export function getFileSize(path: string) {
const stats = fs.statSync(path)
return stats.size
}
================================================
FILE: e2e/compat/src/app/versions.ts
================================================
export const testVersions = {
pluginVersions: [
'local',
// '2.1.2',
// '0.3.4'
],
nxReleases: {
'16': {
'1': [4],
'2': [2],
'3': [2],
'4': [3],
'5': [5],
'6': [0],
'7': [4],
'8': [1],
'9': [1],
'10': [0],
},
'17': {
'0': [3],
'1': [3],
'2': [8],
// --nxCloud option for create-nx-workspace changed in 17.3.2, options are yes, github, circleci, skip
'3': [2],
},
'18': {
'0': [7],
'1': [2],
},
//--------------------------------
// LEGACY
//--------------------------------
// '15': {
// '4': [0, 1, 2, 3, 4, 5],
// '3': [0, 2, 3],
// '2': [0, 1, 2, 3, 4],
// '1': [0, 1],
// '0': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13],
// },
// '14': {
// '8': [0, 1, 2, 3, 4, 5, 6],
// '7': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18],
// '6': [0, 1, 2, 3, 4, 5],
// '5': [0, 1, 2, 3, 4, 5, 6, 8, 10],
// '4': [0, 1, 2, 3],
// '3': [0, 1, 2, 3, 4, 5, 6],
// '2': [1, 2, 4],
// '1': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
// '0': [0, 1, 2, 3, 4, 5],
// },
// '13': {
// '10': [6],
// },
},
}
================================================
FILE: e2e/compat/src/app/workspace.ts
================================================
import * as fs from 'fs'
import { Cache, isNxVersionSince } from './utils/cache'
import { customExec, runNxCommandAsync } from './utils/exec'
import { info, log } from './utils/log'
import { deleteDir, ensureDir, setCwd } from './utils/utils'
import { PACKAGE_MANAGER } from './config'
export function createTestDir(testDir: string) {
ensureDir(testDir)
setCwd(testDir)
}
export function workspaceExists(workspaceDir: string) {
return fs.existsSync(workspaceDir)
}
export function cleanWorkspace(dir: string) {
if (workspaceExists(dir)) {
log(`Cleaning workspace '${dir}'...`)
deleteDir(dir)
}
}
export async function installPlugin(cache: Cache) {
// this version of plugin has peerdeps that only work with node 14 / npm 6
const requireLegacyPeerDeps =
cache.nodeVersion >= 16 && cache.pluginVersion === '0.3.4'
const legacyPeerDeps = requireLegacyPeerDeps ? '--legacy-peer-deps' : ''
if (cache.isLocalPlugin) {
// install the plugin from the nx-firebase workspace as a local file dependency
// await customExec(
// `npm i ${cache.pluginWorkspace}/dist/packages/nx-firebase --save-dev ${legacyPeerDeps}`,
// )
// const pluginVersion = `${cache.pluginVersion}`
const pluginVersion = `2.2.0`
// log(
// `Packing plugin '${cache.pluginWorkspace}/dist/packages/nx-firebase}'...`,
// )
// await customExec(
// `npm pack`,
// `${cache.pluginWorkspace}/dist/packages/nx-firebase`,
// )
const pluginFileSrc = `${cache.pluginWorkspace}/dist/packages/nx-firebase/simondotm-nx-firebase-${pluginVersion}.tgz`
const pluginFileDst = `${cache.workspaceDir}/simondotm-nx-firebase-${pluginVersion}.tgz`
log(`Copying plugin '${pluginFileSrc}' to '${pluginFileDst}'...`)
await customExec(`cp -rf ${pluginFileSrc} ${pluginFileDst}`)
log(`Installing plugin '${pluginFileDst}'...`)
await customExec(
`${PACKAGE_MANAGER} i ${pluginFileDst} --save-dev ${legacyPeerDeps}`,
)
} else {
await customExec(
`${PACKAGE_MANAGER} i @simondotm/nx-firebase@${cache.pluginVersion} --save-dev ${legacyPeerDeps}`,
)
}
}
export async function createWorkspace(cache: Cache) {
cleanWorkspace(cache.workspaceDir)
const nxCloudOption = isNxVersionSince(cache, '17.3.2') ? 'skip' : 'false'
await customExec(
`npx create-nx-workspace@${cache.nxVersion} --preset=apps --interactive=false --name=myorg --nxCloud=${nxCloudOption} --packageManager=${PACKAGE_MANAGER}`,
)
setCwd(cache.workspaceDir)
// we have issues with the daemon when running the workspace with local plugin
// so we turn it off for these workspaces
if (cache.disableDaemon) {
log(`Disabling daemon in workspace...`)
const nxJsonFile = `${cache.workspaceDir}/nx.json`
const content = fs.readFileSync(nxJsonFile, 'utf8')
const nxJson = JSON.parse(content)
nxJson.tasksRunnerOptions.default.options.useDaemonProcess = false
fs.writeFileSync(nxJsonFile, JSON.stringify(nxJson))
// stop nx daemon before we run plugin - why?
log(`Stopping nx daemon...`)
await runNxCommandAsync(`reset`)
}
// we meed this plugin for test suite libs
// update: @nx/node plugin brings in this dependency
// https://github.com/nrwl/nx/blob/fb90767af87c77955f8b8b7cace7cd0b5e3be27d/packages/node/package.json#L32
// so we dont need to install it here as long as we run @simondotm/nx-firebase:init first
// await customExec(`npm i @nx/js@${cache.nxVersion} --save-dev`)
if (!cache.deferPluginInstall) {
info(`Installing plugin in workspace...`)
// // these should be installed with the plugin I guess?
// // if we dont install them here, they'll be found in the parent workspace node_modules
// await customExec(`npm i @nx/js@${cache.nxVersion} --save-dev`)
// await customExec(`npm i @nx/devkit@${cache.nxVersion} --save-dev`)
// await customExec(`npm i @nx/jest@${cache.nxVersion} --save-dev`)
// install the target plugin we want to test
await installPlugin(cache)
// run the plugin initialiser to ensure we have the correct dependencies installed
info(`Initialising plugin in workspace...`)
await runNxCommandAsync(`g @simondotm/nx-firebase:init`)
}
// if (cache.disableDaemon) {
// cleanup - stop nx daemon post setup to prevent connection in use errors
// log(`Stopping nx daemon...`)
// await runNxCommandAsync('reset')
// }
}
================================================
FILE: e2e/compat/src/assets/.gitkeep
================================================
================================================
FILE: e2e/compat/src/environments/environment.prod.ts
================================================
export const environment = {
production: true,
}
================================================
FILE: e2e/compat/src/environments/environment.ts
================================================
export const environment = {
production: false,
}
================================================
FILE: e2e/compat/src/main.ts
================================================
/**
* Custom e2e compatibility test suite for @simondotm/nx-firebase Nx plugin
* The plugin e2e test suite can be unreliable and has limitations in Jest
* This script allows us to run full matrix e2e and regression tests of the plugin across:
* - Node versions 14,16,18
* - Nx versions against plugin versions
* - Check firebase deployments in CI environment
* - We only do light functional tests, this test matrix is for ensuring wide compatibility of plugin generator & executor
*/
import { green, info, red, setLogFile, time } from './app/utils/log'
import { setupNxWorkspace } from './app/setup'
import { testVersions } from './app/versions'
import { clean, testNxVersion } from './app/test'
import { getCache } from './app/utils/cache'
import { customExec } from './app/utils/exec'
import { defaultCwd } from './app/utils/cwd'
// Force CI environment if necessary
// process.env.CI = 'true'
// process.env.NX_DAEMON = 'false'
type CmdOptions = {
onlySetup: boolean
force: boolean
clean: boolean
}
async function main(options: CmdOptions) {
const t = Date.now()
const pluginVersions = testVersions.pluginVersions
// gather all nx versions in the test matrix
const nxReleases: string[] = []
for (const maj in testVersions.nxReleases) {
const majVersions = testVersions.nxReleases[maj]
for (const min in majVersions) {
const patchVersions = majVersions[min]
const latestVersion = patchVersions[patchVersions.length - 1]
const version = `${maj}.${min}.${latestVersion}`
nxReleases.push(version)
}
}
info(`Packing plugin '${defaultCwd}/dist/packages/nx-firebase'...`)
await customExec(`npm pack`, `${defaultCwd}/dist/packages/nx-firebase`)
//-----------------------------------------------------------------------
// setup phase - generates workspaces for each Nx minor release
//-----------------------------------------------------------------------
// gzip's and caches them for re-use
// splitting the setup phase from the test phase allows us to cache
// node_modules in CI github actions for this compat test
const testMatrixSize = nxReleases.length * pluginVersions.length * 2 // 2 = setup+test
//-----------------------------------------------------------------------
// test phase - tests each Nx minor release
//-----------------------------------------------------------------------
let testCounter = 0
const errors: string[] = []
for (let i = 0; i < nxReleases.length; ++i) {
for (const pluginVersion of pluginVersions) {
const release = nxReleases[i]
//-----------------------------------------------------------------------
// setup phase - generates workspaces for each Nx minor release
//-----------------------------------------------------------------------
// gzip's and caches them for re-use
// splitting the setup phase from the test phase allows us to cache
// node_modules in CI github actions for this compat test
const cache = getCache(release, pluginVersion)
setLogFile(`${cache.rootDir}/${cache.nxVersion}.log.txt`)
info(
`-- ${
testCounter + 1
}/${testMatrixSize} --------------------------------------------------------------------------\n`,
)
await setupNxWorkspace(cache, options.force)
++testCounter
//-----------------------------------------------------------------------
// test phase - tests each Nx minor release
//-----------------------------------------------------------------------
info(
`-- ${
testCounter + 1
}/${testMatrixSize} --------------------------------------------------------------------------\n`,
)
if (!options.onlySetup) {
const result = await testNxVersion(cache)
if (result) {
errors.push(result)
}
}
++testCounter
}
}
// report error summary
if (errors.length) {
info(red('TEST ERRORS:`n'))
for (const error of errors) {
info(red(error))
}
} else {
info(green('ALL TESTS SUCCEEDED'))
}
//-----------------------------------------------------------------------
// Complete
//-----------------------------------------------------------------------
const dt = Date.now() - t
info(`Total time ${time(dt)}`)
}
// entry
const options: CmdOptions = { onlySetup: false, force: false, clean: false }
if (process.argv.length > 2) {
if (process.argv[2] === '--setup') {
options.onlySetup = true
} else if (process.argv[2] === '--clean') {
options.clean = true
} else if (process.argv[2] === '--force') {
options.force = true
}
}
if (options.clean) {
clean()
} else {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
main(options)
}
================================================
FILE: e2e/compat/tsconfig.app.json
================================================
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["node"]
},
"exclude": ["**/*.spec.ts", "**/*.test.ts", "jest.config.ts"],
"include": ["**/*.ts"]
}
================================================
FILE: e2e/compat/tsconfig.json
================================================
{
"extends": "../../tsconfig.base.json",
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}
================================================
FILE: e2e/compat/tsconfig.spec.json
================================================
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"]
},
"include": ["**/*.test.ts", "**/*.spec.ts", "**/*.d.ts", "jest.config.ts"]
}
================================================
FILE: e2e/compat/webpack.config.js
================================================
const { composePlugins, withNx } = require('@nx/webpack')
// Nx plugins for webpack.
module.exports = composePlugins(withNx(), (config) => {
// Note: This was added by an Nx migration. Webpack builds are required to have a corresponding Webpack config file.
// See: https://nx.dev/recipes/webpack/webpack-config-setup
return config
})
================================================
FILE: e2e/nx-firebase-e2e/jest.config.js
================================================
module.exports = {
displayName: 'nx-firebase-e2e',
preset: '../../jest.preset.js',
globals: {},
testEnvironment: 'node',
transform: {
'^.+\\.[tj]s$': [
'ts-jest',
{
tsconfig: '<rootDir>/tsconfig.spec.json',
},
],
},
moduleFileExtensions: ['ts', 'js', 'html'],
// Global setup runs once before all test files
globalSetup: '<rootDir>/jest.globalSetup.js',
globalTeardown: '<rootDir>/jest.globalTeardown.js',
// Custom sequencer ensures tests run in correct order
testSequencer: '<rootDir>/jest.testSequencer.js',
// Long timeout for e2e tests (3 minutes per test)
testTimeout: 180000,
// Match spec files in tests directory
testMatch: ['<rootDir>/tests/**/*.spec.ts'],
}
================================================
FILE: e2e/nx-firebase-e2e/jest.globalSetup.js
================================================
/**
* Global setup for e2e tests - runs once before all test files
* Creates and configures the e2e test workspace
*/
const {
ensureNxProject,
updateFile,
runNxCommandAsync,
} = require('@nx/plugin/testing')
const pluginName = '@simondotm/nx-firebase'
const pluginPath = 'dist/packages/nx-firebase'
const workspaceLayout = {
appsDir: 'apps',
libsDir: 'libs',
}
module.exports = async function globalSetup() {
console.log('\n[Global Setup] Creating e2e test workspace...\n')
// Ensure daemon is disabled
process.env['CI'] = 'true'
process.env['NX_DAEMON'] = 'false'
// Create the e2e workspace with the plugin
ensureNxProject(pluginName, pluginPath)
// Configure workspace layout for consistent project naming
updateFile('nx.json', (text) => {
const json = JSON.parse(text)
json.workspaceLayout = workspaceLayout
// Nx 17+ uses root-level useDaemonProcess instead of tasksRunnerOptions.default
json.useDaemonProcess = false
return JSON.stringify(json, null, 2)
})
// Reset nx to ensure clean state
await runNxCommandAsync('reset')
console.log('\n[Global Setup] Workspace ready\n')
}
================================================
FILE: e2e/nx-firebase-e2e/jest.globalTeardown.js
================================================
/**
* Global teardown for e2e tests - runs once after all test files
* Cleans up the e2e test workspace
*/
const { runNxCommandAsync } = require('@nx/plugin/testing')
module.exports = async function globalTeardown() {
console.log('\n[Global Teardown] Cleaning up...\n')
try {
// `nx reset` kills the daemon and performs cleanup
await runNxCommandAsync('reset')
} catch (e) {
console.log('Teardown warning:', e.message)
}
console.log('\n[Global Teardown] Complete\n')
}
================================================
FILE: e2e/nx-firebase-e2e/jest.testSequencer.js
================================================
/**
* Custom test sequencer to ensure e2e tests run in the correct order.
* Some tests depend on shared libraries created by earlier tests.
*/
const Sequencer = require('@jest/test-sequencer').default
class CustomSequencer extends Sequencer {
/**
* Define the order in which test files should run.
* test-workspace must run first to verify setup
* test-libraries must run second to create shared libraries
* other tests can then run and use those libraries
*/
sort(tests) {
const testOrder = [
'test-workspace.spec.ts',
'test-libraries.spec.ts',
'test-application.spec.ts',
'test-function.spec.ts',
'test-bundler.spec.ts',
'test-sync.spec.ts',
'test-migrate.spec.ts',
'test-targets.spec.ts',
]
const getOrder = (test) => {
const filename = test.path.split('/').pop() || ''
const index = testOrder.indexOf(filename)
return index === -1 ? testOrder.length : index
}
return [...tests].sort((a, b) => getOrder(a) - getOrder(b))
}
}
module.exports = CustomSequencer
================================================
FILE: e2e/nx-firebase-e2e/project.json
================================================
{
"name": "nx-firebase-e2e",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"projectType": "application",
"sourceRoot": "e2e/nx-firebase-e2e/src",
"tags": [],
"implicitDependencies": ["nx-firebase"],
"targets": {
"e2e": {
"executor": "@nx/jest:jest",
"options": {
"jestConfig": "e2e/nx-firebase-e2e/jest.config.js",
"runInBand": true,
"passWithNoTests": false,
"bail": true
},
"dependsOn": ["nx-firebase:build"]
}
}
}
================================================
FILE: e2e/nx-firebase-e2e/test-utils/index.ts
================================================
export * from './test-utils-apps'
export * from './test-utils-commands'
export * from './test-utils-functions'
export * from './test-utils-helpers'
export * from './test-utils-imports'
export * from './test-utils-logger'
export * from './test-utils-project-data'
export * from './test-shared-data'
================================================
FILE: e2e/nx-firebase-e2e/test-utils/test-shared-data.ts
================================================
import { getProjectData } from './test-utils-project-data'
/**
* Shared library data that persists across all e2e tests.
* These libraries are created by test-libraries.spec.ts and used by other tests.
*
* Note: Project names must be unique across the workspace.
* The subdir variants use different names (e.g., 'subdir-buildablelib').
*/
export const buildableLibData = getProjectData('libs', 'buildablelib')
export const nonbuildableLibData = getProjectData('libs', 'nonbuildablelib')
export const subDirBuildableLibData = getProjectData(
'libs',
'subdir-buildablelib',
{ dir: 'subdir' },
)
export const subDirNonbuildableLibData = getProjectData(
'libs',
'subdir-nonbuildablelib',
{ dir: 'subdir' },
)
================================================
FILE: e2e/nx-firebase-e2e/test-utils/test-utils-apps.ts
================================================
import type { ProjectData } from './test-utils-project-data'
import { readJson } from '@nx/plugin/testing'
export function expectedAppProjectTargets(appProject: ProjectData) {
return {
build: {
executor: 'nx:run-commands',
dependsOn: ['^build'],
options: {
command: `echo Build succeeded.`,
},
},
watch: {
executor: 'nx:run-commands',
options: {
command: `nx run-many --targets=build --projects=tag:firebase:dep:${appProject.projectName} --parallel=100 --watch`,
},
},
lint: {
executor: 'nx:run-commands',
options: {
command: `nx run-many --targets=lint --projects=tag:firebase:dep:${appProject.projectName} --parallel=100`,
},
},
test: {
executor: 'nx:run-commands',
options: {
command: `nx run-many --targets=test --projects=tag:firebase:dep:${appProject.projectName} --parallel=100`,
},
},
firebase: {
executor: 'nx:run-commands',
options: {
command: `firebase --config=firebase.json`,
},
configurations: {
production: {
command: `firebase --config=firebase.json`,
},
},
},
killports: {
executor: 'nx:run-commands',
options: {
command: `kill-port --port 9099,5001,8080,9000,5000,8085,9199,9299,4000,4400,4500`,
},
},
getconfig: {
executor: 'nx:run-commands',
options: {
command: `nx run ${appProject.projectName}:firebase functions:config:get > ${appProject.projectDir}/environment/.runtimeconfig.json`,
},
},
emulate: {
executor: 'nx:run-commands',
options: {
commands: [
`nx run ${appProject.projectName}:killports`,
`nx run ${appProject.projectName}:firebase emulators:start --import=${appProject.projectDir}/.emulators --export-on-exit`,
],
parallel: false,
},
},
serve: {
executor: '@simondotm/nx-firebase:serve',
options: {
commands: [
`nx run ${appProject.projectName}:watch`,
`nx run ${appProject.projectName}:emulate`,
],
},
},
deploy: {
executor: 'nx:run-commands',
dependsOn: ['build'],
options: {
command: `nx run ${appProject.projectName}:firebase deploy`,
},
},
}
}
export function validateProjectConfig(appProject: ProjectData) {
const project = readJson(`${appProject.projectDir}/project.json`)
// expect(project.root).toEqual(`apps/${projectName}`)
expect(project.targets).toEqual(
expect.objectContaining(expectedAppProjectTargets(appProject)),
)
}
================================================
FILE: e2e/nx-firebase-e2e/test-utils/test-utils-commands.ts
================================================
import { expectStrings, expectNoStrings } from './test-utils-helpers'
import { testDebug, red, green } from './test-utils-logger'
import { runNxCommandAsync } from '@nx/plugin/testing'
import { ProjectData } from './test-utils-project-data'
const STRIP_ANSI_MATCHER =
/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g
export async function safeRunNxCommandAsync(cmd: string) {
testDebug(`- safeRunNxCommandAsync ${cmd}`)
try {
async function runCommand(cmd: string) {
const result = await runNxCommandAsync(`${cmd} --verbose`, {
silenceError: true,
})
// strip chalk TTY ANSI codes from output
result.stdout = result.stdout.replace(STRIP_ANSI_MATCHER, '')
result.stderr = result.stderr.replace(STRIP_ANSI_MATCHER, '')
if (result.stdout) {
testDebug(green(result.stdout))
}
if (result.stderr) {
testDebug(red(result.stderr))
}
return result
}
// getting wierd lock file errors from Nx, so retry at least once
// SM Mar'24: this was due to 16.8.1+ e2e tests having the Nx Daemon enabled
// It's now disabled
let result = await runCommand(cmd)
// if (result.stdout.includes('LOCK-FILES-CHANGED') || result.stderr.includes('LOCK-FILES-CHANGED')) {
// testDebug(red(`Re-running command ${cmd} due to LOCK-FILES-CHANGED`))
// result = await runCommand(cmd)
// }
return result
} catch (e) {
testDebug(red(`ERROR: Running command ${(e as Error).message}`))
throw e
}
}
export async function runTargetAsync(
projectData: ProjectData,
target: string = 'build',
) {
testDebug(`- runTargetAsync ${target} ${projectData.projectName}`)
const result = await safeRunNxCommandAsync(
`${target} ${projectData.projectName}`,
)
if (target === 'build') {
expectStrings(result.stdout, [
`Successfully ran target ${target} for project ${projectData.projectName}`,
])
}
return result
}
export async function removeProjectAsync(projectData: ProjectData) {
const result = await safeRunNxCommandAsync(
`g @nx/workspace:remove ${projectData.projectName} --forceRemove`,
)
expectStrings(result.stdout, [
`DELETE ${projectData.projectDir}/project.json`,
`DELETE ${projectData.projectDir}`,
])
return result
}
export async function renameProjectAsync(
projectData: ProjectData,
renameProjectData: ProjectData,
) {
// In Nx 20, @nx/workspace:move requires:
// --project: the current project name
// --destination: the new directory path (also becomes the new project name by default)
// --newProjectName: explicitly set the new project name (optional, defaults to last segment of destination)
const result = await safeRunNxCommandAsync(
`g @nx/workspace:move --projectName=${projectData.projectName} --destination=${renameProjectData.projectDir} --newProjectName=${renameProjectData.projectName}`,
)
expectStrings(result.stdout, [
`DELETE ${projectData.projectDir}/project.json`,
`DELETE ${projectData.projectDir}`,
`CREATE ${renameProjectData.projectDir}/project.json`,
])
return result
}
export async function appGeneratorAsync(
projectData: ProjectData,
params: string = '',
) {
testDebug(`- appGeneratorAsync ${projectData.projectName} ${params}`)
const result = await safeRunNxCommandAsync(
`g @simondotm/nx-firebase:app ${projectData.name} --directory=${projectData.directory} ${params}`,
)
return result
}
export async function functionGeneratorAsync(
projectData: ProjectData,
params: string = '',
) {
testDebug(`- functionGeneratorAsync ${projectData.projectName} ${params}`)
const result = await safeRunNxCommandAsync(
`g @simondotm/nx-firebase:function ${projectData.name} --directory=${projectData.directory} ${params}`,
)
return result
}
export async function libGeneratorAsync(
projectData: ProjectData,
params: string = '',
) {
testDebug(`- libGeneratorAsync ${projectData.projectName}`)
const result = await safeRunNxCommandAsync(
`g @nx/js:lib ${projectData.name} --directory=${projectData.directory} ${params}`,
)
return result
}
export async function syncGeneratorAsync(params: string = '') {
testDebug(`- syncGeneratorAsync ${params}`)
return await safeRunNxCommandAsync(`g @simondotm/nx-firebase:sync ${params}`)
}
export async function migrateGeneratorAsync(params: string = '') {
testDebug(`- migrateGeneratorAsync ${params}`)
return await safeRunNxCommandAsync(
`g @simondotm/nx-firebase:migrate ${params}`,
)
}
export async function cleanAppAsync(
projectData: ProjectData,
options?: { appsRemaining: number; functionsRemaining: number },
) {
testDebug(`- cleanAppAsync ${projectData.projectName}`)
await removeProjectAsync(projectData)
const result = await syncGeneratorAsync(projectData.projectName)
testDebug(result.stdout)
expect(result.stdout).toMatch(/DELETE (firebase)(\S*)(.json)/)
// Verify the config was cleaned up
expectStrings(result.stdout, [
`CHANGE Firebase config '${projectData.configName}' is no longer referenced by any firebase app, deleted`,
])
// Only assert on exact counts if explicitly provided
if (options) {
expectStrings(result.stdout, [
`This workspace has ${options.appsRemaining} firebase apps and ${options.functionsRemaining} firebase functions`,
])
}
}
export async function cleanFunctionAsync(projectData: ProjectData) {
testDebug(`- cleanFunctionAsync ${projectData.projectName}`)
await removeProjectAsync(projectData)
}
================================================
FILE: e2e/nx-firebase-e2e/test-utils/test-utils-functions.ts
================================================
import type { ProjectData } from './test-utils-project-data'
import { readJson } from '@nx/plugin/testing'
export function expectedFunctionProjectTargets(
functionProject: ProjectData,
appProject: ProjectData,
) {
return {
build: {
executor: '@nx/esbuild:esbuild',
outputs: ['{options.outputPath}'],
options: {
platform: 'node',
outputPath: `dist/${functionProject.projectDir}`,
main: `${functionProject.projectDir}/src/main.ts`,
tsConfig: `${functionProject.projectDir}/tsconfig.app.json`,
assets: [
`${functionProject.projectDir}/src/assets`,
{
glob: '**/*',
input: `${appProject.projectDir}/environment`,
output: '.',
},
],
generatePackageJson: true,
bundle: true,
dependenciesFieldType: 'dependencies',
format: ['esm'],
thirdParty: false,
target: 'node20',
esbuildOptions: {
logLevel: 'info',
},
},
},
deploy: {
executor: 'nx:run-commands',
options: {
command: `nx run ${appProject.projectName}:deploy --only functions:${functionProject.projectName}`,
},
dependsOn: ['build'],
},
lint: {
executor: '@nx/eslint:lint',
},
// Test target is kept with passWithNoTests so functions without tests don't fail
// with passWithNoTests so functions without tests don't fail
// Nx 22+ uses jest.config.cts for ESM projects
test: {
executor: '@nx/jest:jest',
outputs: ['{workspaceRoot}/coverage/{projectRoot}'],
options: {
jestConfig: expect.stringMatching(
new RegExp(`^${functionProject.projectDir}/jest\\.config\\.c?ts$`),
),
passWithNoTests: true,
},
},
}
}
export function validateFunctionConfig(
functionProject: ProjectData,
appProject: ProjectData,
) {
const project = readJson(`${functionProject.projectDir}/project.json`)
// expect(project.root).toEqual(`apps/${projectName}`)
expect(project.targets).toEqual(
expect.objectContaining(
expectedFunctionProjectTargets(functionProject, appProject),
),
)
}
================================================
FILE: e2e/nx-firebase-e2e/test-utils/test-utils-helpers.ts
================================================
export function expectStrings(input: string, contains: string[]) {
contains.forEach((item) => {
expect(input).toContain(item)
})
}
export function expectNoStrings(input: string, contains: string[]) {
contains.forEach((item) => {
expect(input).not.toContain(item)
})
}
================================================
FILE: e2e/nx-firebase-e2e/test-utils/test-utils-imports.ts
================================================
import type { ProjectData } from './test-utils-project-data'
const IMPORT_MATCH = `import * as logger from "firebase-functions/logger";`
export function getMainTs() {
return `
/**
* Import function triggers from their respective submodules:
*
* import {onCall} from "firebase-functions/v2/https";
* import {onDocumentWritten} from "firebase-functions/v2/firestore";
*
* See a full list of supported triggers at https://firebase.google.com/docs/functions
*/
import {onRequest} from "firebase-functions/v2/https";
${IMPORT_MATCH}
// Start writing functions
// https://firebase.google.com/docs/functions/typescript
export const helloWorld = onRequest((request, response) => {
logger.info("Hello logs!", {structuredData: true});
response.send("Hello from Firebase!");
});
`
}
/**
* return the import function for a generated library
*/
export function getLibImport(projectData: ProjectData) {
// convert kebab-case project name to camelCase library import
const libName = projectData.projectName
.split('-')
.map((part, index) =>
index > 0 ? part[0].toUpperCase() + part.substring(1) : part,
)
.join('')
return libName
}
export function addImport(mainTs: string, addition: string) {
const replaced = mainTs.replace(IMPORT_MATCH, `${IMPORT_MATCH}\n${addition}`)
return replaced
}
================================================
FILE: e2e/nx-firebase-e2e/test-utils/test-utils-logger.ts
================================================
const ENABLE_TEST_DEBUG_INFO = true
// https://stackoverflow.com/questions/9781218/how-to-change-node-jss-console-font-color
const GREEN_FG = '\x1b[32m'
const RED_FG = '\x1b[31m'
const RESET_FG = '\x1b[0m'
export function green(text: string) {
return `${GREEN_FG}${text}${RESET_FG}`
}
export function red(text: string) {
return `${RED_FG}${text}${RESET_FG}`
}
export function testDebug(info: string) {
if (ENABLE_TEST_DEBUG_INFO) {
console.debug(info)
}
}
================================================
FILE: e2e/nx-firebase-e2e/test-utils/test-utils-project-data.ts
================================================
import { joinPathFragments } from '@nx/devkit'
const NPM_SCOPE = '@proj'
export interface ProjectData {
name: string
directory: string // the --directory option value for generators
projectName: string
projectDir: string
srcDir: string
distDir: string
mainTsPath: string
npmScope: string
configName: string
}
/**
* Generate test project data
*
* Nx 20+ uses "as-provided" naming only:
* - projectName = name (exactly as provided)
* - projectRoot = directory (exactly as provided)
*
* To maintain backwards compatibility with the original e2e test behavior:
* - The `type` parameter ('apps' or 'libs') is used as the directory prefix
* - If `dir` option is provided, projects are nested under it: type/dir/name
* - Project names must be unique (tests use uniq() for this)
* - This ensures projectRoot (e.g., 'apps/my-app') differs from projectName (e.g., 'my-app')
* which is important because @nx/workspace:move does string replacement of project roots
*
* Note: call this function AFTER initial app firebase.json has been created in order to have a
* correct configName
* @param type - 'libs' or 'apps' - used as directory prefix to match original test behavior
* @param name - project name (must be unique, tests use uniq() for this)
* @param options - optional directory and customConfig settings
* @returns - asset locations for this project
*/
export function getProjectData(
type: 'libs' | 'apps',
name: string,
options?: { dir?: string; customConfig?: boolean },
): ProjectData {
// Project name is exactly as provided (must be unique across workspace)
const projectName = name
// Use the type ('apps' or 'libs') as the base directory prefix
// If additional dir is provided, nest under that as well
// This maintains the original e2e test folder structure
const directory = options?.dir
? joinPathFragments(type, options.dir, name)
: joinPathFragments(type, name)
// Project directory is exactly the directory (which is the full project root)
const projectDir = directory
const srcDir = joinPathFragments(projectDir, 'src')
const mainTsPath = joinPathFragments(srcDir, 'main.ts')
const distDir = joinPathFragments('dist', projectDir)
return {
name: projectName, // name passed to generator
directory, // --directory option for generators (full project root path)
projectName, // project name (must be unique)
projectDir,
srcDir,
distDir,
mainTsPath,
npmScope: `${NPM_SCOPE}/${projectName}`,
configName: options?.customConfig
? `firebase.${projectName}.json`
: 'firebase.json',
}
}
================================================
FILE: e2e/nx-firebase-e2e/tests/test-application.spec.ts
================================================
import {
readJson,
runNxCommandAsync,
uniq,
checkFilesExist,
readFile,
} from '@nx/plugin/testing'
import {
ProjectData,
appGeneratorAsync,
cleanAppAsync,
getProjectData,
testDebug,
validateProjectConfig,
} from '../test-utils'
// Ensure daemon is disabled for all e2e tests
beforeAll(() => {
process.env['CI'] = 'true'
process.env['NX_DAEMON'] = 'false'
})
function expectedAppFiles(projectData: ProjectData) {
const projectPath = projectData.projectDir
return [
`${projectPath}/public/index.html`,
`${projectPath}/public/404.html`,
`${projectPath}/database.rules.json`,
`${projectPath}/firestore.indexes.json`,
`${projectPath}/firestore.rules`,
`${projectPath}/project.json`,
`${projectPath}/readme.md`,
`${projectPath}/storage.rules`,
`${projectData.configName}`,
`.firebaserc`,
]
}
//--------------------------------------------------------------------------------------------------
// Application generator e2e tests
//--------------------------------------------------------------------------------------------------
describe('nx-firebase application', () => {
// Track current test's app for cleanup
let currentAppData: ProjectData | null = null
// Always run cleanup after each test, even on failure
afterEach(async () => {
if (currentAppData) {
try {
await cleanAppAsync(currentAppData)
} catch (e) {
testDebug(`Cleanup warning: ${(e as Error).message}`)
}
currentAppData = null
}
})
it('should create nx-firebase app', async () => {
currentAppData = getProjectData('apps', uniq('firebaseSetupApp'))
await appGeneratorAsync(currentAppData)
// test generator output
expect(() =>
checkFilesExist(...expectedAppFiles(currentAppData!)),
).not.toThrow()
validateProjectConfig(currentAppData)
// check that the firestore.rules file has had the IN_30_DAYS placeholder replaced
const firestoreRules = readFile(`${currentAppData.projectDir}/firestore.rules`)
testDebug(firestoreRules)
expect(firestoreRules).not.toContain('IN_30_DAYS')
})
it('should build nx-firebase app', async () => {
currentAppData = getProjectData('apps', uniq('firebaseSetupApp'))
await appGeneratorAsync(currentAppData)
// test app builder
// at this point there are no functions so it does nothing
const result = await runNxCommandAsync(`build ${currentAppData.projectName}`)
expect(result.stdout).toContain('Build succeeded.')
})
describe('--directory', () => {
it('should create nx-firebase app in the specified directory', async () => {
currentAppData = getProjectData('apps', uniq('firebaseSetupApp'), {
dir: 'subdir',
})
await appGeneratorAsync(currentAppData)
expect(() =>
checkFilesExist(...expectedAppFiles(currentAppData!)),
).not.toThrow()
const project = readJson(`${currentAppData.projectDir}/project.json`)
expect(project.name).toEqual(`${currentAppData.projectName}`)
validateProjectConfig(currentAppData)
})
})
describe('--tags', () => {
it('should add tags to the project', async () => {
currentAppData = getProjectData('apps', uniq('firebaseSetupApp'))
await appGeneratorAsync(currentAppData, `--tags e2etag,e2ePackage`)
const project = readJson(`${currentAppData.projectDir}/project.json`)
expect(project.tags).toEqual([
'firebase:app',
`firebase:name:${currentAppData.projectName}`,
'e2etag',
'e2ePackage',
])
})
})
})
================================================
FILE: e2e/nx-firebase-e2e/tests/test-bundler.spec.ts
================================================
import {
readJson,
uniq,
updateFile,
checkFilesExist,
readFile,
} from '@nx/plugin/testing'
import {
appGeneratorAsync,
cleanAppAsync,
cleanFunctionAsync,
functionGeneratorAsync,
getProjectData,
validateFunctionConfig,
runTargetAsync,
getMainTs,
getLibImport,
addImport,
expectStrings,
testDebug,
ProjectData,
// Shared library data from test-libraries
buildableLibData,
nonbuildableLibData,
subDirBuildableLibData,
subDirNonbuildableLibData,
} from '../test-utils'
// Ensure daemon is disabled for all e2e tests
beforeAll(() => {
process.env['CI'] = 'true'
process.env['NX_DAEMON'] = 'false'
})
//--------------------------------------------------------------------------------------------------
// Test import & dependency handling
//--------------------------------------------------------------------------------------------------
describe('nx-firebase bundle dependencies', () => {
// Track current test's projects for cleanup
let currentAppData: ProjectData | null = null
let currentFunctionData: ProjectData | null = null
// Always run cleanup after each test, even on failure
afterEach(async () => {
if (currentFunctionData) {
try {
await cleanFunctionAsync(currentFunctionData)
} catch (e) {
testDebug(`Function cleanup warning: ${(e as Error).message}`)
}
currentFunctionData = null
}
if (currentAppData) {
try {
await cleanAppAsync(currentAppData)
} catch (e) {
testDebug(`App cleanup warning: ${(e as Error).message}`)
}
currentAppData = null
}
})
it('should inline library dependencies into function bundle', async () => {
// use libs we generated earlier
currentAppData = getProjectData('apps', uniq('firebaseDepsApp'))
currentFunctionData = getProjectData('apps', uniq('firebaseDepsFunction'))
await appGeneratorAsync(currentAppData)
await functionGeneratorAsync(
currentFunctionData,
`--app ${currentAppData.projectName}`,
)
validateFunctionConfig(currentFunctionData, currentAppData)
// add buildable & nonbuildable lib dependencies using import statements
let mainTs = getMainTs()
// import from a buildable lib
const libImport1 = getLibImport(buildableLibData)
const importAddition1 = `import { ${libImport1} } from '${buildableLibData.npmScope}'\nconsole.log(${libImport1}())\n`
mainTs = addImport(mainTs, importAddition1)
// import from a non buildable lib
const libImport2 = getLibImport(nonbuildableLibData)
const importAddition2 = `import { ${libImport2} } from '${nonbuildableLibData.npmScope}'\nconsole.log(${libImport2}())\n`
mainTs = addImport(mainTs, importAddition2)
// import from a buildable subdir lib
const libImport3 = getLibImport(subDirBuildableLibData)
const importAddition3 = `import { ${libImport3} } from '${subDirBuildableLibData.npmScope}'\nconsole.log(${libImport3}())\n`
mainTs = addImport(mainTs, importAddition3)
// import from a non buildable subdir lib
const libImport4 = getLibImport(subDirNonbuildableLibData)
const importAddition4 = `import { ${libImport4} } from '${subDirNonbuildableLibData.npmScope}'\nconsole.log(${libImport4}())\n`
mainTs = addImport(mainTs, importAddition4)
// write the new main.ts
updateFile(currentFunctionData.mainTsPath, () => {
return mainTs
})
// confirm the file changes
const updatedMainTs = readFile(currentFunctionData.mainTsPath)
expect(updatedMainTs).toContain(importAddition1)
expect(updatedMainTs).toContain(importAddition2)
expect(updatedMainTs).toContain(importAddition3)
expect(updatedMainTs).toContain(importAddition4)
// build
const result = await runTargetAsync(currentFunctionData, `build`)
// check console output
expectStrings(result.stdout, [
`Running target build for project ${currentFunctionData.projectName}`,
`nx run ${buildableLibData.projectName}:build`,
`nx run ${subDirBuildableLibData.projectName}:build`,
`Compiling TypeScript files for project "${subDirBuildableLibData.projectName}"`,
`Compiling TypeScript files for project "${buildableLibData.projectName}"`,
`Done compiling TypeScript files for project "${buildableLibData.projectName}"`,
`Done compiling TypeScript files for project "${subDirBuildableLibData.projectName}"`,
`nx run ${currentFunctionData.projectName}:build`,
`Successfully ran target build for project ${currentFunctionData.projectName}`,
])
expectStrings(result.stderr, [`${currentFunctionData.distDir}/main.js`])
// make sure output build is not megabytes in size, which would mean we've
// bundled node_modules as well
expect(result.stdout).not.toContain('Mb')
// check dist outputs
expect(() =>
checkFilesExist(
`${currentFunctionData!.distDir}/package.json`,
`${currentFunctionData!.distDir}/main.js`,
`${currentFunctionData!.distDir}/.env`,
`${currentFunctionData!.distDir}/.env.local`,
`${currentFunctionData!.distDir}/.secret.local`,
),
).not.toThrow()
// check dist package contains external imports
const distPackage = readJson(`${currentFunctionData.distDir}/package.json`)
const deps = distPackage['dependencies']
expect(deps).toBeDefined()
// firebase-admin not in the template anymore
// expect(deps['firebase-admin']).toBeDefined()
expect(deps['firebase-functions']).toBeDefined()
// check bundled code contains the libcode we added
const bundle = readFile(`${currentFunctionData.distDir}/main.js`)
// check that node modules were not bundled, happens in e2e if nx reset not called
// probably the earlier check for deps in the package.json already detects this scenario too
expect(bundle).not.toContain(`require_firebase_app`)
// our imported lib modules should be inlined in the bundle
expect(bundle).toContain(`function ${libImport1}`)
expect(bundle).toContain(`return "${buildableLibData.projectName}"`)
expect(bundle).toContain(`function ${libImport2}`)
expect(bundle).toContain(`return "${nonbuildableLibData.projectName}"`)
expect(bundle).toContain(`function ${libImport3}`)
expect(bundle).toContain(`return "${subDirBuildableLibData.projectName}"`)
expect(bundle).toContain(`function ${libImport4}`)
expect(bundle).toContain(
`return "${subDirNonbuildableLibData.projectName}"`,
)
})
})
================================================
FILE: e2e/nx-firebase-e2e/tests/test-function.spec.ts
================================================
import { readJson, uniq, checkFilesExist, exists, tmpProjPath } from '@nx/plugin/testing'
import { detectPackageManager } from '@nx/devkit'
import {
ProjectData,
appGeneratorAsync,
cleanAppAsync,
cleanFunctionAsync,
functionGeneratorAsync,
getProjectData,
validateFunctionConfig,
runTargetAsync,
expectStrings,
testDebug,
} from '../test-utils'
// Ensure daemon is disabled for all e2e tests
beforeAll(() => {
process.env['CI'] = 'true'
process.env['NX_DAEMON'] = 'false'
})
// Detect package manager in e2e workspace, not main project
const packageManager = detectPackageManager(tmpProjPath())
const packageLockFile =
packageManager === 'npm'
? 'package-lock.json'
: packageManager === 'pnpm'
? 'pnpm-lock.yaml'
: 'yarn.lock'
function expectedFunctionFiles(projectData: ProjectData) {
const projectPath = projectData.projectDir
return [
`${projectPath}/src/main.ts`,
`${projectPath}/package.json`,
`${projectPath}/project.json`,
`${projectPath}/readme.md`,
`${projectPath}/tsconfig.app.json`,
`${projectPath}/tsconfig.json`,
`${projectPath}/tsconfig.spec.json`,
]
}
//--------------------------------------------------------------------------------------------------
// Function generator e2e tests
//--------------------------------------------------------------------------------------------------
describe('nx-firebase function', () => {
// Track current test's projects for cleanup
let currentAppData: ProjectData | null = null
let currentFunctionData: ProjectData | null = null
// Always run cleanup after each test, even on failure
afterEach(async () => {
// Clean up function first (it depends on app)
if (currentFunctionData) {
try {
await cleanFunctionAsync(currentFunctionData)
} catch (e) {
testDebug(`Function cleanup warning: ${(e as Error).message}`)
}
currentFunctionData = null
}
// Then clean up app
if (currentAppData) {
try {
await cleanAppAsync(currentAppData)
} catch (e) {
testDebug(`App cleanup warning: ${(e as Error).message}`)
}
currentAppData = null
}
})
it('should not create nx-firebase function without --app', async () => {
const functionData = getProjectData('apps', uniq('firebaseFunction'))
const result = await functionGeneratorAsync(functionData)
expect(result.stdout).toContain("Required property 'app' is missing")
// no cleanup required - function wasn't created
})
it('should not create nx-firebase function with an invalid --app', async () => {
const functionData = getProjectData('apps', uniq('firebaseFunction'))
const result = await functionGeneratorAsync(functionData, '--app badapple')
expect(result.stdout).toContain(
"A firebase application project called 'badapple' was not found in this workspace.",
)
// no cleanup required - function wasn't created
})
it('should create nx-firebase function', async () => {
currentAppData = getProjectData('apps', uniq('firebaseApp'))
currentFunctionData = getProjectData('apps', uniq('firebaseFunction'))
await appGeneratorAsync(currentAppData)
await functionGeneratorAsync(
currentFunctionData,
`--app ${currentAppData.projectName}`,
)
// test generator output
expect(() =>
checkFilesExist(...expectedFunctionFiles(currentFunctionData!)),
).not.toThrow()
// check dist files dont exist and we havent accidentally run this test out of sequence
expect(() =>
checkFilesExist(
`dist/${currentFunctionData!.projectDir}/main.js`,
`dist/${currentFunctionData!.projectDir}/package.json`,
`dist/${currentFunctionData!.projectDir}/${packageLockFile}`,
`dist/${currentFunctionData!.projectDir}/.env`,
`dist/${currentFunctionData!.projectDir}/.env.local`,
`dist/${currentFunctionData!.projectDir}/.secret.local`,
),
).toThrow()
validateFunctionConfig(currentFunctionData, currentAppData)
// check that google-cloud/functions-framework is added to package.json if pnpm being used
const packageJson = readJson(`${currentFunctionData.projectDir}/package.json`)
if (packageManager === 'pnpm') {
expect(
packageJson.dependencies['@google-cloud/functions-framework'],
).toBeDefined()
} else {
expect(
packageJson.dependencies['@google-cloud/functions-framework'],
).not.toBeDefined()
}
})
it('should build nx-firebase function from the app', async () => {
currentAppData = getProjectData('apps', uniq('firebaseApp'))
currentFunctionData = getProjectData('apps', uniq('firebaseFunction'))
await appGeneratorAsync(currentAppData)
await functionGeneratorAsync(
currentFunctionData,
`--app ${currentAppData.projectName}`,
)
validateFunctionConfig(currentFunctionData, currentAppData)
const result = await runTargetAsync(currentAppData, 'build')
expect(result.stdout).toContain('Build succeeded.')
expect(() =>
checkFilesExist(
`dist/${currentFunctionData!.projectDir}/main.js`,
`dist/${currentFunctionData!.projectDir}/package.json`,
`dist/${currentFunctionData!.projectDir}/${packageLockFile}`,
`dist/${currentFunctionData!.projectDir}/.env`,
`dist/${currentFunctionData!.projectDir}/.env.local`,
`dist/${currentFunctionData!.projectDir}/.secret.local`,
),
).not.toThrow()
// check that nx preserves the function `package.json` dependencies in the output `package.json`
const packageJson = readJson(
`dist/${currentFunctionData.projectDir}/package.json`,
)
if (packageManager === 'pnpm') {
expect(
packageJson.dependencies['@google-cloud/functions-framework'],
).toBeDefined()
} else {
expect(
packageJson.dependencies['@google-cloud/functions-framework'],
).not.toBeDefined()
}
})
it('should build nx-firebase function directly', async () => {
currentAppData = getProjectData('apps', uniq('firebaseApp'))
currentFunctionData = getProjectData('apps', uniq('firebaseFunction'))
await appGeneratorAsync(currentAppData)
await functionGeneratorAsync(
currentFunctionData,
`--app ${currentAppData.projectName}`,
)
validateFunctionConfig(currentFunctionData, currentAppData)
const result = await runTargetAsync(currentFunctionData, 'build')
expect(result.stdout).toContain(
`nx run ${currentFunctionData.projectName}:build`,
)
// esbuild outputs to stderr for some reason
expect(result.stderr).toContain(`${currentFunctionData.distDir}/main.js`)
// make sure it hasnt bundled node_modules, indicator is that bundle size is megabytes in size
expect(result.stderr).not.toContain(`Mb`)
})
it('should add correct dependencies to the built function package.json', async () => {
currentAppData = getProjectData('apps', uniq('firebaseApp'))
currentFunctionData = getProjectData('apps', uniq('firebaseFunction'))
await appGeneratorAsync(currentAppData)
await functionGeneratorAsync(
currentFunctionData,
`--app ${currentAppData.projectName}`,
)
validateFunctionConfig(currentFunctionData, currentAppData)
const result = await runTargetAsync(currentFunctionData, 'build')
expect(result.stdout).toContain(
`Successfully ran target build for project ${currentFunctionData.projectName}`,
)
expectStrings(result.stderr, [`${currentFunctionData.distDir}/main.js`])
// make sure output build is not megabytes in size, which would mean we've
// bundled node_modules as well
expect(result.stdout).not.toContain('Mb')
const distPackageFile = `${currentFunctionData.distDir}/package.json`
expect(() => checkFilesExist(distPackageFile)).not.toThrow()
const distPackage = readJson(distPackageFile)
const deps = distPackage['dependencies']
expect(deps).toBeDefined()
// firebase-admin is No longer in the default main.ts template
// expect(deps['firebase-admin']).toBeDefined()
expect(deps['firebase-functions']).toBeDefined()
})
it('should add tags to the function project', async () => {
currentAppData = getProjectData('apps', uniq('firebaseApp'))
currentFunctionData = getProjectData('apps', uniq('firebaseFunction'))
await appGeneratorAsync(currentAppData)
await functionGeneratorAsync(
currentFunctionData,
`--app ${currentAppData.projectName} --tags e2etag,e2ePackage`,
)
validateFunctionConfig(currentFunctionData, currentAppData)
const project = readJson(`${currentFunctionData.projectDir}/project.json`)
expect(project.tags).toEqual([
'firebase:function',
`firebase:name:${currentFunctionData.projectName}`,
`firebase:dep:${currentAppData.projectName}`,
'e2etag',
'e2ePackage',
])
})
})
================================================
FILE: e2e/nx-firebase-e2e/tests/test-libraries.spec.ts
================================================
import { readJson, checkFilesExist } from '@nx/plugin/testing'
import {
safeRunNxCommandAsync,
libGeneratorAsync,
buildableLibData,
nonbuildableLibData,
subDirBuildableLibData,
subDirNonbuildableLibData,
} from '../test-utils'
// Ensure daemon is disabled for all e2e tests
beforeAll(() => {
process.env['CI'] = 'true'
process.env['NX_DAEMON'] = 'false'
})
const compileComplete = 'Done compiling TypeScript files for project'
const buildSuccess = 'Successfully ran target build for project'
//--------------------------------------------------------------------------------------------------
// Create Libraries for e2e function generator tests
// NOTE: This test file creates shared libraries that other tests depend on.
// It does NOT clean up after itself as other tests use these libraries.
//--------------------------------------------------------------------------------------------------
describe('setup libraries', () => {
it('should create buildable typescript library', async () => {
await libGeneratorAsync(
buildableLibData,
`--bundler=tsc --importPath="${buildableLibData.npmScope}"`,
)
// no need to test the js library generator, only that it ran ok
expect(() =>
checkFilesExist(`${buildableLibData.projectDir}/package.json`),
).not.toThrow()
const result = await safeRunNxCommandAsync(
`build ${buildableLibData.projectName}`,
)
expect(result.stdout).toContain(compileComplete)
expect(result.stdout).toContain(
`${buildSuccess} ${buildableLibData.projectName}`,
)
})
it('should create buildable typescript library in subdir', async () => {
await libGeneratorAsync(
subDirBuildableLibData,
`--bundler=tsc --importPath="${subDirBuildableLibData.npmScope}"`,
)
// no need to test the js library generator, only that it ran ok
expect(() =>
checkFilesExist(`${subDirBuildableLibData.projectDir}/package.json`),
).not.toThrow()
const result = await safeRunNxCommandAsync(
`build ${subDirBuildableLibData.projectName}`,
)
expect(result.stdout).toContain(compileComplete)
expect(result.stdout).toContain(
`${buildSuccess} ${subDirBuildableLibData.projectName}`,
)
})
it('should create non-buildable typescript library', async () => {
await libGeneratorAsync(
nonbuildableLibData,
`--bundler=none --importPath="${nonbuildableLibData.npmScope}"`,
)
expect(() =>
checkFilesExist(`${nonbuildableLibData.projectDir}/package.json`),
).toThrow()
const project = readJson(`${nonbuildableLibData.projectDir}/project.json`)
expect(project.targets.build).not.toBeDefined()
})
it('should create non-buildable typescript library in subdir', async () => {
await libGeneratorAsync(
subDirNonbuildableLibData,
`--bundler=none --importPath="${subDirNonbuildableLibData.npmScope}"`,
)
expect(() =>
checkFilesExist(`${subDirNonbuildableLibData.projectDir}/package.json`),
).toThrow()
const project = readJson(
`${subDirNonbuildableLibData.projectDir}/project.json`,
)
expect(project.targets.build).not.toBeDefined()
})
})
================================================
FILE: e2e/nx-firebase-e2e/tests/test-migrate.spec.ts
================================================
import { readJson, uniq, updateFile, renameFile } from '@nx/plugin/testing'
import {
ProjectData,
appGeneratorAsync,
cleanAppAsync,
cleanFunctionAsync,
functionGeneratorAsync,
getProjectData,
validateProjectConfig,
validateFunctionConfig,
migrateGeneratorAsync,
expectStrings,
expectNoStrings,
testDebug,
} from '../test-utils'
import { ProjectConfiguration, joinPathFragments } from '@nx/devkit'
// Ensure daemon is disabled for all e2e tests
beforeAll(() => {
process.env['CI'] = 'true'
process.env['NX_DAEMON'] = 'false'
})
//--------------------------------------------------------------------------------------------------
// Test migrations
//--------------------------------------------------------------------------------------------------
describe('nx-firebase migrate', () => {
// Track current test's projects for cleanup
let currentAppData: ProjectData | null = null
let currentFunctionData: ProjectData | null = null
// Always run cleanup after each test, even on failure
afterEach(async () => {
// Clean up function first (it depends on app)
if (currentFunctionData) {
try {
await cleanFunctionAsync(currentFunctionData)
} catch (e) {
testDebug(`Function cleanup warning: ${(e as Error).message}`)
}
currentFunctionData = null
}
// Then clean up app
if (currentAppData) {
try {
await cleanAppAsync(currentAppData)
} catch (e) {
testDebug(`App cleanup warning: ${(e as Error).message}`)
}
currentAppData = null
}
})
it('should successfuly migrate for legacy app', async () => {
currentAppData = getProjectData('apps', uniq('firebaseMigrateApp'))
currentFunctionData = getProjectData('apps', uniq('firebaseMigrateFunction'))
await appGeneratorAsync(currentAppData)
await functionGeneratorAsync(
currentFunctionData,
`--app ${currentAppData.projectName}`,
)
const result = await migrateGeneratorAsync()
expectStrings(result.stdout, [`Running plugin migrations for workspace`])
// modify firebase app to be v2 schema
const projectFile = `${currentAppData.projectDir}/project.json`
const projectJson = readJson<ProjectConfiguration>(projectFile)
projectJson.targets['serve'].executor = 'nx:run-commands'
projectJson.targets[
'getconfig'
].options.command = `nx run ${currentAppData.projectName}:firebase functions:config:get > ${currentAppData.projectDir}/.runtimeconfig.json`
updateFile(projectFile, JSON.stringify(projectJson, null, 3))
// remove environment folder from app
// cant delete in e2e, so lets just rename environment dir for now
renameFile(
joinPathFragments(currentAppData.projectDir, 'environment'),
joinPathFragments(currentAppData.projectDir, uniq('environment')),
)
// modify firebase.json to be v2 schema
const configFile = `firebase.json`
const configJson = readJson(configFile)
delete configJson.functions[0].ignore
updateFile(configFile, JSON.stringify(configJson, null, 3))
// remove globs from function project
const functionFile = `${currentFunctionData.projectDir}/project.json`
const functionJson = readJson<ProjectConfiguration>(functionFile)
const options = functionJson.targets['build'].options
const assets = options.assets as string[]
options.assets = [assets.shift()]
updateFile(functionFile, JSON.stringify(functionJson, null, 3))
// run migrate script
const result2 = await migrateGeneratorAsync()
expectStrings(result2.stdout, [
`MIGRATE Added default environment file 'environment/.env' for firebase app '${currentAppData.projectName}'`,
`MIGRATE Added default environment file 'environment/.env.local' for firebase app '${currentAppData.projectName}'`,
`MIGRATE Added default environment file 'environment/.secret.local' for firebase app '${currentAppData.projectName}'`,
`MIGRATE Updated getconfig target to use ignore environment directory for firebase app '${currentAppData.projectName}'`,
`MIGRATE Updated serve target for firebase app '${currentAppData.projectName}'`,
`MIGRATE Added assets glob for firebase function app '${currentFunctionData.projectName}'`,
`UPDATE firebase.json`,
`CREATE ${currentAppData.projectDir}/environment/.env`,
`CREATE ${currentAppData.projectDir}/environment/.env.local`,
`CREATE ${currentAppData.projectDir}/environment/.secret.local`,
`UPDATE ${currentAppData.projectDir}/project.json`,
])
validateProjectConfig(currentAppData)
//todo: validateFunctionConfig - IMPORTANT since we missed some errors in last release due to this missing test
// where assets glob was malformed
validateFunctionConfig(currentFunctionData, currentAppData)
// run it again
const result3 = await migrateGeneratorAsync()
expectStrings(result.stdout, [`Running plugin migrations for workspace`])
expectNoStrings(result3.stdout, [
`MIGRATE Added default environment file 'environment/.env' for firebase app '${currentAppData.projectName}'`,
`MIGRATE Added default environment file 'environment/.env.local' for firebase app '${currentAppData.projectName}'`,
`MIGRATE Added default environment file 'environment/.secret.local' for firebase app '${currentAppData.projectName}'`,
`MIGRATE Updated getconfig target to use ignore environment directory for firebase app '${currentAppData.projectName}'`,
`MIGRATE Updated serve target for firebase app '${currentAppData.projectName}'`,
`MIGRATE Added assets glob for firebase function app '${currentFunctionData.projectName}'`,
`UPDATE firebase.json`,
`CREATE ${currentAppData.projectDir}/environment/.env`,
`CREATE ${currentAppData.projectDir}/environment/.env.local`,
`CREATE ${currentAppData.projectDir}/environment/.secret.local`,
`UPDATE ${currentAppData.projectDir}/project.json`,
])
})
})
================================================
FILE: e2e/nx-firebase-e2e/tests/test-sync.spec.ts
================================================
import { readJson, uniq, exists, checkFilesExist } from '@nx/plugin/testing'
import {
ProjectData,
appGeneratorAsync,
cleanAppAsync,
cleanFunctionAsync,
functionGeneratorAsync,
getProjectData,
syncGeneratorAsync,
validateProjectConfig,
validateFunctionConfig,
removeProjectAsync,
renameProjectAsync,
expectStrings,
testDebug,
} from '../test-utils'
// Ensure daemon is disabled for all e2e tests
beforeAll(() => {
process.env['CI'] = 'true'
process.env['NX_DAEMON'] = 'false'
})
//--------------------------------------------------------------------------------------------------
// Test the nx-firebase sync generator
//--------------------------------------------------------------------------------------------------
describe('nx-firebase sync', () => {
// Track current test's projects for cleanup
let currentAppData: ProjectData | null = null
let currentAppData2: ProjectData | null = null
let currentFunctionData: ProjectData | null = null
let currentFunctionData2: ProjectData | null = null
let renamedAppData: ProjectData | null = null
let renamedFunctionData: ProjectData | null = null
// Always run cleanup after each test, even on failure
afterEach(async () => {
// Clean up functions first (they depend on apps)
if (renamedFunctionData) {
try {
await cleanFunctionAsync(renamedFunctionData)
} catch (e) {
testDebug(`Renamed function cleanup warning: ${(e as Error).message}`)
}
renamedFunctionData = null
}
if (currentFunctionData2) {
try {
await cleanFunctionAsync(currentFunctionData2)
} catch (e) {
testDebug(`Function2 cleanup warning: ${(e as Error).message}`)
}
currentFunctionData2 = null
}
if (currentFunctionData) {
try {
await cleanFunctionAsync(currentFunctionData)
} catch (e) {
testDebug(`Function cleanup warning: ${(e as Error).message}`)
}
currentFunctionData = null
}
// Then clean up apps
if (renamedAppData) {
try {
await cleanAppAsync(renamedAppData)
} catch (e) {
testDebug(`Renamed app cleanup warning: ${(e as Error).message}`)
}
renamedAppData = null
}
if (currentAppData2) {
try {
await cleanAppAsync(currentAppData2)
} catch (e) {
testDebug(`App2 cleanup warning: ${(e as Error).message}`)
}
currentAppData2 = null
}
if (currentAppData) {
try {
await cleanAppAsync(currentAppData)
} catch (e) {
testDebug(`App cleanup warning: ${(e as Error).message}`)
}
currentAppData = null
}
})
it('should sync firebase workspace with no changes', async () => {
const result = await syncGeneratorAsync()
expect(result.stdout).not.toContain('CHANGE')
expect(result.stdout).not.toContain('UPDATE')
expect(result.stdout).not.toContain('CREATE')
expect(result.stdout).not.toContain('DELETE')
})
describe('--project', () => {
it('should set firebase app project using --project', async () => {
currentAppData = getProjectData('apps', uniq('firebaseSyncApp'))
await appGeneratorAsync(currentAppData)
expect(
readJson(`${currentAppData.projectDir}/project.json`).targets.firebase
.options.command,
).not.toContain(`--project`)
const result = await syncGeneratorAsync(
`--app=${currentAppData.projectName} --project=test`,
)
expectStrings(result.stdout, [
`CHANGE setting firebase target --project for '${currentAppData.projectName}' to '--project=test'`,
`UPDATE ${currentAppData.projectDir}/project.json`,
])
expect(
readJson(`${currentAppData.projectDir}/project.json`).targets.firebase
.options.command,
).toContain(`--project=test`)
})
it('should update firebase app project using --project', async () => {
currentAppData = getProjectData('apps', uniq('firebaseSyncApp'))
await appGeneratorAsync(currentAppData, `--project=test`)
expect(
readJson(`${currentAppData.projectDir}/project.json`).targets.firebase
.options.command,
).toContain(`--project=test`)
const result = await syncGeneratorAsync(
`--app=${currentAppData.projectName} --project=test2`,
)
expectStrings(result.stdout, [
`CHANGE updating firebase target --project for '${currentAppData.projectName}' to '--project=test2'`,
`UPDATE ${currentAppData.projectDir}/project.json`,
])
expect(
readJson(`${currentAppData.projectDir}/project.json`).targets.firebase
.options.command,
).toContain(`--project=test2`)
})
})
describe('deletions', () => {
it('should detect deleted firebase functions', async () => {
currentAppData = getProjectData('apps', uniq('firebaseSyncApp'))
currentFunctionData = getProjectData('apps', uniq('firebaseSyncFunction'))
await appGeneratorAsync(currentAppData)
await functionGeneratorAsync(
currentFunctionData,
`--app ${currentAppData.projectName}`,
)
await removeProjectAsync(currentFunctionData)
// Mark as null since we manually removed it
const removedFunctionData = currentFunctionData
currentFunctionData = null
const result = await syncGeneratorAsync()
expectStrings(result.stdout, [
`CHANGE Firebase function '${removedFunctionData.projectName}' was deleted, removing function codebase from '${currentAppData.configName}'`,
`UPDATE ${currentAppData.configName}`,
])
})
it('should detect deleted firebase apps', async () => {
currentAppData = getProjectData('apps', uniq('firebaseSyncApp'))
currentFunctionData = getProjectData('apps', uniq('firebaseSyncFunction'))
await appGeneratorAsync(currentAppData)
await functionGeneratorAsync(
currentFunctionData,
`--app ${currentAppData.projectName}`,
)
await removeProjectAsync(currentAppData)
// Mark as null since we manually removed it
const removedAppData = currentAppData
currentAppData = null
const result = await syncGeneratorAsync()
expectStrings(result.stdout, [`DELETE ${removedAppData.configName}`])
expectStrings(result.stderr, [
`CHANGE Firebase app '${removedAppData.projectName}' was deleted, firebase:dep tag for firebase function '${currentFunctionData.projectName}' is no longer linked to a Firebase app.`,
])
})
it('should warn when no firebase apps use firebase.json config', async () => {
currentAppData = getProjectData('apps', uniq('firebaseSyncApp'))
currentAppData2 = getProjectData('apps', uniq('firebaseSyncApp'), {
customConfig: true,
})
await appGeneratorAsync(currentAppData)
await appGeneratorAsync(currentAppData2)
// delete the app that used firebase.json
await removeProjectAsync(currentAppData)
const removedAppData = currentAppData
currentAppData = null
const result = await syncGeneratorAsync()
expectStrings(result.stderr, [
`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'.`,
])
})
})
describe('renames', () => {
it('should detect renamed firebase functions', async () => {
currentAppData = getProjectData('apps', uniq('firebaseSyncApp'))
currentFunctionData = getProjectData('apps', uniq('firebaseSyncFunction'))
renamedFunctionData = getProjectData('apps', uniq('firebaseSyncFunction'))
await appGeneratorAsync(currentAppData)
await functionGeneratorAsync(
currentFunctionData,
`--app ${currentAppData.projectName}`,
)
expect(
readJson(`${currentFunctionData.projectDir}/project.json`).targets
.deploy.options.command,
).toContain(`--only functions:${currentFunctionData.projectName}`)
await renameProjectAsync(currentFunctionData, renamedFunctionData)
// The original function is now renamed, clear the reference
const originalFunctionData = currentFunctionData
currentFunctionData = null
const result = await syncGeneratorAsync()
expectStrings(result.stdout, [
`CHANGE Firebase function '${originalFunctionData.projectName}' was renamed to '${renamedFunctionData.projectName}', updated firebase:name tag`,
`CHANGE Firebase function '${originalFunctionData.projectName}' was renamed to '${renamedFunctionData.projectName}', updated codebase in '${currentAppData.configName}'`,
`CHANGE Firebase function '${originalFunctionData.projectName}' was renamed to '${renamedFunctionData.projectName}', updated deploy target to '--only=functions:${renamedFunctionData.projectName}'`,
`UPDATE ${renamedFunctionData.projectDir}/project.json`,
`UPDATE ${currentAppData.configName}`,
])
expect(
readJson(`${renamedFunctionData.projectDir}/project.json`).targets
.deploy.options.command,
).toContain(`--only functions:${renamedFunctionData.projectName}`)
validateFunctionConfig(renamedFunctionData, currentAppData)
})
it('should detect renamed firebase apps', async () => {
currentAppData = getProjectData('apps', uniq('firebaseSyncApp'))
currentFunctionData = getProjectData('apps', uniq('firebaseSyncFunction'))
currentFunctionData2 = getProjectData(
'apps',
uniq('firebaseSyncFunction'),
)
renamedAppData = getProjectData('apps', uniq('firebaseSyncApp'))
await appGeneratorAsync(currentAppData)
await functionGeneratorAsync(
currentFunctionData,
`--app ${currentAppData.projectName}`,
)
await functionGeneratorAsync(
currentFunctionData2,
`--app ${currentAppData.projectName}`,
)
await renameProjectAsync(currentAppData, renamedAppData)
const originalAppData = currentAppData
currentAppData = null
const result = await syncGeneratorAsync()
expectStrings(result.stdout, [
`CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', updated firebase:name tag`,
`CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', updated targets`,
`CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', updated firebase:dep tag in firebase function '${currentFunctionData.projectName}'`,
`CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', updated environment assets path in firebase function '${currentFunctionData.projectName}'`,
`CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', updated firebase:dep tag in firebase function '${currentFunctionData2.projectName}'`,
`CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', updated environment assets path in firebase function '${currentFunctionData2.projectName}'`,
`CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', updated database rules in '${renamedAppData.configName}'`,
`CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', updated firestore rules in '${renamedAppData.configName}'`,
`CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', updated firestore indexes in '${renamedAppData.configName}'`,
`CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', updated storage rules in '${renamedAppData.configName}'`,
`CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', updated firebase deploy command in firebase function '${currentFunctionData.projectName}'`,
`CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', updated firebase deploy command in firebase function '${currentFunctionData2.projectName}'`,
`UPDATE ${renamedAppData.configName}`,
`UPDATE ${renamedAppData.projectDir}/project.json`,
`UPDATE ${currentFunctionData.projectDir}/project.json`,
`UPDATE ${currentFunctionData2.projectDir}/project.json`,
])
expectStrings(result.stderr, [
`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?`,
])
// we should not rename config if it is called firebase.json
expect(result.stdout).not.toContain(
`CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', renamed config file to '${renamedAppData.configName}'`,
)
expect(result.stdout).not.toContain(`DELETE ${originalAppData.configName}`)
expect(result.stdout).not.toContain(
`CREATE ${renamedAppData.configName}`,
)
// check that app project has correct --config setting after rename
expect(
readJson(`${renamedAppData.projectDir}/project.json`).targets.firebase
.options.command,
).toContain(`--config=${renamedAppData.configName}`)
// check rename was successful
validateProjectConfig(renamedAppData)
validateFunctionConfig(currentFunctionData, renamedAppData)
validateFunctionConfig(currentFunctionData2, renamedAppData)
// run another sync to check there should be no orphaned functions from an app rename
const result2 = await syncGeneratorAsync()
expect(result2.stderr).not.toContain(
'is no longer linked to a Firebase app',
)
expect(result2.stdout).not.toContain('UPDATE')
})
it('should detect renamed firebase apps & functions', async () => {
currentAppData = getProjectData('apps', uniq('firebaseSyncApp'))
currentFunctionData = getProjectData('apps', uniq('firebaseSyncFunction'))
renamedFunctionData = getProjectData('apps', uniq('firebaseSyncFunction'))
renamedAppData = getProjectData('apps', uniq('firebaseSyncApp'))
await appGeneratorAsync(currentAppData)
await functionGeneratorAsync(
currentFunctionData,
`--app ${currentAppData.projectName}`,
)
// rename app & function
await renameProjectAsync(currentAppData, renamedAppData)
await renameProjectAsync(currentFunctionData, renamedFunctionData)
const originalAppData = currentAppData
const originalFunctionData = currentFunctionData
currentAppData = null
currentFunctionData = null
const result = await syncGeneratorAsync()
expectStrings(result.stdout, [
`CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', updated firebase:name tag`,
`CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', updated targets`,
`CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', updated firebase:dep tag in firebase function '${renamedFunctionData.projectName}'`,
`CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', updated environment assets path in firebase function '${renamedFunctionData.projectName}'`,
`CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', updated database rules in '${renamedAppData.configName}'`,
`CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', updated firestore rules in '${renamedAppData.configName}'`,
`CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', updated firestore indexes in '${renamedAppData.configName}'`,
`CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', updated storage rules in '${renamedAppData.configName}'`,
`CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', updated firebase deploy command in firebase function '${renamedFunctionData.projectName}'`,
`CHANGE Firebase function '${originalFunctionData.projectName}' was renamed to '${renamedFunctionData.projectName}', updated firebase:name tag`,
`CHANGE Firebase function '${originalFunctionData.projectName}' was renamed to '${renamedFunctionData.projectName}', updated deploy target to '--only=functions:${renamedFunctionData.projectName}'`,
`CHANGE Firebase function '${originalFunctionData.projectName}' was renamed to '${renamedFunctionData.projectName}', updated codebase in '${renamedAppData.configName}'`,
`UPDATE ${renamedAppData.projectDir}/project.json`,
`UPDATE ${renamedFunctionData.projectDir}/project.json`,
])
expectStrings(result.stderr, [
`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?`,
])
// we should not rename config if it is called firebase.json
expect(result.stdout).not.toContain(
`CHANGE Firebase app '${originalAppData.projectName}' was renamed to '${renamedAppData.projectName}', renamed config file to '${renamedAppData.configName}'`,
)
expect(result.stdout).not.toContain(`DELETE ${originalAppData.configName}`)
expect(result.stdout).not.toContain(
`CREATE ${renamedAppData.configName}`,
)
// check that app project has correct --config setting after rename
expect(
readJson(`${renamedAppData.projectDir}/project.json`).targets.firebase
.options.command,
).toContain(`--config=${renamedAppData.configName}`)
// check that function project has correct --config setting after rename
expect(
readJson(`${renamedFunctionData.projectDir}/project.json`).targets
.deploy.options.command,
).toContain(`--only functions:${renamedFunctionData.projectName}`)
// check rename was successful
validateProjectConfig(renamedAppData)
validateFunctionConfig(renamedFunctionData, renamedAppData)
})
it('should rename configs for renamed firebase apps when multiple apps in workspace', async () => {
// Nx 20 exists() function does not use the e2e tmpPath (bug?) so we need to use checkFilesExist instead
// expect(exists('firebase.json')).toBe(false)
expect(() => checkFilesExist('firebase.json')).toThrow()
// create first project that will have the primary firebase.json config
currentAppData = getProjectData('apps', uniq('firebaseSyncApp'))
currentFunctionData = getProjectData('apps', uniq('firebaseSyncFunction'))
await appGeneratorAsync(currentAppData)
expect(currentAppData.configName).toEqual('firebase.json')
expect(
readJson(`${currentAppData.projectDir}/project.json`).targets.firebase
.options.command,
).toContain(`--config=firebase.json`)
// expect(exists('firebase.json')).toBe(true)
expect(() => checkFilesExist('firebase.json')).not.toThrow()
// generate second app after first app is generated so that first config is detected
currentAppData2 = getProjectData('apps', uniq('firebaseSyncApp'), {
customConfig: true,
})
renamedAppData = getProjectData('apps', uniq('firebaseSyncApp'), {
customConfig: true,
})
expect(currentAppData2.configName).not.toEqual('firebase.json')
expect(renamedAppData.configName).not.toEqual('firebase.json')
await appGeneratorAsync(currentAppData2)
await functionGeneratorAsync(
currentFunctionData,
`--app ${currentAppData2.projectName}`,
)
expect(
readJson(`${currentAppData2.projectDir}/project.json`).targets.firebase
.options.command,
).not.toContain(`--config=firebase.json`)
expect(
readJson(`${currentAppData2.projectDir}/project.json`).targets.firebase
.options.command,
).toContain(`--config=${currentAppData2.configName}`)
await renameProjectAsync(currentAppData2, renamedAppData)
const originalAppData2 = currentAppData2
currentAppData2 = null
const result = await syncGeneratorAsync()
expectStrings(result.stdout, [
`CHANGE Firebase app '${originalAppData2.projectName}' was renamed to '${renamedAppData.projectName}', renamed config file to '${renamedAppData.configName}'`,
`CHANGE Firebase app '${originalAppData2.projectName}' was renamed to '${renamedAppData.projectName}', updated firebase:name tag`,
`CHANGE Firebase app '${originalAppData2.projectName}' was renamed to '${renamedAppData.projectName}', updated firebase:dep tag in firebase function '${currentFunctionData.projectName}'`,
`CHANGE Firebase app '${originalAppData2.projectName}' was renamed to '${renamedAppData.projectName}', updated database rules in '${renamedAppData.configName}'`,
`CHANGE Firebase app '${originalAppData2.projectName}' was renamed to '${renamedAppData.projectName}', updated firestore rules in '${renamedAppData.configName}'`,
`CHANGE Firebase app '${originalAppData2.projectName}' was renamed to '${renamedAppData.projectName}', updated firestore indexes in '${renamedAppData.configName}'`,
`CHANGE Firebase app '${originalAppData2.projectName}' was renamed to '${renamedAppData.projectName}', updated storage rules in '${renamedAppData.configName}'`,
`CHANGE Firebase app '${originalAppData2.projectName}' was renamed to '${renamedAppData.projectName}', updated firebase deploy command in firebase function '${currentFunctionData.projectName}'`,
`UPDATE ${renamedAppData.projectDir}/project.json`,
`UPDATE ${currentFunctionData.projectDir}/project.json`,
`DELETE ${originalAppData2.configName}`,
`CREATE ${renamedAppData.configName}`,
])
expectStrings(result.stderr, [
`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?`,
])
// check that app project has correct --config setting after rename
expect(
readJson(`${renamedAppData.projectDir}/project.json`).targets.firebase
.options.command,
).toContain(`--config=${renamedAppData.configName}`)
// run another sync to check there should be no orphaned functions from an app rename
const result2 = await syncGeneratorAsync()
expect(result2.stderr).not.toContain(
'is no longer linked to a Firebase app',
)
expect(result2.stdout).not.toContain('UPDATE')
})
})
})
================================================
FILE: e2e/nx-firebase-e2e/tests/test-targets.spec.ts
================================================
import { uniq } from '@nx/plugin/testing'
import {
ProjectData,
appGeneratorAsync,
cleanAppAsync,
cleanFunctionAsync,
functionGeneratorAsync,
getProjectData,
runTargetAsync,
expectStrings,
testDebug,
} from '../test-utils'
// Ensure daemon is disabled for all e2e tests
beforeAll(() => {
process.env['CI'] = 'true'
process.env['NX_DAEMON'] = 'false'
})
// Since targets is the last e2e test suite to run, we can disable cleanup here to leave the e2e
// tmp workspace intact for inspection if needed.
const ENABLE_CLEANUP = false
//--------------------------------------------------------------------------------------------------
// Test app targets
//--------------------------------------------------------------------------------------------------
describe('nx-firebase app targets', () => {
// Track current test's projects for cleanup
let currentAppData: ProjectData | null = null
let currentFunctionData: ProjectData | null = null
let currentFunctionData2: ProjectData | null = null
// Only cleanup if enabled
if (ENABLE_CLEANUP) {
// Always run cleanup after each test, even on failure
afterEach(async () => {
// Clean up functions first (they depend on apps)
if (currentFunctionData2) {
try {
await cleanFunctionAsync(currentFunctionData2)
} catch (e) {
testDebug(`Function2 cleanup warning: ${(e as Error).message}`)
}
currentFunctionData2 = null
}
if (currentFunctionData) {
try {
await cleanFunctionAsync(currentFunctionData)
} catch (e) {
testDebug(`Function cleanup warning: ${(e as Error).message}`)
}
currentFunctionData = null
}
// Then clean up app
if (currentAppData) {
try {
await cleanAppAsync(currentAppData)
} catch (e) {
testDebug(`App cleanup warning: ${(e as Error).message}`)
}
currentAppData = null
}
})
}
it('should run lint target for app', async () => {
currentAppData = getProjectData('apps', uniq('firebaseTargetsApp'))
currentFunctionData = getProjectData('apps', uniq('firebaseTargetsFunction'))
currentFunctionData2 = getProjectData(
'apps',
uniq('firebaseTargetsFunction'),
)
await appGeneratorAsync(currentAppData)
await functionGeneratorAsync(
currentFunctionData,
`--app ${currentAppData.projectName}`,
)
await functionGeneratorAsync(
currentFunctionData2,
`--app ${currentAppData.projectName}`,
)
const result = await runTargetAsync(currentAppData, 'lint')
expectStrings(result.stdout, [
`nx run ${currentAppData.projectName}:lint`,
`nx run ${currentFunctionData.projectName}:lint`,
`nx run ${currentFunctionData2.projectName}:lint`,
// SM March 2024: The firebase SDK templates dont pass linting !
// `All files pass linting`,
`Successfully ran target lint for 2 projects`,
`Successfully ran target lint for project ${currentAppData.projectName}`,
])
})
it('should run test target for app', async () => {
currentAppData = getProjectData('apps', uniq('firebaseTargetsApp'))
currentFunctionData = getProjectData('apps', uniq('firebaseTargetsFunction'))
currentFunctionData2 = getProjectData(
'apps',
uniq('firebaseTargetsFunction'),
)
await appGeneratorAsync(currentAppData)
await functionGeneratorAsync(
currentFunctionData,
`--app ${currentAppData.projectName}`,
)
await functionGeneratorAsync(
currentFunctionData2,
`--app ${currentAppData.projectName}`,
)
const result = await runTargetAsync(currentAppData, 'test')
expectStrings(result.stdout, [
`nx run ${currentAppData.projectName}:test`,
`Running target test for 2 projects`,
`nx run ${currentFunctionData.projectName}:test`,
`nx run ${currentFunctionData2.projectName}:test`,
`Successfully ran target test for 2 projects`,
`Successfully ran target test for project ${currentAppData.projectName}`,
])
})
it('should run deploy target for app', async () => {
currentAppData = getProjectData('apps', uniq('firebaseTargetsApp'))
currentFunctionData = getProjectData('apps', uniq('firebaseDepsFunction'))
currentFunctionData2 = getProjectData('apps', uniq('firebaseDepsFunction'))
await appGeneratorAsync(currentAppData)
await functionGeneratorAsync(
currentFunctionData,
`--app ${currentAppData.projectName}`,
)
await functionGeneratorAsync(
currentFunctionData2,
`--app ${currentAppData.projectName}`,
)
// deploy
gitextract_197jq99g/ ├── .editorconfig ├── .github/ │ ├── dependabot.yml │ └── workflows/ │ ├── ci.yml │ └── publish.yml ├── .gitignore ├── .nvmrc ├── .prettierignore ├── .prettierrc ├── .vscode/ │ ├── extensions.json │ ├── settings.json │ └── tasks.json ├── CHANGELOG.md ├── README.md ├── docs/ │ ├── nx-firebase-applications.md │ ├── nx-firebase-databases.md │ ├── nx-firebase-emulators.md │ ├── nx-firebase-functions-environment.md │ ├── nx-firebase-functions.md │ ├── nx-firebase-hosting.md │ ├── nx-firebase-migrations.md │ ├── nx-firebase-project-structure.md │ ├── nx-firebase-projects.md │ ├── nx-firebase-sync.md │ ├── nx-libraries.md │ ├── nx-migration.md │ ├── nx-plugin-commands.md │ ├── nx-setup-mac.md │ ├── nx-workspace-layout.md │ └── user-guide.md ├── e2e/ │ ├── compat/ │ │ ├── eslint.config.mjs │ │ ├── jest.config.ts │ │ ├── project.json │ │ ├── src/ │ │ │ ├── app/ │ │ │ │ ├── config.ts │ │ │ │ ├── setup.ts │ │ │ │ ├── test.ts │ │ │ │ ├── utils/ │ │ │ │ │ ├── cache.ts │ │ │ │ │ ├── cwd.ts │ │ │ │ │ ├── exec.ts │ │ │ │ │ ├── jest-ish.ts │ │ │ │ │ ├── log.ts │ │ │ │ │ └── utils.ts │ │ │ │ ├── versions.ts │ │ │ │ └── workspace.ts │ │ │ ├── assets/ │ │ │ │ └── .gitkeep │ │ │ ├── environments/ │ │ │ │ ├── environment.prod.ts │ │ │ │ └── environment.ts │ │ │ └── main.ts │ │ ├── tsconfig.app.json │ │ ├── tsconfig.json │ │ ├── tsconfig.spec.json │ │ └── webpack.config.js │ └── nx-firebase-e2e/ │ ├── jest.config.js │ ├── jest.globalSetup.js │ ├── jest.globalTeardown.js │ ├── jest.testSequencer.js │ ├── project.json │ ├── test-utils/ │ │ ├── index.ts │ │ ├── test-shared-data.ts │ │ ├── test-utils-apps.ts │ │ ├── test-utils-commands.ts │ │ ├── test-utils-functions.ts │ │ ├── test-utils-helpers.ts │ │ ├── test-utils-imports.ts │ │ ├── test-utils-logger.ts │ │ └── test-utils-project-data.ts │ ├── tests/ │ │ ├── test-application.spec.ts │ │ ├── test-bundler.spec.ts │ │ ├── test-function.spec.ts │ │ ├── test-libraries.spec.ts │ │ ├── test-migrate.spec.ts │ │ ├── test-sync.spec.ts │ │ ├── test-targets.spec.ts │ │ └── test-workspace.spec.ts │ ├── tsconfig.json │ └── tsconfig.spec.json ├── eslint.config.mjs ├── jest.config.ts ├── jest.preset.js ├── migrations.json ├── nx.json ├── package.json ├── packages/ │ └── nx-firebase/ │ ├── README.md │ ├── eslint.config.mjs │ ├── executors.json │ ├── generators.json │ ├── jest.config.ts │ ├── jest.setup.ts │ ├── package.json │ ├── project.json │ ├── src/ │ │ ├── __generated__/ │ │ │ └── nx-firebase-versions.ts │ │ ├── executors/ │ │ │ └── serve/ │ │ │ ├── schema.d.ts │ │ │ ├── schema.json │ │ │ ├── serve.spec.ts │ │ │ └── serve.ts │ │ ├── generators/ │ │ │ ├── application/ │ │ │ │ ├── application.spec.ts │ │ │ │ ├── application.ts │ │ │ │ ├── files/ │ │ │ │ │ ├── database.rules.json │ │ │ │ │ ├── environment/ │ │ │ │ │ │ └── .secret.local │ │ │ │ │ ├── firestore.indexes.json │ │ │ │ │ ├── firestore.rules │ │ │ │ │ ├── public/ │ │ │ │ │ │ ├── 404.html │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── readme.md__tmpl__ │ │ │ │ │ └── storage.rules │ │ │ │ ├── files_firebase/ │ │ │ │ │ └── firebase.json__tmpl__ │ │ │ │ ├── files_firebaserc/ │ │ │ │ │ └── .firebaserc__tmpl__ │ │ │ │ ├── files_workspace/ │ │ │ │ │ └── firebase.__projectName__.json__tmpl__ │ │ │ │ ├── lib/ │ │ │ │ │ ├── create-files.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── schema.d.ts │ │ │ │ └── schema.json │ │ │ ├── function/ │ │ │ │ ├── files/ │ │ │ │ │ ├── package.json__tmpl__ │ │ │ │ │ ├── readme.md__tmpl__ │ │ │ │ │ └── src/ │ │ │ │ │ └── main.ts__tmpl__ │ │ │ │ ├── function.spec.ts │ │ │ │ ├── function.ts │ │ │ │ ├── lib/ │ │ │ │ │ ├── add-function-config.ts │ │ │ │ │ ├── create-files.ts │ │ │ │ │ ├── delete-files.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── update-project.ts │ │ │ │ ├── schema.d.ts │ │ │ │ └── schema.json │ │ │ ├── init/ │ │ │ │ ├── init.spec.ts │ │ │ │ ├── init.ts │ │ │ │ ├── lib/ │ │ │ │ │ ├── add-dependencies.ts │ │ │ │ │ ├── add-git-ignore-entry.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── schema.d.ts │ │ │ │ └── schema.json │ │ │ ├── migrate/ │ │ │ │ ├── migrate.spec.ts │ │ │ │ ├── migrate.ts │ │ │ │ ├── schema.d.ts │ │ │ │ └── schema.json │ │ │ └── sync/ │ │ │ ├── lib/ │ │ │ │ ├── firebase-changes.ts │ │ │ │ ├── firebase-configs.ts │ │ │ │ ├── firebase-projects.ts │ │ │ │ ├── firebase-workspace.ts │ │ │ │ ├── index.ts │ │ │ │ ├── tags.ts │ │ │ │ └── update-targets.ts │ │ │ ├── schema.d.ts │ │ │ ├── schema.json │ │ │ ├── sync.spec.ts │ │ │ └── sync.ts │ │ ├── index.ts │ │ ├── types/ │ │ │ ├── index.ts │ │ │ └── lib/ │ │ │ ├── firebase-config.ts │ │ │ ├── firebase-function.ts │ │ │ └── firebase-workspace.ts │ │ └── utils/ │ │ ├── debug.ts │ │ ├── firebase-config.ts │ │ ├── index.ts │ │ ├── project-name.ts │ │ ├── update-tsconfig.ts │ │ └── workspace.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ └── tsconfig.spec.json ├── tools/ │ ├── generate-package-versions.js │ └── tsconfig.tools.json └── tsconfig.base.json
SYMBOL INDEX (157 symbols across 55 files)
FILE: e2e/compat/src/app/config.ts
type PackageManager (line 4) | type PackageManager = 'npm' | 'yarn' | 'pnpm'
constant PACKAGE_MANAGER (line 5) | const PACKAGE_MANAGER: PackageManager = 'pnpm'
constant CACHE_DIR (line 8) | const CACHE_DIR = `${defaultCwd}/.nx-firebase`
FILE: e2e/compat/src/app/setup.ts
function setupNxWorkspace (line 21) | async function setupNxWorkspace(cache: Cache, force = false) {
FILE: e2e/compat/src/app/test.ts
constant DELETE_AFTER_TEST (line 19) | const DELETE_AFTER_TEST = false
function testPlugin (line 26) | async function testPlugin(cache: Cache) {
function clean (line 131) | function clean() {
function testNxVersion (line 137) | async function testNxVersion(cache: Cache) {
FILE: e2e/compat/src/app/utils/cache.ts
type Cache (line 8) | type Cache = {
function getCache (line 28) | function getCache(nxVersion: string, pluginVersion: string): Cache {
function isNxVersionSince (line 55) | function isNxVersionSince(cache: Cache, nxVersion: string) {
FILE: e2e/compat/src/app/utils/exec.ts
function customExec (line 11) | async function customExec(
function runNxCommandAsync (line 48) | async function runNxCommandAsync(command: string, dir?: string) {
FILE: e2e/compat/src/app/utils/jest-ish.ts
function etc (line 10) | function etc(content: string, expected: string) {
function expectToContainInner (line 15) | function expectToContainInner(content: string, expected: string | string...
function expectToContain (line 28) | function expectToContain(content: string, expected: string | string[]) {
function expectToNotContain (line 42) | function expectToNotContain(
function it (line 60) | async function it(testName: string, testFunc: () => Promise<void>) {
FILE: e2e/compat/src/app/utils/log.ts
constant ENABLE_LOG (line 1) | const ENABLE_LOG = false
constant DEFAULT_LOG_FILE (line 2) | const DEFAULT_LOG_FILE = `${process.cwd()}/e2e.log`
constant LOG_FILE (line 8) | let LOG_FILE: string | undefined
function writeLog (line 10) | function writeLog(msg: string) {
function setLogFile (line 15) | function setLogFile(path?: string) {
function log (line 24) | function log(msg: string) {
function info (line 31) | function info(msg: string) {
function time (line 37) | function time(ms: number) {
constant GREEN_FG (line 42) | const GREEN_FG = '\x1b[32m'
constant RED_FG (line 43) | const RED_FG = '\x1b[31m'
constant RESET_FG (line 44) | const RESET_FG = '\x1b[0m'
function green (line 46) | function green(text: string) {
function red (line 49) | function red(text: string) {
FILE: e2e/compat/src/app/utils/utils.ts
function setCwd (line 10) | function setCwd(dir: string) {
function ensureDir (line 23) | function ensureDir(dir: string) {
function fileExists (line 32) | function fileExists(path: string) {
function deleteFile (line 36) | function deleteFile(path: string) {
function deleteDir (line 41) | function deleteDir(path: string) {
function addContentToTextFile (line 52) | function addContentToTextFile(
function getFileSize (line 67) | function getFileSize(path: string) {
FILE: e2e/compat/src/app/workspace.ts
function createTestDir (line 8) | function createTestDir(testDir: string) {
function workspaceExists (line 13) | function workspaceExists(workspaceDir: string) {
function cleanWorkspace (line 17) | function cleanWorkspace(dir: string) {
function installPlugin (line 24) | async function installPlugin(cache: Cache) {
function createWorkspace (line 62) | async function createWorkspace(cache: Cache) {
FILE: e2e/compat/src/main.ts
type CmdOptions (line 23) | type CmdOptions = {
function main (line 29) | async function main(options: CmdOptions) {
FILE: e2e/nx-firebase-e2e/jest.testSequencer.js
class CustomSequencer (line 7) | class CustomSequencer extends Sequencer {
method sort (line 14) | sort(tests) {
FILE: e2e/nx-firebase-e2e/test-utils/test-utils-apps.ts
function expectedAppProjectTargets (line 4) | function expectedAppProjectTargets(appProject: ProjectData) {
function validateProjectConfig (line 83) | function validateProjectConfig(appProject: ProjectData) {
FILE: e2e/nx-firebase-e2e/test-utils/test-utils-commands.ts
constant STRIP_ANSI_MATCHER (line 6) | const STRIP_ANSI_MATCHER =
function safeRunNxCommandAsync (line 9) | async function safeRunNxCommandAsync(cmd: string) {
function runTargetAsync (line 43) | async function runTargetAsync(
function removeProjectAsync (line 61) | async function removeProjectAsync(projectData: ProjectData) {
function renameProjectAsync (line 72) | async function renameProjectAsync(
function appGeneratorAsync (line 91) | async function appGeneratorAsync(
function functionGeneratorAsync (line 102) | async function functionGeneratorAsync(
function libGeneratorAsync (line 113) | async function libGeneratorAsync(
function syncGeneratorAsync (line 124) | async function syncGeneratorAsync(params: string = '') {
function migrateGeneratorAsync (line 129) | async function migrateGeneratorAsync(params: string = '') {
function cleanAppAsync (line 136) | async function cleanAppAsync(
function cleanFunctionAsync (line 157) | async function cleanFunctionAsync(projectData: ProjectData) {
FILE: e2e/nx-firebase-e2e/test-utils/test-utils-functions.ts
function expectedFunctionProjectTargets (line 4) | function expectedFunctionProjectTargets(
function validateFunctionConfig (line 62) | function validateFunctionConfig(
FILE: e2e/nx-firebase-e2e/test-utils/test-utils-helpers.ts
function expectStrings (line 1) | function expectStrings(input: string, contains: string[]) {
function expectNoStrings (line 7) | function expectNoStrings(input: string, contains: string[]) {
FILE: e2e/nx-firebase-e2e/test-utils/test-utils-imports.ts
constant IMPORT_MATCH (line 3) | const IMPORT_MATCH = `import * as logger from "firebase-functions/logger";`
function getMainTs (line 5) | function getMainTs() {
function getLibImport (line 32) | function getLibImport(projectData: ProjectData) {
function addImport (line 43) | function addImport(mainTs: string, addition: string) {
FILE: e2e/nx-firebase-e2e/test-utils/test-utils-logger.ts
constant ENABLE_TEST_DEBUG_INFO (line 1) | const ENABLE_TEST_DEBUG_INFO = true
constant GREEN_FG (line 4) | const GREEN_FG = '\x1b[32m'
constant RED_FG (line 5) | const RED_FG = '\x1b[31m'
constant RESET_FG (line 6) | const RESET_FG = '\x1b[0m'
function green (line 8) | function green(text: string) {
function red (line 11) | function red(text: string) {
function testDebug (line 15) | function testDebug(info: string) {
FILE: e2e/nx-firebase-e2e/test-utils/test-utils-project-data.ts
constant NPM_SCOPE (line 3) | const NPM_SCOPE = '@proj'
type ProjectData (line 5) | interface ProjectData {
function getProjectData (line 38) | function getProjectData(
FILE: e2e/nx-firebase-e2e/tests/test-application.spec.ts
function expectedAppFiles (line 24) | function expectedAppFiles(projectData: ProjectData) {
FILE: e2e/nx-firebase-e2e/tests/test-function.spec.ts
function expectedFunctionFiles (line 32) | function expectedFunctionFiles(projectData: ProjectData) {
FILE: e2e/nx-firebase-e2e/tests/test-targets.spec.ts
constant ENABLE_CLEANUP (line 23) | const ENABLE_CLEANUP = false
FILE: packages/nx-firebase/src/executors/serve/schema.d.ts
type FirebaseServeExecutorSchema (line 2) | interface FirebaseServeExecutorSchema {
FILE: packages/nx-firebase/src/executors/serve/serve.ts
type NxRunCommandsTargetConfiguration (line 10) | type NxRunCommandsTargetConfiguration = TargetConfiguration<{
type ValidTarget (line 15) | type ValidTarget = 'firebase' | 'watch' | 'emulate'
function getCommandFromTarget (line 17) | function getCommandFromTarget(
function runFirebaseServeExecutor (line 47) | async function runFirebaseServeExecutor(
FILE: packages/nx-firebase/src/generators/application/application.ts
function normalizeOptions (line 16) | async function normalizeOptions(
function applicationGenerator (line 83) | async function applicationGenerator(
FILE: packages/nx-firebase/src/generators/application/lib/create-files.ts
function createFiles (line 11) | function createFiles(tree: Tree, options: NormalizedSchema): void {
FILE: packages/nx-firebase/src/generators/application/schema.d.ts
type Schema (line 1) | interface Schema {
type NormalizedSchema (line 10) | interface NormalizedSchema extends Schema {
FILE: packages/nx-firebase/src/generators/function/function.ts
function normalizeOptions (line 20) | async function normalizeOptions(
function functionGenerator (line 88) | async function functionGenerator(
FILE: packages/nx-firebase/src/generators/function/lib/add-function-config.ts
function addFunctionConfig (line 5) | function addFunctionConfig(host: Tree, options: NormalizedSchema) {
FILE: packages/nx-firebase/src/generators/function/lib/create-files.ts
function createFiles (line 18) | function createFiles(host: Tree, options: NormalizedSchema): void {
FILE: packages/nx-firebase/src/generators/function/lib/delete-files.ts
function deleteFiles (line 9) | function deleteFiles(host: Tree, options: NormalizedSchema): void {
FILE: packages/nx-firebase/src/generators/function/lib/update-project.ts
function updateProject (line 9) | function updateProject(host: Tree, options: NormalizedSchema): void {
FILE: packages/nx-firebase/src/generators/function/schema.d.ts
type Schema (line 3) | interface Schema {
type NormalizedSchema (line 22) | interface NormalizedSchema extends Schema {
FILE: packages/nx-firebase/src/generators/init/init.ts
function initGenerator (line 18) | async function initGenerator(
FILE: packages/nx-firebase/src/generators/init/lib/add-dependencies.ts
function addDependencies (line 12) | function addDependencies(tree: Tree): GeneratorCallback {
FILE: packages/nx-firebase/src/generators/init/lib/add-git-ignore-entry.ts
function addIgnoreRules (line 24) | function addIgnoreRules(host: Tree, ignoreRules: string[], ignoreFile: s...
function addGitIgnore (line 43) | function addGitIgnore(host: Tree) {
function addNxIgnore (line 47) | function addNxIgnore(host: Tree) {
FILE: packages/nx-firebase/src/generators/init/schema.d.ts
type InitGeneratorOptions (line 1) | type InitGeneratorOptions = {
FILE: packages/nx-firebase/src/generators/migrate/migrate.ts
function migrateGenerator (line 24) | async function migrateGenerator(
FILE: packages/nx-firebase/src/generators/migrate/schema.d.ts
type MigrateGeneratorSchema (line 2) | interface MigrateGeneratorSchema {}
FILE: packages/nx-firebase/src/generators/sync/lib/firebase-changes.ts
function isRenamed (line 18) | function isRenamed(project: ProjectConfiguration): string | undefined {
function getFirebaseChanges (line 32) | function getFirebaseChanges(
FILE: packages/nx-firebase/src/generators/sync/lib/firebase-configs.ts
constant FIREBASE_CONFIG_FILE_MATCHER (line 17) | const FIREBASE_CONFIG_FILE_MATCHER = /(firebase)(\S*)(.json)/
function getFirebaseConfigs (line 19) | function getFirebaseConfigs(
FILE: packages/nx-firebase/src/generators/sync/lib/firebase-projects.ts
constant FIREBASE_PROJECT_MATCHER (line 13) | const FIREBASE_PROJECT_MATCHER = /(--project[ =])([^\s]+)/
constant FIREBASE_COMMAND_MATCHER (line 14) | const FIREBASE_COMMAND_MATCHER = /(firebase)/
function isFirebaseApp (line 16) | function isFirebaseApp(project: ProjectConfiguration) {
function isFirebaseFunction (line 20) | function isFirebaseFunction(project: ProjectConfiguration) {
function getFirebaseProjects (line 31) | function getFirebaseProjects(tree: Tree): FirebaseProjects {
function updateFirebaseAppDeployProject (line 58) | function updateFirebaseAppDeployProject(
FILE: packages/nx-firebase/src/generators/sync/lib/firebase-workspace.ts
function getFirebaseWorkspace (line 8) | function getFirebaseWorkspace(tree: Tree): FirebaseWorkspace {
FILE: packages/nx-firebase/src/generators/sync/lib/tags.ts
function getFirebaseScopeFromTag (line 16) | function getFirebaseScopeFromTag(
function updateFirebaseProjectNameTag (line 44) | function updateFirebaseProjectNameTag(
FILE: packages/nx-firebase/src/generators/sync/lib/update-targets.ts
type NxRunCommandsTargetConfiguration (line 8) | type NxRunCommandsTargetConfiguration = TargetConfiguration<{
type ValidTarget (line 13) | type ValidTarget =
function renameCommandForTarget (line 24) | function renameCommandForTarget(
FILE: packages/nx-firebase/src/generators/sync/schema.d.ts
type SyncGeneratorSchema (line 1) | interface SyncGeneratorSchema {
FILE: packages/nx-firebase/src/generators/sync/sync.ts
constant FUNCTIONS_DEPLOY_MATCHER (line 28) | const FUNCTIONS_DEPLOY_MATCHER = /(--only[ =]functions:)([^\s]+)/
function syncGenerator (line 34) | async function syncGenerator(
FILE: packages/nx-firebase/src/types/lib/firebase-config.ts
type FirebaseFunction (line 1) | interface FirebaseFunction {
type FirebaseHosting (line 9) | interface FirebaseHosting {
type FirebaseConfig (line 44) | interface FirebaseConfig {
FILE: packages/nx-firebase/src/types/lib/firebase-function.ts
type FunctionAssetsGlob (line 1) | type FunctionAssetsGlob = {
type FunctionAssetsEntry (line 6) | type FunctionAssetsEntry = string | FunctionAssetsGlob
FILE: packages/nx-firebase/src/types/lib/firebase-workspace.ts
constant CONFIG_NO_APP (line 5) | const CONFIG_NO_APP = 'CONFIG_NO_APP'
type FirebaseProjects (line 7) | interface FirebaseProjects {
type FirebaseConfigs (line 18) | interface FirebaseConfigs {
type FirebaseChanges (line 33) | interface FirebaseChanges {
type FirebaseWorkspace (line 53) | type FirebaseWorkspace = FirebaseProjects &
FILE: packages/nx-firebase/src/utils/debug.ts
constant ENABLE_DEBUG_INFO (line 4) | const ENABLE_DEBUG_INFO = false
function debugInfo (line 6) | function debugInfo(info: string) {
function mapKeys (line 12) | function mapKeys<T, R>(map: Map<T, R>) {
function mapValues (line 16) | function mapValues<T, R>(map: Map<T, R>) {
function mapEntries (line 20) | function mapEntries<T, R>(map: Map<T, R>) {
FILE: packages/nx-firebase/src/utils/firebase-config.ts
constant FIREBASE_TARGET_CONFIG_MATCHER (line 3) | const FIREBASE_TARGET_CONFIG_MATCHER = /(--config[ =])([^\s]+)/
function getFirebaseConfigFromCommand (line 12) | function getFirebaseConfigFromCommand(
function getFirebaseConfigFromProject (line 40) | function getFirebaseConfigFromProject(
function setFirebaseConfigFromCommand (line 56) | function setFirebaseConfigFromCommand(
FILE: packages/nx-firebase/src/utils/project-name.ts
function getProjectName (line 9) | function getProjectName(tree: Tree, name: string, directory?: string) {
FILE: packages/nx-firebase/src/utils/update-tsconfig.ts
function updateTsConfig (line 19) | function updateTsConfig(
FILE: packages/nx-firebase/src/utils/workspace.ts
type PackageJson (line 4) | type PackageJson = {
type WorkspaceVersion (line 14) | type WorkspaceVersion =
function readNxWorkspaceVersion (line 23) | function readNxWorkspaceVersion(): WorkspaceVersion {
function checkNxVersion (line 51) | function checkNxVersion() {
FILE: tools/generate-package-versions.js
function ensureDirectoryExistence (line 16) | function ensureDirectoryExistence(filePath) {
function shouldUpdateFile (line 26) | function shouldUpdateFile(file, data) {
function maybeUpdateFile (line 34) | function maybeUpdateFile(file, data) {
function updateVersions (line 41) | function updateVersions() {
function updateTemplates (line 77) | function updateTemplates() {
Condensed preview — 162 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (367K chars).
[
{
"path": ".editorconfig",
"chars": 245,
"preview": "# Editor configuration, see http://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = "
},
{
"path": ".github/dependabot.yml",
"chars": 1211,
"preview": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where "
},
{
"path": ".github/workflows/ci.yml",
"chars": 1837,
"preview": "# Build, Test, Lint & e2e pushes.\nname: CI\n\non:\n # support manual trigger of this workflow\n workflow_dispatch:\n\n # ru"
},
{
"path": ".github/workflows/publish.yml",
"chars": 2112,
"preview": "# Publish package to npm when a Github release is published\nname: Publish\n\non:\n release:\n types: [released]\n\njobs:\n "
},
{
"path": ".gitignore",
"chars": 621,
"preview": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# compiled output\ndist\ntmp\n/out-tsc\n\n# depend"
},
{
"path": ".nvmrc",
"chars": 8,
"preview": "22.21.1\n"
},
{
"path": ".prettierignore",
"chars": 105,
"preview": "# Add files here to ignore them from prettier formatting\n\n/dist\n/coverage\n\n/.nx/cache\n/.nx/workspace-data"
},
{
"path": ".prettierrc",
"chars": 231,
"preview": "{\n \"semi\": false,\n \"singleQuote\": true,\n \"trailingComma\": \"all\",\n \"arrowParens\": \"always\",\n \"printWidth\": 80,\n \"br"
},
{
"path": ".vscode/extensions.json",
"chars": 154,
"preview": "{\n \"recommendations\": [\n \"nrwl.angular-console\",\n \"esbenp.prettier-vscode\",\n \"dbaeumer.vscode-eslint\",\n \"fi"
},
{
"path": ".vscode/settings.json",
"chars": 34,
"preview": "{\n \"eslint.validate\": [\"json\"]\n}\n"
},
{
"path": ".vscode/tasks.json",
"chars": 668,
"preview": "{\n \"version\": \"2.0.0\",\n \"tasks\": [\n {\n \"type\": \"npm\",\n \"script\": \"build -- nx-firebase\",\n \"group\": \""
},
{
"path": "CHANGELOG.md",
"chars": 17774,
"preview": "# @simondotm/nx-firebase Changelog\n\n* **Nx badges** for each release indicate the Nx version the plugin was built agains"
},
{
"path": "README.md",
"chars": 4626,
"preview": "# @simondotm/nx-firebase  \n - [Nx-Firebase Functions](#nx-firebase-functions)\n "
},
{
"path": "docs/nx-firebase-hosting.md",
"chars": 1192,
"preview": "# Firebase Hosting\n\nIf you have one or more other web apps (Angular/React/HTML) that are deployed to a hosting site on y"
},
{
"path": "docs/nx-firebase-migrations.md",
"chars": 4544,
"preview": "# Nx-Firebase Plugin Migrations\n\nNewer versions of the plugin occasionally need to update the workspace configurations, "
},
{
"path": "docs/nx-firebase-project-structure.md",
"chars": 6606,
"preview": "# Nx-Firebase schemas (plugin v2)\n\n- [Nx-Firebase schemas (plugin v2)](#nx-firebase-schemas-plugin-v2)\n - [Overview](#o"
},
{
"path": "docs/nx-firebase-projects.md",
"chars": 3995,
"preview": "# Firebase CLI Projects\n\n- [Firebase CLI Projects](#firebase-cli-projects)\n - [Introduction](#introduction)\n - [Nx Wor"
},
{
"path": "docs/nx-firebase-sync.md",
"chars": 6186,
"preview": "# Firebase Sync\n\n- [Firebase Sync](#firebase-sync)\n - [Nx-Firebase Sync Generator](#nx-firebase-sync-generator)\n - [Re"
},
{
"path": "docs/nx-libraries.md",
"chars": 1763,
"preview": "# Using Nx Libraries with Firebase Functions\n\nNx-Firebase supports use of Nx Libraries within functions code.\n\n## Creati"
},
{
"path": "docs/nx-migration.md",
"chars": 1186,
"preview": "# Migrating an existing Firebase project to Nx\n\nTo bring an existing Firebase project into an Nx workspace, simply [gene"
},
{
"path": "docs/nx-plugin-commands.md",
"chars": 1049,
"preview": "# Nx Plugin Development\n\nNotes mainly for my own benefit/reminder here.\n\n## To create the plugin workspace\n\n- `npx creat"
},
{
"path": "docs/nx-setup-mac.md",
"chars": 3671,
"preview": "# Nx Development Setup Guide (Mac)\n\nClean installation steps for the whole setup of node/npm/nvm/nx, assuming mac with `"
},
{
"path": "docs/nx-workspace-layout.md",
"chars": 1488,
"preview": "# Nx-Firebase Workspace Layout\n\nFirebase applications and functions can be generated in whichever directories you like.\n"
},
{
"path": "docs/user-guide.md",
"chars": 998,
"preview": "# User Guide\n\n**Nx Firebase Generators**\n\n- [Firebase Applications](./nx-firebase-applications.md)\n- [Firebase Functions"
},
{
"path": "e2e/compat/eslint.config.mjs",
"chars": 538,
"preview": "import baseConfig from '../../eslint.config.mjs'\n\nexport default [\n {\n ignores: ['**/dist'],\n },\n ...baseConfig,\n "
},
{
"path": "e2e/compat/jest.config.ts",
"chars": 320,
"preview": "/* eslint-disable */\nexport default {\n displayName: 'compat',\n preset: '../../jest.preset.js',\n globals: {},\n testEn"
},
{
"path": "e2e/compat/project.json",
"chars": 1400,
"preview": "{\n \"name\": \"compat\",\n \"$schema\": \"../../node_modules/nx/schemas/project-schema.json\",\n \"sourceRoot\": \"e2e/compat/src\""
},
{
"path": "e2e/compat/src/app/config.ts",
"chars": 401,
"preview": "import { defaultCwd } from './utils/cwd'\n\n// this is the package manager that will be used for the test Nx workspace\nexp"
},
{
"path": "e2e/compat/src/app/setup.ts",
"chars": 2087,
"preview": "// setup re-usable workspaces for e2e testbed\nimport { customExec } from './utils/exec'\nimport { green, info, log, red }"
},
{
"path": "e2e/compat/src/app/test.ts",
"chars": 6422,
"preview": "import { Cache, getCache, isNxVersionSince } from './utils/cache'\nimport { customExec, runNxCommandAsync } from './utils"
},
{
"path": "e2e/compat/src/app/utils/cache.ts",
"chars": 2016,
"preview": "import { CACHE_DIR } from '../config'\nimport { defaultCwd } from './cwd'\nimport { info } from './log'\nimport { satisfies"
},
{
"path": "e2e/compat/src/app/utils/cwd.ts",
"chars": 73,
"preview": "export const defaultCwd = process.cwd()\nconsole.log(`cwd=${defaultCwd}`)\n"
},
{
"path": "e2e/compat/src/app/utils/exec.ts",
"chars": 1569,
"preview": "import { PACKAGE_MANAGER } from '../config'\nimport { info, log } from './log'\nimport { exec } from 'child_process'\n\n/**\n"
},
{
"path": "e2e/compat/src/app/utils/jest-ish.ts",
"chars": 1628,
"preview": "import { info } from 'console'\nimport { log, red } from './log'\n\n/**\n * Test helper function approximating the Jest styl"
},
{
"path": "e2e/compat/src/app/utils/log.ts",
"chars": 1162,
"preview": "const ENABLE_LOG = false\nconst DEFAULT_LOG_FILE = `${process.cwd()}/e2e.log`\n\nimport * as fs from 'fs'\nimport { ensureDi"
},
{
"path": "e2e/compat/src/app/utils/utils.ts",
"chars": 1740,
"preview": "import * as fs from 'fs'\n// import { readJsonFile, writeJsonFile } from '@nx/devkit'\n// import { exit } from 'process'\ni"
},
{
"path": "e2e/compat/src/app/versions.ts",
"chars": 1279,
"preview": "export const testVersions = {\n pluginVersions: [\n 'local',\n // '2.1.2',\n // '0.3.4'\n ],\n nxReleases: {\n '"
},
{
"path": "e2e/compat/src/app/workspace.ts",
"chars": 4412,
"preview": "import * as fs from 'fs'\nimport { Cache, isNxVersionSince } from './utils/cache'\nimport { customExec, runNxCommandAsync "
},
{
"path": "e2e/compat/src/assets/.gitkeep",
"chars": 0,
"preview": ""
},
{
"path": "e2e/compat/src/environments/environment.prod.ts",
"chars": 51,
"preview": "export const environment = {\n production: true,\n}\n"
},
{
"path": "e2e/compat/src/environments/environment.ts",
"chars": 52,
"preview": "export const environment = {\n production: false,\n}\n"
},
{
"path": "e2e/compat/src/main.ts",
"chars": 4803,
"preview": "/**\n * Custom e2e compatibility test suite for @simondotm/nx-firebase Nx plugin\n * The plugin e2e test suite can be unre"
},
{
"path": "e2e/compat/tsconfig.app.json",
"chars": 238,
"preview": "{\n \"extends\": \"./tsconfig.json\",\n \"compilerOptions\": {\n \"outDir\": \"../../dist/out-tsc\",\n \"module\": \"commonjs\",\n "
},
{
"path": "e2e/compat/tsconfig.json",
"chars": 197,
"preview": "{\n \"extends\": \"../../tsconfig.base.json\",\n \"files\": [],\n \"include\": [],\n \"references\": [\n {\n \"path\": \"./tsco"
},
{
"path": "e2e/compat/tsconfig.spec.json",
"chars": 233,
"preview": "{\n \"extends\": \"./tsconfig.json\",\n \"compilerOptions\": {\n \"outDir\": \"../../dist/out-tsc\",\n \"module\": \"commonjs\",\n "
},
{
"path": "e2e/compat/webpack.config.js",
"chars": 342,
"preview": "const { composePlugins, withNx } = require('@nx/webpack')\n\n// Nx plugins for webpack.\nmodule.exports = composePlugins(wi"
},
{
"path": "e2e/nx-firebase-e2e/jest.config.js",
"chars": 741,
"preview": "module.exports = {\n displayName: 'nx-firebase-e2e',\n preset: '../../jest.preset.js',\n globals: {},\n testEnvironment:"
},
{
"path": "e2e/nx-firebase-e2e/jest.globalSetup.js",
"chars": 1147,
"preview": "/**\n * Global setup for e2e tests - runs once before all test files\n * Creates and configures the e2e test workspace\n */"
},
{
"path": "e2e/nx-firebase-e2e/jest.globalTeardown.js",
"chars": 497,
"preview": "/**\n * Global teardown for e2e tests - runs once after all test files\n * Cleans up the e2e test workspace\n */\nconst { ru"
},
{
"path": "e2e/nx-firebase-e2e/jest.testSequencer.js",
"chars": 1075,
"preview": "/**\n * Custom test sequencer to ensure e2e tests run in the correct order.\n * Some tests depend on shared libraries crea"
},
{
"path": "e2e/nx-firebase-e2e/project.json",
"chars": 515,
"preview": "{\n \"name\": \"nx-firebase-e2e\",\n \"$schema\": \"../../node_modules/nx/schemas/project-schema.json\",\n \"projectType\": \"appli"
},
{
"path": "e2e/nx-firebase-e2e/test-utils/index.ts",
"chars": 298,
"preview": "export * from './test-utils-apps'\nexport * from './test-utils-commands'\nexport * from './test-utils-functions'\nexport * "
},
{
"path": "e2e/nx-firebase-e2e/test-utils/test-shared-data.ts",
"chars": 724,
"preview": "import { getProjectData } from './test-utils-project-data'\n\n/**\n * Shared library data that persists across all e2e test"
},
{
"path": "e2e/nx-firebase-e2e/test-utils/test-utils-apps.ts",
"chars": 2642,
"preview": "import type { ProjectData } from './test-utils-project-data'\nimport { readJson } from '@nx/plugin/testing'\n\nexport funct"
},
{
"path": "e2e/nx-firebase-e2e/test-utils/test-utils-commands.ts",
"chars": 5548,
"preview": "import { expectStrings, expectNoStrings } from './test-utils-helpers'\nimport { testDebug, red, green } from './test-util"
},
{
"path": "e2e/nx-firebase-e2e/test-utils/test-utils-functions.ts",
"chars": 2193,
"preview": "import type { ProjectData } from './test-utils-project-data'\nimport { readJson } from '@nx/plugin/testing'\n\nexport funct"
},
{
"path": "e2e/nx-firebase-e2e/test-utils/test-utils-helpers.ts",
"chars": 285,
"preview": "export function expectStrings(input: string, contains: string[]) {\n contains.forEach((item) => {\n expect(input).toCo"
},
{
"path": "e2e/nx-firebase-e2e/test-utils/test-utils-imports.ts",
"chars": 1329,
"preview": "import type { ProjectData } from './test-utils-project-data'\n\nconst IMPORT_MATCH = `import * as logger from \"firebase-fu"
},
{
"path": "e2e/nx-firebase-e2e/test-utils/test-utils-logger.ts",
"chars": 471,
"preview": "const ENABLE_TEST_DEBUG_INFO = true\n\n// https://stackoverflow.com/questions/9781218/how-to-change-node-jss-console-font-"
},
{
"path": "e2e/nx-firebase-e2e/test-utils/test-utils-project-data.ts",
"chars": 2625,
"preview": "import { joinPathFragments } from '@nx/devkit'\n\nconst NPM_SCOPE = '@proj'\n\nexport interface ProjectData {\n name: string"
},
{
"path": "e2e/nx-firebase-e2e/tests/test-application.spec.ts",
"chars": 3586,
"preview": "import {\n readJson,\n runNxCommandAsync,\n uniq,\n checkFilesExist,\n readFile,\n} from '@nx/plugin/testing'\n\nimport {\n "
},
{
"path": "e2e/nx-firebase-e2e/tests/test-bundler.spec.ts",
"chars": 6528,
"preview": "import {\n readJson,\n uniq,\n updateFile,\n checkFilesExist,\n readFile,\n} from '@nx/plugin/testing'\n\nimport {\n appGen"
},
{
"path": "e2e/nx-firebase-e2e/tests/test-function.spec.ts",
"chars": 8953,
"preview": "import { readJson, uniq, checkFilesExist, exists, tmpProjPath } from '@nx/plugin/testing'\nimport { detectPackageManager "
},
{
"path": "e2e/nx-firebase-e2e/tests/test-libraries.spec.ts",
"chars": 3199,
"preview": "import { readJson, checkFilesExist } from '@nx/plugin/testing'\nimport {\n safeRunNxCommandAsync,\n libGeneratorAsync,\n "
},
{
"path": "e2e/nx-firebase-e2e/tests/test-migrate.spec.ts",
"chars": 5995,
"preview": "import { readJson, uniq, updateFile, renameFile } from '@nx/plugin/testing'\n\nimport {\n ProjectData,\n appGeneratorAsync"
},
{
"path": "e2e/nx-firebase-e2e/tests/test-sync.spec.ts",
"chars": 23306,
"preview": "import { readJson, uniq, exists, checkFilesExist } from '@nx/plugin/testing'\n\nimport {\n ProjectData,\n appGeneratorAsyn"
},
{
"path": "e2e/nx-firebase-e2e/tests/test-targets.spec.ts",
"chars": 6594,
"preview": "import { uniq } from '@nx/plugin/testing'\n\nimport {\n ProjectData,\n appGeneratorAsync,\n cleanAppAsync,\n cleanFunction"
},
{
"path": "e2e/nx-firebase-e2e/tests/test-workspace.spec.ts",
"chars": 2981,
"preview": "import { readJson, tmpProjPath } from '@nx/plugin/testing'\nimport { detectPackageManager } from '@nx/devkit'\nimport { sa"
},
{
"path": "e2e/nx-firebase-e2e/tsconfig.json",
"chars": 148,
"preview": "{\n \"extends\": \"../../tsconfig.base.json\",\n \"files\": [],\n \"include\": [],\n \"references\": [\n {\n \"path\": \"./tsco"
},
{
"path": "e2e/nx-firebase-e2e/tsconfig.spec.json",
"chars": 233,
"preview": "{\n \"extends\": \"./tsconfig.json\",\n \"compilerOptions\": {\n \"outDir\": \"../../dist/out-tsc\",\n \"module\": \"commonjs\",\n "
},
{
"path": "eslint.config.mjs",
"chars": 2942,
"preview": "import { FlatCompat } from '@eslint/eslintrc'\nimport { dirname } from 'path'\nimport { fileURLToPath } from 'url'\nimport "
},
{
"path": "jest.config.ts",
"chars": 157,
"preview": "const { getJestProjectsAsync } = require('@nx/jest')\n\nexport default async () => ({\n projects: await getJestProjectsAsy"
},
{
"path": "jest.preset.js",
"chars": 641,
"preview": "const nxPreset = require('@nx/jest/preset').default\n\nmodule.exports = {\n ...nxPreset,\n /* TODO: Update to latest Jest "
},
{
"path": "migrations.json",
"chars": 2813,
"preview": "{\n \"migrations\": [\n {\n \"version\": \"22.0.0-beta.1\",\n \"description\": \"Updates release version config based o"
},
{
"path": "nx.json",
"chars": 1281,
"preview": "{\n \"workspaceLayout\": {\n \"appsDir\": \"e2e\",\n \"libsDir\": \"packages\"\n },\n \"defaultProject\": \"nx-firebase\",\n \"$sch"
},
{
"path": "package.json",
"chars": 2130,
"preview": "{\n \"name\": \"simondotm\",\n \"version\": \"0.0.0\",\n \"license\": \"MIT\",\n \"packageManager\": \"pnpm@8.15.5\",\n \"scripts\": {\n "
},
{
"path": "packages/nx-firebase/README.md",
"chars": 4626,
"preview": "# @simondotm/nx-firebase  :\n //\n // \"indexes\": [\n // {\n // \"collectionGroup\": \"widgets\",\n // \"quer"
},
{
"path": "packages/nx-firebase/src/generators/application/files/firestore.rules",
"chars": 737,
"preview": "rules_version='2'\n\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /{document=**} {\n "
},
{
"path": "packages/nx-firebase/src/generators/application/files/public/404.html",
"chars": 1808,
"preview": "<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initia"
},
{
"path": "packages/nx-firebase/src/generators/application/files/public/index.html",
"chars": 4420,
"preview": "<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initia"
},
{
"path": "packages/nx-firebase/src/generators/application/files/readme.md__tmpl__",
"chars": 1619,
"preview": "# <%= projectName %>\n\nThis Nx Firebase application was generated by [@simondotm/nx-firebase](https://github.com/simondot"
},
{
"path": "packages/nx-firebase/src/generators/application/files/storage.rules",
"chars": 315,
"preview": "rules_version = '2';\n\n// Craft rules based on data in your Firestore database\n// allow write: if firestore.get(\n// /d"
},
{
"path": "packages/nx-firebase/src/generators/application/files_firebase/firebase.json__tmpl__",
"chars": 994,
"preview": "{\n \"database\": {\n \"rules\": \"<%= projectRoot %>/database.rules.json\"\n },\n \"firestore\": {\n \"rules\": \"<%= projectR"
},
{
"path": "packages/nx-firebase/src/generators/application/files_firebaserc/.firebaserc__tmpl__",
"chars": 43,
"preview": "{\n \"targets\": {\n },\n \"projects\": {\n }\n}"
},
{
"path": "packages/nx-firebase/src/generators/application/files_workspace/firebase.__projectName__.json__tmpl__",
"chars": 994,
"preview": "{\n \"database\": {\n \"rules\": \"<%= projectRoot %>/database.rules.json\"\n },\n \"firestore\": {\n \"rules\": \"<%= projectR"
},
{
"path": "packages/nx-firebase/src/generators/application/lib/create-files.ts",
"chars": 2527,
"preview": "import { generateFiles, joinPathFragments, Tree } from '@nx/devkit'\n\nimport type { NormalizedSchema } from '../schema'\n\n"
},
{
"path": "packages/nx-firebase/src/generators/application/lib/index.ts",
"chars": 31,
"preview": "export * from './create-files'\n"
},
{
"path": "packages/nx-firebase/src/generators/application/schema.d.ts",
"chars": 353,
"preview": "export interface Schema {\n // standard @nx project generator options\n name: string\n directory?: string\n tags?: strin"
},
{
"path": "packages/nx-firebase/src/generators/application/schema.json",
"chars": 1002,
"preview": "{\n \"$schema\": \"http://json-schema.org/schema\",\n \"$id\": \"NxFirebaseApplicationGenerator\",\n \"title\": \"Nx Firebase Appli"
},
{
"path": "packages/nx-firebase/src/generators/function/files/package.json__tmpl__",
"chars": 473,
"preview": "{\n \"name\": \"<%= projectName %>\",\n \"description\": \"Firebase Function, auto generated by @simondotm/nx-firebase\",\n \"scr"
},
{
"path": "packages/nx-firebase/src/generators/function/files/readme.md__tmpl__",
"chars": 1239,
"preview": "# <%= projectName %>\n\nThis Nx Firebase function was generated by [@simondotm/nx-firebase](https://github.com/simondotm/n"
},
{
"path": "packages/nx-firebase/src/generators/function/files/src/main.ts__tmpl__",
"chars": 1447,
"preview": "/**\n * Import function triggers from their respective submodules:\n *\n * import {onCall} from \"firebase-functions/v2/http"
},
{
"path": "packages/nx-firebase/src/generators/function/function.spec.ts",
"chars": 11441,
"preview": "import {\n getProjects,\n joinPathFragments,\n readJson,\n readProjectConfiguration,\n Tree,\n writeJson,\n} from '@nx/de"
},
{
"path": "packages/nx-firebase/src/generators/function/function.ts",
"chars": 4540,
"preview": "import {\n GeneratorCallback,\n readProjectConfiguration,\n Tree,\n convertNxGenerator,\n formatFiles,\n runTasksInSeria"
},
{
"path": "packages/nx-firebase/src/generators/function/lib/add-function-config.ts",
"chars": 1118,
"preview": "import { Tree, joinPathFragments, updateJson } from '@nx/devkit'\nimport type { FirebaseConfig, FirebaseFunction } from '"
},
{
"path": "packages/nx-firebase/src/generators/function/lib/create-files.ts",
"chars": 2098,
"preview": "import {\n detectPackageManager,\n offsetFromRoot,\n Tree,\n updateJson,\n generateFiles,\n joinPathFragments,\n} from '@"
},
{
"path": "packages/nx-firebase/src/generators/function/lib/delete-files.ts",
"chars": 827,
"preview": "import { type Tree, joinPathFragments } from '@nx/devkit'\nimport type { NormalizedSchema } from '../schema'\n\n/**\n * Dele"
},
{
"path": "packages/nx-firebase/src/generators/function/lib/index.ts",
"chars": 102,
"preview": "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",
"chars": 3667,
"preview": "import {\n Tree,\n readProjectConfiguration,\n updateProjectConfiguration,\n} from '@nx/devkit'\nimport type { NormalizedS"
},
{
"path": "packages/nx-firebase/src/generators/function/schema.d.ts",
"chars": 714,
"preview": "import { ProjectConfiguration } from '@nx/devkit'\n\nexport interface Schema {\n // standard nx generator options\n name: "
},
{
"path": "packages/nx-firebase/src/generators/function/schema.json",
"chars": 1814,
"preview": "{\n \"$schema\": \"http://json-schema.org/schema\",\n \"$id\": \"NxFirebaseFunctionGenerator\",\n \"title\": \"Nx Firebase Function"
},
{
"path": "packages/nx-firebase/src/generators/init/init.spec.ts",
"chars": 5672,
"preview": "import type { Tree } from '@nx/devkit'\nimport * as devkit from '@nx/devkit'\nimport { createTreeWithEmptyWorkspace } from"
},
{
"path": "packages/nx-firebase/src/generators/init/init.ts",
"chars": 815,
"preview": "import {\n runTasksInSerial,\n type GeneratorCallback,\n type Tree,\n formatFiles,\n} from '@nx/devkit'\nimport { addDepen"
},
{
"path": "packages/nx-firebase/src/generators/init/lib/add-dependencies.ts",
"chars": 3373,
"preview": "import {\n GeneratorCallback,\n readJson,\n Tree,\n addDependenciesToPackageJson,\n detectPackageManager,\n logger,\n} fr"
},
{
"path": "packages/nx-firebase/src/generators/init/lib/add-git-ignore-entry.ts",
"chars": 1151,
"preview": "import { Tree } from '@nx/devkit'\n\nexport const gitIgnoreRules = [\n '# Nx-Firebase',\n '.runtimeconfig.json',\n '**/.em"
},
{
"path": "packages/nx-firebase/src/generators/init/lib/index.ts",
"chars": 74,
"preview": "export * from './add-dependencies'\nexport * from './add-git-ignore-entry'\n"
},
{
"path": "packages/nx-firebase/src/generators/init/schema.d.ts",
"chars": 62,
"preview": "export type InitGeneratorOptions = {\n skipFormat?: boolean\n}\n"
},
{
"path": "packages/nx-firebase/src/generators/init/schema.json",
"chars": 344,
"preview": "{\n \"$schema\": \"http://json-schema.org/schema\",\n \"$id\": \"NxFirebaseInitGenerator\",\n \"title\": \"Init Firebase Plugin\",\n "
},
{
"path": "packages/nx-firebase/src/generators/migrate/migrate.spec.ts",
"chars": 573,
"preview": "import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'\nimport { Tree } from '@nx/devkit'\n\nimport generator fr"
},
{
"path": "packages/nx-firebase/src/generators/migrate/migrate.ts",
"chars": 5123,
"preview": "import {\n GeneratorCallback,\n logger,\n runTasksInSerial,\n Tree,\n joinPathFragments,\n updateProjectConfiguration,\n "
},
{
"path": "packages/nx-firebase/src/generators/migrate/schema.d.ts",
"chars": 109,
"preview": "// eslint-disable-next-line @typescript-eslint/no-empty-interface\nexport interface MigrateGeneratorSchema {}\n"
},
{
"path": "packages/nx-firebase/src/generators/migrate/schema.json",
"chars": 142,
"preview": "{\n \"$schema\": \"http://json-schema.org/schema\",\n \"$id\": \"Migrate\",\n \"title\": \"\",\n \"type\": \"object\",\n \"properties\": {"
},
{
"path": "packages/nx-firebase/src/generators/sync/lib/firebase-changes.ts",
"chars": 5348,
"preview": "import { ProjectConfiguration, Tree } from '@nx/devkit'\n\nimport {\n FirebaseProjects,\n FirebaseChanges,\n FirebaseConfi"
},
{
"path": "packages/nx-firebase/src/generators/sync/lib/firebase-configs.ts",
"chars": 3130,
"preview": "import { Tree, readJson } from '@nx/devkit'\n\nimport {\n getFirebaseConfigFromCommand,\n getFirebaseConfigFromProject,\n "
},
{
"path": "packages/nx-firebase/src/generators/sync/lib/firebase-projects.ts",
"chars": 2894,
"preview": "import {\n ProjectConfiguration,\n TargetConfiguration,\n Tree,\n getProjects,\n logger,\n} from '@nx/devkit'\n\nimport { S"
},
{
"path": "packages/nx-firebase/src/generators/sync/lib/firebase-workspace.ts",
"chars": 618,
"preview": "import { Tree } from '@nx/devkit'\nimport { getFirebaseChanges } from './firebase-changes'\nimport { getFirebaseProjects }"
},
{
"path": "packages/nx-firebase/src/generators/sync/lib/index.ts",
"chars": 236,
"preview": "export * from '../../../utils/debug'\nexport * from './firebase-changes'\nexport * from './firebase-configs'\nexport * from"
},
{
"path": "packages/nx-firebase/src/generators/sync/lib/tags.ts",
"chars": 1730,
"preview": "import {\n ProjectConfiguration,\n Tree,\n updateProjectConfiguration,\n} from '@nx/devkit'\nimport { debugInfo } from '.."
},
{
"path": "packages/nx-firebase/src/generators/sync/lib/update-targets.ts",
"chars": 1050,
"preview": "import {\n ProjectConfiguration,\n TargetConfiguration,\n Tree,\n updateProjectConfiguration,\n} from '@nx/devkit'\n\ntype "
},
{
"path": "packages/nx-firebase/src/generators/sync/schema.d.ts",
"chars": 100,
"preview": "export interface SyncGeneratorSchema {\n app?: string\n project?: string\n // functions?: boolean\n}\n"
},
{
"path": "packages/nx-firebase/src/generators/sync/schema.json",
"chars": 441,
"preview": "{\n \"$schema\": \"http://json-schema.org/schema\",\n \"$id\": \"Sync\",\n \"title\": \"\",\n \"type\": \"object\",\n \"properties\": {\n "
},
{
"path": "packages/nx-firebase/src/generators/sync/sync.spec.ts",
"chars": 775,
"preview": "import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'\nimport { Tree } from '@nx/devkit'\n\nimport generator fr"
},
{
"path": "packages/nx-firebase/src/generators/sync/sync.ts",
"chars": 14307,
"preview": "import {\n GeneratorCallback,\n getProjects,\n joinPathFragments,\n logger,\n readProjectConfiguration,\n runTasksInSeri"
},
{
"path": "packages/nx-firebase/src/index.ts",
"chars": 0,
"preview": ""
},
{
"path": "packages/nx-firebase/src/types/index.ts",
"chars": 119,
"preview": "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",
"chars": 1251,
"preview": "export interface FirebaseFunction {\n predeploy?: string[]\n source: string\n codebase: string\n runtime: string // 'nod"
},
{
"path": "packages/nx-firebase/src/types/lib/firebase-function.ts",
"chars": 147,
"preview": "export type FunctionAssetsGlob = {\n glob: string\n input: string\n output: string\n}\nexport type FunctionAssetsEntry = s"
},
{
"path": "packages/nx-firebase/src/types/lib/firebase-workspace.ts",
"chars": 1353,
"preview": "import { ProjectConfiguration } from '@nx/devkit'\n\nimport { FirebaseConfig } from './firebase-config'\n\nexport const CONF"
},
{
"path": "packages/nx-firebase/src/utils/debug.ts",
"chars": 510,
"preview": "import { logger } from '@nx/devkit'\n\n// debug info just for plugin\nconst ENABLE_DEBUG_INFO = false\n\nexport function debu"
},
{
"path": "packages/nx-firebase/src/utils/firebase-config.ts",
"chars": 2268,
"preview": "import { Tree, ProjectConfiguration } from '@nx/devkit'\n\nconst FIREBASE_TARGET_CONFIG_MATCHER = /(--config[ =])([^\\s]+)/"
},
{
"path": "packages/nx-firebase/src/utils/index.ts",
"chars": 151,
"preview": "export * from './workspace'\nexport * from './update-tsconfig'\nexport * from './project-name'\nexport * from './firebase-c"
},
{
"path": "packages/nx-firebase/src/utils/project-name.ts",
"chars": 1660,
"preview": "import {\n getWorkspaceLayout,\n joinPathFragments,\n names,\n Tree,\n extractLayoutDirectory,\n} from '@nx/devkit'\n\nexpo"
},
{
"path": "packages/nx-firebase/src/utils/update-tsconfig.ts",
"chars": 998,
"preview": "import { type Tree, joinPathFragments, updateJson } from '@nx/devkit'\nimport { packageVersions } from '../__generated__/"
},
{
"path": "packages/nx-firebase/src/utils/workspace.ts",
"chars": 2197,
"preview": "import { logger, readJsonFile, workspaceRoot } from '@nx/devkit'\nimport { packageVersions } from '../__generated__/nx-fi"
},
{
"path": "packages/nx-firebase/tsconfig.json",
"chars": 250,
"preview": "{\n \"extends\": \"../../tsconfig.base.json\",\n \"compilerOptions\": {\n \"module\": \"commonjs\"\n },\n \"files\": [],\n \"includ"
},
{
"path": "packages/nx-firebase/tsconfig.lib.json",
"chars": 396,
"preview": "{\n \"extends\": \"./tsconfig.json\",\n \"compilerOptions\": {\n \"outDir\": \"../../dist/out-tsc\",\n \"declaration\": true,\n "
},
{
"path": "packages/nx-firebase/tsconfig.spec.json",
"chars": 343,
"preview": "{\n \"extends\": \"./tsconfig.json\",\n \"compilerOptions\": {\n \"outDir\": \"../../dist/out-tsc\",\n \"module\": \"commonjs\",\n "
},
{
"path": "tools/generate-package-versions.js",
"chars": 4060,
"preview": "const path = require('path')\nconst fs = require('fs')\n\nconst packageJson = require('../package.json')\nconst nxVersion = "
},
{
"path": "tools/tsconfig.tools.json",
"chars": 251,
"preview": "{\n \"extends\": \"../tsconfig.base.json\",\n \"compilerOptions\": {\n \"outDir\": \"../dist/out-tsc/tools\",\n \"rootDir\": \".\""
},
{
"path": "tsconfig.base.json",
"chars": 542,
"preview": "{\n \"compileOnSave\": false,\n \"compilerOptions\": {\n \"rootDir\": \".\",\n \"sourceMap\": true,\n \"declaration\": false,\n"
}
]
About this extraction
This page contains the full source code of the simondotm/nx-firebase GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 162 files (334.3 KB), approximately 86.6k tokens, and a symbol index with 157 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.