feat(functions): support subsequent installs and multi-project configuration for function kits - #10931
Conversation
Wiz Scan Summary
To detect these findings earlier in the dev lifecycle, try the Wiz Code extension for VS Code, JetBrains, or Visual Studio. |
There was a problem hiding this comment.
Code Review
This pull request introduces support for subsequent installations of already installed function kits, allowing users to add new instances or configure existing ones for their active project. The review feedback highlights a potential TypeError when options.rc.projects is undefined, recommends throwing a FirebaseError in non-interactive mode if multiple matching kits are found, and suggests simplifying the configuration update logic to avoid duplication and silent failures.
We should assume there is at most one kit containing the package to install.
|
|
||
| expect(writeProjectFileStub).to.not.have.been.called; | ||
| expect(loggerInfoStub).to.have.been.calledWith( | ||
| sinon.match(/firebase deploy --only functions:inst-1 --project my-staging-project/), |
There was a problem hiding this comment.
Is it always including a --project flag?
If the active project is the default such that deploy --only functions:inst-1 would do the right thing and the user didn't include a --project flag on install I kind of lean towards not including it. The users may not be familiar with the flag if they're only ever working in one project.
But if there's a CLI convention to always include it or something I could easily be convicned otherwise.
There was a problem hiding this comment.
Since it's a standard CLI flag, I don't think should be confusing or misleading to users. We're just being extra explicit about what to run if they want to configure an instance for a certain project.
There was a problem hiding this comment.
I might check with Joe. Standard flag doesn't mean commonly used or seen. In my own usage I setup a default project as I configured things and have almost never used the --project flag.
But this is a minor UX nit since you're right it will work. Easy to adjust later and shouldn't block this PR.
There was a problem hiding this comment.
I've used --project quite a bit in my own testing, so I'm not sure what speaks to the most common UX :) Getting Joe's insight isn't a bad idea.
Simplifies the instance assignment logic in `functions:kits:install` by directly updating `existingKit.instances` in place instead of performing a redundant search and defensive error check over `configSrc.functions`. Removes the synthetic unit test that artificially zeroed out config mid-execution to hit the unreachable error branch.
- Adds `hasProjectEnv(dir, projectId, projectAlias)` to `src/functions/env.ts` as a generic helper to check for `.env.<projectId>` and `.env.<projectAlias>` in a directory. - Renames `hasDotenvForProject` in `functions-kits-install.ts` to `isKitConfiguredForProject` and delegates directory checks to `hasProjectEnv`. - Eliminates custom `getProjectIdentifiers` resolution and `readdirSync` directory itøeration, aligning with how the CLI resolves environment files.
ajperel
left a comment
There was a problem hiding this comment.
Some last thoughts I trust you to work through. Thanks for addressing my last feedback.
|
|
||
| expect(writeProjectFileStub).to.not.have.been.called; | ||
| expect(loggerInfoStub).to.have.been.calledWith( | ||
| sinon.match(/firebase deploy --only functions:inst-1 --project my-staging-project/), |
There was a problem hiding this comment.
I might check with Joe. Standard flag doesn't mean commonly used or seen. In my own usage I setup a default project as I configured things and have almost never used the --project flag.
But this is a minor UX nit since you're right it will work. Easy to adjust later and shouldn't block this PR.
| ): Promise<boolean> { | ||
| const isThirdParty = isThirdPartyPackage(packageName); | ||
| if (isThirdParty) { | ||
| logger.warn( |
There was a problem hiding this comment.
Could be in a different PR if we want to address... but trying actual installs I wonder if this should use the log bullets like other messages.
There was a problem hiding this comment.
Yeah definitely. I was planning to do a small PR for this after this lands.
| const absConfigDirPath = options.config.path(configDirPath); | ||
| await fs.ensureDir(absConfigDirPath); | ||
|
|
||
| existingKit.instances[instanceId] = configDirPath; |
There was a problem hiding this comment.
Comment from Joe's tools review skill:
Finding: Mutating existingKit.instances in-place modifies the shared options.config.src object in memory. While safe in this CLI context because the command exits immediately, it can lead to side effects in long-running processes (like dev servers) or cause state pollution in unit tests.
Suggested Fix: Clone the configuration before mutating (e.g., using _.cloneDeep(options.config.src)).
I don't think we're relying on the mutated existingKit object after this so it'd be safe to do?
There was a problem hiding this comment.
Exactly, we're not relying on this object after this as the CLI exits soon after. Also I'm not too worried about tests since unit tests supply a fresh Config object per test.
Description
Enhances the
functions:kits:installcommand to support subsequent installations of an already installed Function Kit package in both single-project and multi-project settings:Subsequent Install Detection:
firebase.json.Project Dotenv Detection & Conditional Prompting:
configDir) in the kit for existing.env.<project-id>files matching the active project ID or alias..envfile exists for the active project (and in interactive mode): Prompts the user to choose between:function-kits/<kit>/config-<instanceId>, and updatesfirebase.json.firebase deploycommand so the deploy workflow can prompt for parameters and automatically generate.env.<project-id>..envfile already exists for the active project (or in--non-interactivemode): Automatically skips the choice prompt and directly proceeds with adding a new instance.Collision & Validation Checks:
Scenarios Tested
firebase.json, and skips npm install.addInstance): Prompts for custom instance name, creates config directory, and updatesfirebase.json..env.<current-project>already exists: Skips the action prompt and directly entersaddInstance.addEnv:firebase deploy --only functions:<inst> --project <project>without writing empty.envfiles or modifyingfirebase.json.<project-name>placeholder.src/commands/functions-kits-install.spec.ts(57 passing tests).Sample Commands
firebase functions:kits:install --npm_package <package>for subsequent installs to add an instancefirebase functions:kits:install --npm_package <package> --project <unconfigured-project>