Skip to content

Add firebase-remote-config-admin extension#27125

Open
danilorequena wants to merge 2 commits intoraycast:mainfrom
danilorequena:ext/firebase-remote-config-admin
Open

Add firebase-remote-config-admin extension#27125
danilorequena wants to merge 2 commits intoraycast:mainfrom
danilorequena:ext/firebase-remote-config-admin

Conversation

@danilorequena
Copy link
Copy Markdown

Description

Screencast

Checklist

- Add .claude/ to gitignore
- Initial release of Firebase - Remote Config extension
@raycastbot raycastbot added new extension Label for PRs with new extensions AI Extension platform: macOS labels Apr 12, 2026
@raycastbot
Copy link
Copy Markdown
Collaborator

Congratulations on your new Raycast extension! 🚀

We're currently experiencing a high volume of incoming requests. As a result, the initial review may take up to 10-15 business days.

Once the PR is approved and merged, the extension will be available on our Store.

@danilorequena danilorequena marked this pull request as ready for review April 16, 2026 18:04
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Apr 16, 2026

Greptile Summary

This PR adds a new firebase-remote-config-admin extension that lets users browse, edit, and bulk-publish Firebase Remote Config parameters and conditions across multiple projects, with support for service-account, ADC, and OAuth authentication.

  • resolveProjectsForTool fallback (storage.ts line 914): when input is provided but none of the projectRefs/groupName entries match a saved project, the function returns every enabled project instead of an empty set. AI write tools (upsert-parameter, delete-parameter, etc.) will then silently apply changes to all projects on a typo or unrecognized reference.
  • Missing metadata/ folder: the extension ships with screenshots under media/ but Raycast requires a metadata/ folder with Raycast-styled screenshots for all view-mode commands before store submission.
  • Manual Preferences interface: Preferences is hand-coded in types.ts and passed to getPreferenceValues<Preferences>(); Raycast auto-generates this type in raycast-env.d.ts, and the manual definition can drift from package.json silently.

Confidence Score: 4/5

Not safe to merge until the resolveProjectsForTool fallback and missing metadata folder are addressed.

Two P1 findings block merge: the resolveProjectsForTool silent fallback can cause unintended mass writes in AI tool context, and the missing metadata/ folder blocks Raycast store submission. The manual Preferences type is a rule violation worth fixing. Remaining findings are P2 style/type issues.

extensions/firebase-remote-config-admin/src/storage.ts (resolveProjectsForTool fallback), extensions/firebase-remote-config-admin/src/types.ts (manual Preferences interface), and the missing metadata/ folder.

Important Files Changed

Filename Overview
extensions/firebase-remote-config-admin/src/storage.ts Two issues: manual Preferences interface (rule violation) and resolveProjectsForTool silently falling back to all projects when provided refs don't match, risking unintended mass writes.
extensions/firebase-remote-config-admin/src/bulk-engine.ts Solid ETag-based conflict detection and preview/publish separation; sortConditionalValues has a minor return-type annotation issue dropping useInAppDefault.
extensions/firebase-remote-config-admin/src/google-auth.ts Implements service-account JWT, ADC refresh-token exchange, and PKCE OAuth; token caching with expiry buffer looks correct.
extensions/firebase-remote-config-admin/src/bulk-operation-form.tsx Form.Description evaluates against initial?.operationType (mount-time prop) rather than the live dropdown state, so the hint text doesn't update when the user switches operation types.
extensions/firebase-remote-config-admin/eslint.config.js Uses .flat(Infinity) instead of the required defineConfig wrapper from eslint/config; should follow the repository's ESLint v9 pattern.
extensions/firebase-remote-config-admin/package.json Correct category (Developer Tools), macOS platform, CHANGELOG uses {PR_MERGE_DATE}; preferences, commands, and tools all well-structured.
extensions/firebase-remote-config-admin/src/types.ts Manually defines a Preferences interface that should instead be auto-generated by Raycast into raycast-env.d.ts; all other types are well-structured.

Comments Outside Diff (1)

  1. extensions/firebase-remote-config-admin/src/storage.ts, line 876-916 (link)

    P1 Unmatched project refs silently fall back to ALL projects

    When input is provided but none of the groupName, groupId, projectIds, or projectRefs entries resolve to an actual project, resolved remains empty and the function returns every enabled project. For AI write tools (upsert-parameter, delete-parameter, etc.), a single typo in a project name would silently apply the change to every enabled project instead of erroring out.

    if (resolved.size === 0) return projects; // ← returns all when refs matched nothing

    This should return an empty array (or throw) when input was provided but nothing matched, so the tool can surface "no matching projects found":

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: extensions/firebase-remote-config-admin/src/storage.ts
    Line: 876-916
    
    Comment:
    **Unmatched project refs silently fall back to ALL projects**
    
    When `input` is provided but none of the `groupName`, `groupId`, `projectIds`, or `projectRefs` entries resolve to an actual project, `resolved` remains empty and the function returns every enabled project. For AI write tools (`upsert-parameter`, `delete-parameter`, etc.), a single typo in a project name would silently apply the change to every enabled project instead of erroring out.
    
    ```typescript
    if (resolved.size === 0) return projects; // ← returns all when refs matched nothing
    ```
    
    This should return an empty array (or throw) when input was provided but nothing matched, so the tool can surface "no matching projects found":
    
    
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: extensions/firebase-remote-config-admin/src/storage.ts
Line: 1-5

Comment:
**Manual `Preferences` type will diverge from auto-generated types**

`Preferences` is manually defined in `types.ts` and passed to `getPreferenceValues<Preferences>()` here. Raycast auto-generates this interface in `raycast-env.d.ts` from `package.json` at build time. Using a hand-crafted version means any future change to the preferences schema in `package.json` won't be reflected here, causing silent type drift and potential runtime mismatches.

Remove the `Preferences` interface from `types.ts` and update `storage.ts` to use the auto-generated type:

```suggestion
import { LocalStorage, getPreferenceValues } from "@raycast/api";
import { randomUUID } from "node:crypto";

import type {
  ProjectConfig,
  ProjectGroup,
  SelectableProjectScope,
} from "./types";
```

And update `getPreferences`:
```typescript
export function getPreferences() {
  return getPreferenceValues<Preferences>();
}
```

`Preferences` will be resolved from `raycast-env.d.ts`.

**Rule Used:** What: Don't manually define `Preferences` for `get... ([source](https://app.greptile.com/review/custom-context?memory=d93fc9fb-a45d-4479-a6a4-b1b4af98ebc8))

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: extensions/firebase-remote-config-admin/src/storage.ts
Line: 876-916

Comment:
**Unmatched project refs silently fall back to ALL projects**

When `input` is provided but none of the `groupName`, `groupId`, `projectIds`, or `projectRefs` entries resolve to an actual project, `resolved` remains empty and the function returns every enabled project. For AI write tools (`upsert-parameter`, `delete-parameter`, etc.), a single typo in a project name would silently apply the change to every enabled project instead of erroring out.

```typescript
if (resolved.size === 0) return projects; // ← returns all when refs matched nothing
```

This should return an empty array (or throw) when input was provided but nothing matched, so the tool can surface "no matching projects found":

```suggestion
  if (resolved.size === 0) return [];
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: extensions/firebase-remote-config-admin/eslint.config.js
Line: 1-3

Comment:
**ESLint config should use `defineConfig` from `eslint/config`**

The current config calls `.flat(Infinity)` directly instead of the recommended `defineConfig` wrapper. ESLint v9's official pattern uses `defineConfig` to handle nested config arrays from presets like `@raycast/eslint-config`.

```suggestion
const { defineConfig } = require("eslint/config");
const raycastConfig = require("@raycast/eslint-config");

module.exports = defineConfig([...raycastConfig]);
```

**Rule Used:** What: Enforce importing `defineConfig` from `"esli... ([source](https://app.greptile.com/review/custom-context?memory=645a7150-4078-490e-a70c-d6aad94e0cf5))

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: extensions/firebase-remote-config-admin/src/bulk-operation-form.tsx
Line: 415-425

Comment:
**Form description is static and won't reflect the user's selected operation**

`usesParameter` and `usesCondition` are evaluated against `initial?.operationType`, which is the prop passed at mount time — not the value currently selected in the `operationType` dropdown. When the user switches operations, the description text stays frozen at the initial value. This should track the live form state (e.g., via a `useState` for the selected operation type).

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: extensions/firebase-remote-config-admin/src/bulk-engine.ts
Line: 30-36

Comment:
**Return type drops `useInAppDefault` from `RemoteConfigValue`**

The return type is declared as `Record<string, { value: string }>`, losing the `useInAppDefault` optional field present in `RemoteConfigValue`. At runtime the property is preserved because `Object.entries` / `Object.fromEntries` pass through all own properties, but the TypeScript type strips it, masking this field downstream in the type checker.

```suggestion
function sortConditionalValues(
  values: Record<string, { value: string; useInAppDefault?: boolean }>,
): Record<string, { value: string; useInAppDefault?: boolean }> {
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "Add CHANGELOG for firebase-remote-config..." | Re-trigger Greptile

Comment on lines +1 to +5
import { LocalStorage, getPreferenceValues } from "@raycast/api";
import { randomUUID } from "node:crypto";

import type {
Preferences,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Manual Preferences type will diverge from auto-generated types

Preferences is manually defined in types.ts and passed to getPreferenceValues<Preferences>() here. Raycast auto-generates this interface in raycast-env.d.ts from package.json at build time. Using a hand-crafted version means any future change to the preferences schema in package.json won't be reflected here, causing silent type drift and potential runtime mismatches.

Remove the Preferences interface from types.ts and update storage.ts to use the auto-generated type:

Suggested change
import { LocalStorage, getPreferenceValues } from "@raycast/api";
import { randomUUID } from "node:crypto";
import type {
Preferences,
import { LocalStorage, getPreferenceValues } from "@raycast/api";
import { randomUUID } from "node:crypto";
import type {
ProjectConfig,
ProjectGroup,
SelectableProjectScope,
} from "./types";

And update getPreferences:

export function getPreferences() {
  return getPreferenceValues<Preferences>();
}

Preferences will be resolved from raycast-env.d.ts.

Rule Used: What: Don't manually define Preferences for `get... (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/firebase-remote-config-admin/src/storage.ts
Line: 1-5

Comment:
**Manual `Preferences` type will diverge from auto-generated types**

`Preferences` is manually defined in `types.ts` and passed to `getPreferenceValues<Preferences>()` here. Raycast auto-generates this interface in `raycast-env.d.ts` from `package.json` at build time. Using a hand-crafted version means any future change to the preferences schema in `package.json` won't be reflected here, causing silent type drift and potential runtime mismatches.

Remove the `Preferences` interface from `types.ts` and update `storage.ts` to use the auto-generated type:

```suggestion
import { LocalStorage, getPreferenceValues } from "@raycast/api";
import { randomUUID } from "node:crypto";

import type {
  ProjectConfig,
  ProjectGroup,
  SelectableProjectScope,
} from "./types";
```

And update `getPreferences`:
```typescript
export function getPreferences() {
  return getPreferenceValues<Preferences>();
}
```

`Preferences` will be resolved from `raycast-env.d.ts`.

**Rule Used:** What: Don't manually define `Preferences` for `get... ([source](https://app.greptile.com/review/custom-context?memory=d93fc9fb-a45d-4479-a6a4-b1b4af98ebc8))

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +1 to +3
const raycastConfig = require("@raycast/eslint-config");

module.exports = raycastConfig.flat(Infinity);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 ESLint config should use defineConfig from eslint/config

The current config calls .flat(Infinity) directly instead of the recommended defineConfig wrapper. ESLint v9's official pattern uses defineConfig to handle nested config arrays from presets like @raycast/eslint-config.

Suggested change
const raycastConfig = require("@raycast/eslint-config");
module.exports = raycastConfig.flat(Infinity);
const { defineConfig } = require("eslint/config");
const raycastConfig = require("@raycast/eslint-config");
module.exports = defineConfig([...raycastConfig]);

Rule Used: What: Enforce importing defineConfig from `"esli... (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/firebase-remote-config-admin/eslint.config.js
Line: 1-3

Comment:
**ESLint config should use `defineConfig` from `eslint/config`**

The current config calls `.flat(Infinity)` directly instead of the recommended `defineConfig` wrapper. ESLint v9's official pattern uses `defineConfig` to handle nested config arrays from presets like `@raycast/eslint-config`.

```suggestion
const { defineConfig } = require("eslint/config");
const raycastConfig = require("@raycast/eslint-config");

module.exports = defineConfig([...raycastConfig]);
```

**Rule Used:** What: Enforce importing `defineConfig` from `"esli... ([source](https://app.greptile.com/review/custom-context?memory=645a7150-4078-490e-a70c-d6aad94e0cf5))

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +415 to +425
<List
isLoading={isPublishing}
isShowingDetail
navigationTitle="Bulk Preview"
searchBarPlaceholder="Review project-level preview results"
>
{prepared.map((result) => (
<List.Item
key={result.project.id}
title={result.project.displayName}
subtitle={
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Form description is static and won't reflect the user's selected operation

usesParameter and usesCondition are evaluated against initial?.operationType, which is the prop passed at mount time — not the value currently selected in the operationType dropdown. When the user switches operations, the description text stays frozen at the initial value. This should track the live form state (e.g., via a useState for the selected operation type).

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/firebase-remote-config-admin/src/bulk-operation-form.tsx
Line: 415-425

Comment:
**Form description is static and won't reflect the user's selected operation**

`usesParameter` and `usesCondition` are evaluated against `initial?.operationType`, which is the prop passed at mount time — not the value currently selected in the `operationType` dropdown. When the user switches operations, the description text stays frozen at the initial value. This should track the live form state (e.g., via a `useState` for the selected operation type).

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +30 to +36
}

function normalizeTemplate(
template: RemoteConfigTemplate,
): RemoteConfigTemplate {
const nextTemplate = cloneTemplate(template);
nextTemplate.parameters = Object.fromEntries(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Return type drops useInAppDefault from RemoteConfigValue

The return type is declared as Record<string, { value: string }>, losing the useInAppDefault optional field present in RemoteConfigValue. At runtime the property is preserved because Object.entries / Object.fromEntries pass through all own properties, but the TypeScript type strips it, masking this field downstream in the type checker.

Suggested change
}
function normalizeTemplate(
template: RemoteConfigTemplate,
): RemoteConfigTemplate {
const nextTemplate = cloneTemplate(template);
nextTemplate.parameters = Object.fromEntries(
function sortConditionalValues(
values: Record<string, { value: string; useInAppDefault?: boolean }>,
): Record<string, { value: string; useInAppDefault?: boolean }> {
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/firebase-remote-config-admin/src/bulk-engine.ts
Line: 30-36

Comment:
**Return type drops `useInAppDefault` from `RemoteConfigValue`**

The return type is declared as `Record<string, { value: string }>`, losing the `useInAppDefault` optional field present in `RemoteConfigValue`. At runtime the property is preserved because `Object.entries` / `Object.fromEntries` pass through all own properties, but the TypeScript type strips it, masking this field downstream in the type checker.

```suggestion
function sortConditionalValues(
  values: Record<string, { value: string; useInAppDefault?: boolean }>,
): Record<string, { value: string; useInAppDefault?: boolean }> {
```

How can I resolve this? If you propose a fix, please make it concise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI Extension new extension Label for PRs with new extensions platform: macOS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants