Skip to content

Commit 15faf70

Browse files
authored
stack-cli: explicit --cloud-project-id / --config-file across exec, config, project (#1422)
## Summary Reworks the `stack` CLI surface so the cloud-vs-local choice is **explicit at every invocation**, removing the global `--project-id` / `STACK_PROJECT_ID` env var and the local-default `exec` behavior introduced earlier in this branch. ### `stack exec` - Removes `--cloud`, `STACK_EXEC_DEFAULT_TARGET`, and the implicit local default. The CLI now requires **exactly one** of: - `--cloud-project-id <id>` — run against the Stack Auth cloud API - `--config-file <path>` — run against the local emulator project mapped to that absolute config-file path - The `--config-file` branch resolves the project id by calling the existing `GET /api/latest/internal/local-emulator/project` endpoint and matching `absolute_file_path` client-side. No new backend endpoint introduced. ### `stack config pull` / `stack config push` - Both now take `--cloud-project-id <id>` per-command instead of the global flag / `STACK_PROJECT_ID` env. - `config pull --config-file` is **optional**: when omitted, the CLI uses `./stack.config.ts` from the current directory. If neither flag nor cwd file is present, it exits with a clear hint to pass `--config-file` or `cd` into a directory containing `stack.config.ts`. ### `stack project list` - Default (no flags) lists both **cloud and local emulator** projects. Each entry carries a `target: "cloud" | "dev"` field (text format: `<id>\t<displayName>\t[<target>]`). - `--cloud` / `--dev` filter to a single source (mutually exclusive — passing both errors). - On the default code path, an unreachable local emulator emits a single stderr warning (`warning: skipping dev projects — local emulator not reachable …`) and the command still succeeds with cloud results. With `--dev` explicit, the unreachable case hard-errors. ### `stack project create` - Now requires `--cloud` to make the cloud-vs-local choice explicit. There is no local alternative today; the flag exists to surface the decision so a future local-project create doesn't silently change behavior. ### Backend - Bumps the `LIMIT` on `GET /api/latest/internal/local-emulator/project` from 20 → 100 so `project list --dev` doesn't silently truncate. ### Refactors (from earlier in this branch, unchanged here) - Local-emulator paths/ports/PCK polling live in `packages/stack-cli/src/lib/emulator-paths.ts`. - Shared local-emulator admin credentials live in `packages/stack-shared/src/local-emulator.ts`. - `resolveAuth` / `resolveLocalEmulatorAuth` take an explicit `projectId: string` (no more `Flags` parameter). - New `packages/stack-cli/src/lib/local-emulator-client.ts` encapsulates the GET-and-match flow used by both `exec --config-file` and `project list --dev`. ## Breaking changes **Scripts that relied on any of the following must be updated:** | Removed | Replacement | | --- | --- | | Global `--project-id <id>` flag | Per-command `--cloud-project-id <id>` | | `STACK_PROJECT_ID` env var | Per-command `--cloud-project-id <id>` | | `stack exec --cloud` | `stack exec --cloud-project-id <id>` | | `STACK_EXEC_DEFAULT_TARGET=cloud\|local` | `--cloud-project-id <id>` or `--config-file <path>` | | `stack exec` defaulting to local emulator | Explicit `--config-file <path>` required | | `stack project create` without a flag | `stack project create --cloud …` required | ## Test plan - [x] `pnpm lint` (stack-cli, backend, e2e) — clean - [x] `pnpm --filter @stackframe/stack-cli typecheck` — clean - [x] `pnpm --filter @stackframe/stack-cli exec vitest run` — **72/72 passing** (new unit tests: `parseExecTarget`, `resolveConfigFilePathForPull`, `resolveProjectListSources`, `formatProjectList`) - [x] `pnpm test run apps/e2e/tests/general/cli.test.ts` — **73 passing, 4 skipped, 0 failing**. New e2e cases cover: - `exec` with neither flag → errors with "Specify a target" - `exec` with both flags → errors with "not both" - `exec --config-file` with missing file / missing PCK / unreachable API - `exec --config-file` happy path against a real local-emulator backend (gated on `NEXT_PUBLIC_STACK_IS_LOCAL_EMULATOR=true`) - `config pull` cwd fallback to `./stack.config.ts` - `config pull` with no `--config-file` and no cwd `stack.config.ts` → errors with `Pass --config-file …` - `project list --cloud --dev` together → errors - `project list` default with unreachable emulator → cloud results + single stderr warning - `project create` without `--cloud` → errors - All previously-`--cloud` exec cases ported to `--cloud-project-id` - [x] Manual smoke: `stack exec --help`, `stack project list --cloud --dev`, `stack project create` all emit the expected friendly errors / help text. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes * **New Features** * CLI `exec`, `config`, and `project` commands now require explicit targeting via `--cloud-project-id` (cloud) or `--config-file` (local emulator). * `project list` now supports `--cloud` and `--dev` flags to display projects from both sources with target indicators. * Enhanced environment variable validation for emulator service ports with proper fallback handling. * **Bug Fixes** * `project list` now gracefully handles unreachable emulator with warning fallback instead of failure. * **Tests** * Expanded test coverage for project targeting, config file resolution, and emulator connectivity scenarios. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 024da3c commit 15faf70

23 files changed

Lines changed: 1071 additions & 182 deletions

File tree

apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ export const GET = createSmartRouteHandler({
404404
SELECT "projectId", "absoluteFilePath", "updatedAt"
405405
FROM "LocalEmulatorProject"
406406
ORDER BY "updatedAt" DESC
407-
LIMIT 20
407+
LIMIT 100
408408
`);
409409

410410
const projectIds = rows.map((r) => r.projectId);

apps/backend/src/lib/local-emulator.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { globalPrismaClient } from "@/prisma-client";
22
import { detectImportPackageFromDir, renderConfigFileContent } from "@stackframe/stack-shared/dist/config-rendering";
33
import { isValidConfig } from "@stackframe/stack-shared/dist/config/format";
4+
import { LOCAL_EMULATOR_ADMIN_EMAIL, LOCAL_EMULATOR_ADMIN_PASSWORD } from "@stackframe/stack-shared/dist/local-emulator";
45
import { getEnvVariable } from "@stackframe/stack-shared/dist/utils/env";
56
import { StatusError } from "@stackframe/stack-shared/dist/utils/errors";
67
import fs from "fs/promises";
@@ -9,8 +10,7 @@ import path from "path";
910

1011
export const LOCAL_EMULATOR_ADMIN_USER_ID = "63abbc96-5329-454a-ba56-e0460173c6c1";
1112
export const LOCAL_EMULATOR_OWNER_TEAM_ID = "5a0c858b-d9e9-49d4-9943-8ce385d86428";
12-
export const LOCAL_EMULATOR_ADMIN_EMAIL = "local-emulator@stack-auth.com";
13-
export const LOCAL_EMULATOR_ADMIN_PASSWORD = "LocalEmulatorPassword";
13+
export { LOCAL_EMULATOR_ADMIN_EMAIL, LOCAL_EMULATOR_ADMIN_PASSWORD };
1414

1515
export const LOCAL_EMULATOR_ENV_CONFIG_BLOCKED_MESSAGE =
1616
"Environment configuration overrides cannot be changed in the local emulator. Update this in your production deployment instead.";

apps/dashboard/src/app/(main)/(protected)/layout-client.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { CursorBlastEffect } from "@stackframe/dashboard-ui-components";
55
import { ConfigUpdateDialogProvider } from "@/lib/config-update";
66
import { getPublicEnvVar } from '@/lib/env';
77
import { useStackApp, useUser } from "@stackframe/stack";
8+
import { LOCAL_EMULATOR_ADMIN_EMAIL, LOCAL_EMULATOR_ADMIN_PASSWORD } from "@stackframe/stack-shared/dist/local-emulator";
89
import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
910
import { generateUuid } from "@stackframe/stack-shared/dist/utils/uuids";
1011
import { useEffect } from "react";
@@ -20,8 +21,8 @@ export default function LayoutClient({ children }: { children: React.ReactNode }
2021
if (user) return;
2122
if (isLocalEmulator) {
2223
await app.signInWithCredential({
23-
email: "local-emulator@stack-auth.com",
24-
password: "LocalEmulatorPassword",
24+
email: LOCAL_EMULATOR_ADMIN_EMAIL,
25+
password: LOCAL_EMULATOR_ADMIN_PASSWORD,
2526
});
2627
} else if (isPreview) {
2728
const id = generateUuid();

0 commit comments

Comments
 (0)