Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughAdds a local-emulator feature: removes legacy Docker emulator artifacts, adds DB mapping for project↔file-path, provides a provisioning API to obtain emulator credentials and file-based branch config read/write, and adds guards to block environment-config changes for local-emulator projects. Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Dashboard Client
participant API as Backend API\nPOST /internal/local-emulator/project
participant DB as Database
participant FS as File System
participant Auth as Auth/Credentials
Client->>API: POST { absolute_file_path }
API->>API: assert NEXT_PUBLIC_STACK_IS_LOCAL_EMULATOR
API->>API: validate absolute path & file existence
API->>DB: ensure owner team exists & admin membership
API->>FS: readConfigFromFile(absolute_file_path)
FS-->>API: config object or error
API->>DB: upsert Project & LocalEmulatorProject mapping
API->>Auth: ensure/get API keys for project
Auth-->>API: secret_server_key, super_secret_admin_key
API->>FS: stringify branch config for response
API-->>Client: 200 { project_id, secret_server_key, super_secret_admin_key, branch_config_override_string }
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Greptile SummaryThis PR replaces the previous Docker-compose-based local emulator approach with a new path-based system where any local Key changes:
Issues found:
Confidence Score: 1/5
Sequence DiagramsequenceDiagram
participant Dashboard as Dashboard (browser)
participant Backend as Backend API
participant DB as PostgreSQL
participant FS as Filesystem
Note over Dashboard,FS: "Open config file" flow (local emulator mode)
Dashboard->>Backend: POST /internal/local-emulator/project<br/>{absolute_file_path}
Backend->>DB: SELECT projectId FROM LocalEmulatorProject<br/>WHERE absoluteFilePath = ?
DB-->>Backend: existing row (or empty)
Backend->>DB: UPSERT Project (id = existing or new UUID)
Backend->>DB: UPSERT Tenancy
Backend->>DB: INSERT LocalEmulatorProject ON CONFLICT DO UPDATE
Backend->>DB: findFirst ApiKeySet (valid, non-revoked)
alt no valid key set
Backend->>DB: CREATE ApiKeySet (expires 2099)
end
Backend->>FS: readConfigFromFile(absoluteFilePath)
FS-->>Backend: config object (or {} if file missing)
Backend-->>Dashboard: {project_id, secret_server_key,<br/>super_secret_admin_key, branch_config_override_string}
Dashboard->>Dashboard: router.push(/projects/{project_id})
Note over Backend,FS: On every branch config fetch (getBranchConfigOverrideQuery)
Backend->>DB: SELECT absoluteFilePath FROM LocalEmulatorProject<br/>WHERE projectId = ?
DB-->>Backend: file path
Backend->>FS: jiti.evalModule(fileContents) — executes TS
FS-->>Backend: config object
Last reviewed commit: 2afacf0 |
apps/e2e/tests/backend/endpoints/api/v1/internal/local-emulator-project.test.ts
Outdated
Show resolved
Hide resolved
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx
Show resolved
Hide resolved
apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 8
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/e2e-api-tests-local-emulator.yaml:
- Around line 130-138: The GitHub Actions step "Start mock-oauth-server in
background" is waiting on the wrong URL (http://localhost:8102); update its
wait-on target to the mock OAuth server port (http://localhost:8114) or
reference the STACK_OAUTH_MOCK_URL environment variable so the readiness check
actually verifies the mock-oauth-server started (the step uses run: pnpm run
start:mock-oauth-server, tail: true, wait-for, etc., so change the wait-on value
accordingly).
In `@apps/backend/prisma/seed.ts`:
- Line 47: The seed currently gates creation of the invariant local-emulator
rows behind the localEmulatorEnabled flag (localEmulatorEnabled /
isLocalEmulatorEnabled), which results in the provisioning route being enabled
but missing LOCAL_EMULATOR_OWNER_TEAM_ID and LOCAL_EMULATOR_ADMIN_USER_ID if the
flag is flipped after seeding; remove that conditional and always ensure those
invariant rows are created/upserted during seed (or alternatively move the
upsert logic into the provisioning route), i.e., replace the guarded creation
code for the owner team and admin user with unconditional upsert logic that
creates or updates the rows with the constants LOCAL_EMULATOR_OWNER_TEAM_ID and
LOCAL_EMULATOR_ADMIN_USER_ID so they exist regardless of when
NEXT_PUBLIC_STACK_IS_LOCAL_EMULATOR is set.
In `@apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx`:
- Around line 162-182: The response schema currently returns secret_server_key
and super_secret_admin_key to callers authenticated with
clientOrHigherAuthTypeSchema; update the handler in route.tsx so that when
auth.type matches clientOrHigherAuthTypeSchema it only returns project_id
(remove secret_server_key and super_secret_admin_key from that response), and
require a stricter auth level (e.g., admin or server-level) to return
secret_server_key and super_secret_admin_key (or move those to a separate
endpoint) — modify the request/response branching logic and the response
yupObject to enforce this separation and ensure only privileged auth values
receive the secret fields.
- Around line 58-105: getOrCreateLocalEmulatorProjectId can race under
concurrency because it reads the mapping, generates a UUID, then creates
project/tenancy before upserting LocalEmulatorProject; serialize same-path
claims by acquiring a Postgres advisory lock derived from absoluteFilePath at
the start of getOrCreateLocalEmulatorProjectId (use
globalPrismaClient.$executeRaw to call pg_advisory_xact_lock or
pg_advisory_lock) and hold it while re-checking the mapping, creating/upserting
the Project (project.upsert) and Tenancy (tenancy.upsert) and performing the
LocalEmulatorProject insert/ON CONFLICT; alternatively wrap the lock +
operations in a single transaction so the lock is released automatically,
ensuring only one caller provisions a project for a given absoluteFilePath and
preventing orphaned projects.
In `@apps/backend/src/lib/local-emulator.ts`:
- Around line 46-60: readConfigFromFile currently swallows file/parse failures
and returns {}, which the caller treats as a valid override and silently hides
errors; change readConfigFromFile to surface failures by removing the
catch-that-returns-empty behavior and either rethrow the caught Error or change
the return type to Promise<Record<string, unknown> | null> and return null on
failure so callers (e.g., the code that prefers local-emulator values) can
distinguish "no config" from an empty config; ensure you still log the error via
captureError but do not convert failures into {} and update callers to handle
the thrown error or null return accordingly; keep references to isValidConfig
and captureError in the flow.
- Line 11: Replace the repo-wide constant LOCAL_EMULATOR_ADMIN_PASSWORD with a
per-provisioned or environment-controlled secret: remove the fixed string and
instead export a function or value that either reads a secure env var (e.g.,
process.env.LOCAL_EMULATOR_ADMIN_PASSWORD) and falls back to generating a
cryptographically-random password at startup, and/or only enables the emulator
admin path when running locally (check host binding or NODE_ENV) so it cannot be
exposed in shared environments; update any imports that reference
LOCAL_EMULATOR_ADMIN_PASSWORD to use the new getter/value and ensure the
generated secret is logged or persisted only to local machine output or a
dev-only file.
In
`@apps/e2e/tests/backend/endpoints/api/v1/internal/local-emulator-project.test.ts`:
- Around line 47-85: The test's assertions expecting
branch_config_override_string to contain the temp file path are incorrect
because route.tsx returns JSON.stringify(readConfigFromFile(absolute_file_path))
and local-emulator.ts falls back to {} for missing files; update the test in
local-emulator-project.test.ts to create real temp config files at pathA/pathB
with known contents (e.g., write a minimal config object/string to the generated
/tmp/<uuid>/stack.config.ts) before calling niceBackendFetch, then assert that
JSON.parse(responseX.body.branch_config_override_string) equals or contains the
known config object/string; alternatively, if you prefer minimal change, remove
the toContain(pathA/pathB) assertions and instead assert that
branch_config_override_string parses to an object (e.g.,
expect(JSON.parse(...)).toEqual(expect.any(Object))). Ensure you reference
branch_config_override_string, niceBackendFetch, and
LOCAL_EMULATOR_PROJECT_ENDPOINT when making the change.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 02624f88-7fe7-4028-adc7-528b602fbedf
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (33)
.github/workflows/docker-emulator-test.yaml.github/workflows/e2e-api-tests-local-emulator.yamlapps/backend/.envapps/backend/.env.developmentapps/backend/package.jsonapps/backend/prisma/migrations/20260226100000_add_local_emulator_project_mapping/migration.sqlapps/backend/prisma/migrations/20260226100000_add_local_emulator_project_mapping/tests/cascades-on-project-delete.tsapps/backend/prisma/migrations/20260226100000_add_local_emulator_project_mapping/tests/persists-and-enforces-uniqueness.tsapps/backend/prisma/schema.prismaapps/backend/prisma/seed.tsapps/backend/src/app/api/latest/internal/config/override/[level]/reset-keys/route.tsxapps/backend/src/app/api/latest/internal/config/override/[level]/route.tsxapps/backend/src/app/api/latest/internal/local-emulator/project/route.tsxapps/backend/src/lib/config.tsxapps/backend/src/lib/local-emulator.tsapps/dashboard/.envapps/dashboard/.env.developmentapps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/layout.tsxapps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client.tsxapps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsxapps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page.tsxapps/dashboard/src/app/(main)/(protected)/layout-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/emails/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsxapps/dashboard/src/lib/config-update.tsxapps/dashboard/src/lib/env.tsxapps/dashboard/src/lib/utils.tsxapps/e2e/tests/backend/endpoints/api/v1/internal/config-local-emulator.test.tsapps/e2e/tests/backend/endpoints/api/v1/internal/local-emulator-project.test.tsdocker/README.mddocker/emulator/docker.compose.yamldocker/emulator/inbucket-nginx.confdocker/readme.md
💤 Files with no reviewable changes (7)
- docker/README.md
- docker/readme.md
- docker/emulator/docker.compose.yaml
- apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/layout.tsx
- docker/emulator/inbucket-nginx.conf
- .github/workflows/docker-emulator-test.yaml
- apps/dashboard/src/lib/utils.tsx
apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx
Show resolved
Hide resolved
apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx
Show resolved
Hide resolved
apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx
Show resolved
Hide resolved
apps/e2e/tests/backend/endpoints/api/v1/internal/local-emulator-project.test.ts
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/sidebar-layout.tsx:
- Line 612: The header is rendering ProjectSwitcher (and UserButton)
unconditionally which causes internal-only hooks (useUser with
projectIdMustMatch and user.useOwnedProjects) to run in local-emulator mode;
update the header in sidebar-layout.tsx to explicitly check the
NEXT_PUBLIC_STACK_IS_LOCAL_EMULATOR flag and only render <ProjectSwitcher ...>
and <UserButton ...> when the flag is false, otherwise render a simplified
emulator-safe header/placeholder; alternatively make ProjectSwitcher and
UserButton emulator-safe by short-circuiting useUser and user.useOwnedProjects
when NEXT_PUBLIC_STACK_IS_LOCAL_EMULATOR so they never call internal-only hooks.
Ensure you reference the ProjectSwitcher and UserButton components and the
NEXT_PUBLIC_STACK_IS_LOCAL_EMULATOR env var in your change.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: cd2ec3aa-1a43-4f0c-b5a2-e1a4368c3d97
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@configs/tsdown/js-library.ts`:
- Line 82: The current ESM-dir check uses options.dir?.endsWith('/esm') or ===
'dist/esm', which fails on Windows paths; normalize the path string before
checking by replacing backslashes with forward slashes (or use path.normalize
and then convert to posix-style) and then test for suffixes like '/esm' or
'/dist/esm' against the normalized options.dir. Update the check around
options.dir in js-library.ts to normalize options.dir first and then perform the
endsWith/equals checks so absolute or Windows-style paths (e.g.,
'C:\\...\\dist\\esm' or 'dist\\esm') are detected correctly.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 4f8c595c-268b-45fa-ae9a-a621c83b8a95
📒 Files selected for processing (1)
configs/tsdown/js-library.ts
apps/e2e/tests/backend/endpoints/api/v1/internal/local-emulator-project.test.ts
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (3)
apps/e2e/tests/backend/endpoints/api/v1/internal/local-emulator-project.test.ts (2)
12-17: Consider cleaning up temp files after tests.The tests create temp files in
/tmpthat are not cleaned up after test completion. While this is minor (OS typically cleans/tmp), adding cleanup improves test hygiene and prevents potential disk space issues in CI with many test runs.💡 Example cleanup pattern
import { afterAll } from "vitest"; const tempFilesToCleanup: string[] = []; async function createTempConfigFile(): Promise<string> { const filePath = `/tmp/${randomUUID()}/stack.config.ts`; await fs.mkdir(path.dirname(filePath), { recursive: true }); await fs.writeFile(filePath, "export const config = {};\n", "utf-8"); tempFilesToCleanup.push(path.dirname(filePath)); return filePath; } afterAll(async () => { for (const dir of tempFilesToCleanup) { await fs.rm(dir, { recursive: true, force: true }).catch(() => {}); } });Also applies to: 71-89, 91-156
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/e2e/tests/backend/endpoints/api/v1/internal/local-emulator-project.test.ts` around lines 12 - 17, The test creates temporary config files in createTempConfigFile() but never removes them; add a module-scoped array (e.g., tempFilesToCleanup) to record created directories or file paths inside createTempConfigFile(), and register an afterAll() hook that iterates the array and removes each entry with fs.rm(..., { recursive: true, force: true }) (catch errors to avoid failing teardown). Update all places that create temp artifacts (the other blocks noted in the review) to push their paths into tempFilesToCleanup so the centralized afterAll cleanup removes them after tests.
9-9: Consider importingLOCAL_EMULATOR_OWNER_TEAM_IDfrom a shared location.The constant is duplicated from
apps/backend/src/lib/local-emulator.ts. If the value changes in the source, this test would fail silently with a misleading assertion error.However, importing directly from backend code into E2E tests may require additional build configuration. If that's not feasible, consider adding a comment noting the dependency:
-const LOCAL_EMULATOR_OWNER_TEAM_ID = "5a0c858b-d9e9-49d4-9943-8ce385d86428"; +// Must match LOCAL_EMULATOR_OWNER_TEAM_ID in apps/backend/src/lib/local-emulator.ts +const LOCAL_EMULATOR_OWNER_TEAM_ID = "5a0c858b-d9e9-49d4-9943-8ce385d86428";🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/e2e/tests/backend/endpoints/api/v1/internal/local-emulator-project.test.ts` at line 9, The test defines a duplicated constant LOCAL_EMULATOR_OWNER_TEAM_ID that is already declared in the backend's local-emulator module; remove the hard-coded value from the test and import LOCAL_EMULATOR_OWNER_TEAM_ID from the shared source (or from the backend module that exports it) so the test uses the single source of truth; if importing is not possible due to build constraints, keep the constant but add a clear comment above LOCAL_EMULATOR_OWNER_TEAM_ID in the test referencing the original symbol in local-emulator.ts and note that it must be updated in both places, and consider moving the constant to a shared/test-helpers module to resolve the duplication permanently.apps/backend/src/lib/config.tsx (1)
269-275: Consider handling the case whereisLocalEmulatorProject()returns true butfilePathis null.When
isLocalEmulatorProject(options.projectId)returnstruebutgetLocalEmulatorFilePath(options.projectId)returnsnull, the code silently falls through to the DB write path. This could happen if there's a TOCTOU window where theLocalEmulatorProjectrow is deleted between the two async calls, or if the mapping row exists without a validabsoluteFilePath.While this may be intentional as a fallback, consider whether throwing an error would be more appropriate to surface unexpected state:
💡 Suggested alternative
if (isLocalEmulatorEnabled() && await isLocalEmulatorProject(options.projectId)) { const filePath = await getLocalEmulatorFilePath(options.projectId); - if (filePath) { - await writeConfigToFile(filePath, newConfig); - return; - } + if (!filePath) { + throw new StackAssertionError("Local emulator project exists but has no file path", { projectId: options.projectId }); + } + await writeConfigToFile(filePath, newConfig); + return; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/backend/src/lib/config.tsx` around lines 269 - 275, The code currently treats a truthy isLocalEmulatorProject(options.projectId) but null filePath from getLocalEmulatorFilePath(options.projectId) as a silent fallthrough; update the isLocalEmulatorProject + getLocalEmulatorFilePath branch to explicitly handle the null filePath case (instead of silently proceeding to DB writes) by throwing a descriptive error or logging and returning, so callers don’t unknowingly write to DB when an emulator mapping existed but no absolute path was found; modify the block around isLocalEmulatorEnabled, isLocalEmulatorProject, getLocalEmulatorFilePath, writeConfigToFile and newConfig to implement this explicit error/early-return behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx`:
- Around line 190-194: The endpoint accepts arbitrary absolute paths and passes
them into readConfigFromFile which ultimately uses jiti.evalModule, allowing
remote code execution; restrict and validate paths and raise auth level. Update
the route handler that uses path.isAbsolute and path.resolve on
req.body.absolute_file_path to enforce a whitelist or base directory (e.g.,
join+normalize against a configured SAFE_CONFIG_DIR) and reject any path outside
that directory, and/or change the route's auth from clientOrHigherAuthTypeSchema
to a server/admin-only schema; additionally add safe file-content checks in
readConfigFromFile before calling jiti.evalModule (e.g., disallow executable JS,
require a JSON/YAML config or signature) and log/reject suspicious files.
In
`@apps/dashboard/src/app/`(main)/(protected)/(outside-dashboard)/projects/page-client.tsx:
- Around line 88-103: The code currently calls response.json() before checking
response.ok which throws a SyntaxError for non-JSON responses; change the logic
in page-client.tsx so you read the raw body first (e.g., response.text()),
inspect response.headers.get("content-type") or try JSON.parse on the text, and
only then branch on response.ok; if the response is non-JSON use the raw text as
the error message (or fall back to a generic message) so the error thrown from
this block (where responseBody is used) preserves actionable backend/proxy text
instead of hiding it behind a SyntaxError.
- Around line 195-223: The Dialog can still close via onOpenChange while
openingConfigFile is true; update the onOpenChange handler used by Dialog so it
guards against closing during the loading state: in the Dialog component that
uses onOpenChange, check the openingConfigFile flag and ignore attempts to set
open to false when openingConfigFile is true (i.e., only call
setOpenConfigFileDialog(false) if !openingConfigFile), and likewise ensure the
Cancel button and any other close paths respect openingConfigFile (keep existing
disabled prop), leaving handleOpenConfigFile unchanged.
---
Nitpick comments:
In `@apps/backend/src/lib/config.tsx`:
- Around line 269-275: The code currently treats a truthy
isLocalEmulatorProject(options.projectId) but null filePath from
getLocalEmulatorFilePath(options.projectId) as a silent fallthrough; update the
isLocalEmulatorProject + getLocalEmulatorFilePath branch to explicitly handle
the null filePath case (instead of silently proceeding to DB writes) by throwing
a descriptive error or logging and returning, so callers don’t unknowingly write
to DB when an emulator mapping existed but no absolute path was found; modify
the block around isLocalEmulatorEnabled, isLocalEmulatorProject,
getLocalEmulatorFilePath, writeConfigToFile and newConfig to implement this
explicit error/early-return behavior.
In
`@apps/e2e/tests/backend/endpoints/api/v1/internal/local-emulator-project.test.ts`:
- Around line 12-17: The test creates temporary config files in
createTempConfigFile() but never removes them; add a module-scoped array (e.g.,
tempFilesToCleanup) to record created directories or file paths inside
createTempConfigFile(), and register an afterAll() hook that iterates the array
and removes each entry with fs.rm(..., { recursive: true, force: true }) (catch
errors to avoid failing teardown). Update all places that create temp artifacts
(the other blocks noted in the review) to push their paths into
tempFilesToCleanup so the centralized afterAll cleanup removes them after tests.
- Line 9: The test defines a duplicated constant LOCAL_EMULATOR_OWNER_TEAM_ID
that is already declared in the backend's local-emulator module; remove the
hard-coded value from the test and import LOCAL_EMULATOR_OWNER_TEAM_ID from the
shared source (or from the backend module that exports it) so the test uses the
single source of truth; if importing is not possible due to build constraints,
keep the constant but add a clear comment above LOCAL_EMULATOR_OWNER_TEAM_ID in
the test referencing the original symbol in local-emulator.ts and note that it
must be updated in both places, and consider moving the constant to a
shared/test-helpers module to resolve the duplication permanently.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: e917fc96-5fd1-4597-8ef8-00b78f9711a8
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (11)
apps/backend/.env.developmentapps/backend/package.jsonapps/backend/prisma/schema.prismaapps/backend/src/app/api/latest/internal/local-emulator/project/route.tsxapps/backend/src/lib/config.tsxapps/backend/src/lib/local-emulator.tsapps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/emails/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsxapps/e2e/tests/backend/endpoints/api/v1/internal/local-emulator-project.test.tspackages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/backend/src/lib/local-emulator.ts
- apps/backend/prisma/schema.prisma
Summary by CodeRabbit
New Features
Changes
Tests
Chores