Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughIntroduces a complete Stack CLI implementation with authentication, project management, code execution, and configuration management capabilities, plus comprehensive end-to-end tests and build configuration. Changes
Possibly related PRs
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ❌ 3❌ Failed checks (2 warnings, 1 inconclusive)
✏️ 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 adds a new CLI package ( Major Changes:
Security practices followed:
Test coverage:
Confidence Score: 5/5
Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
Start[User runs stack command] --> Parse[Commander.js parses args]
Parse --> CheckAuth{Requires auth?}
CheckAuth -->|No| Init[stack init]
CheckAuth -->|Session auth| Session[stack login/logout/project]
CheckAuth -->|Project auth| Project[stack exec/config]
Init --> Delegate[Delegate to @stackframe/init-stack via execFileSync]
Session --> ResolveSession[resolveSessionAuth]
ResolveSession --> CheckToken{Has refresh token?}
CheckToken -->|No| LoginFlow[stack login: Browser auth flow]
CheckToken -->|Yes| GetUser[Get internal user]
LoginFlow --> SaveToken[Save to ~/.stackrc with 0o600]
SaveToken --> Success1[Command executes]
GetUser --> Success1
Project --> ResolveProject[resolveAuth]
ResolveProject --> CheckPID{Has project ID?}
CheckPID -->|No| Error1[Error: No project ID]
CheckPID -->|Yes| ValidateProject[Validate project ownership]
ValidateProject --> ExecOrConfig{Which command?}
ExecOrConfig -->|exec| RunJS[Create AsyncFunction & execute JS]
ExecOrConfig -->|config pull| PullConfig[Get config & generate .ts/.js file]
ExecOrConfig -->|config push| PushConfig[Load config via jiti & push to API]
PullConfig --> JSONStringify[Use JSON.stringify for safe code generation]
RunJS --> Output[Output result as JSON]
JSONStringify --> Output
PushConfig --> Output
Last reviewed commit: 35770d7 |
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (2)
apps/e2e/tests/general/cli.test.ts (2)
163-171: Consider typing the parsed JSON or adding a comment forany.Line 168 uses
anyfor the project type in the callback. Per coding guidelines,anyusage should be explained or avoided where possible.Proposed fix
const projects = JSON.parse(stdout); - const found = projects.find((p: any) => p.id === createdProjectId); + // JSON.parse returns unknown structure; we only need to check the id property + const found = projects.find((p: { id: string }) => p.id === createdProjectId); expect(found).toBeDefined();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/e2e/tests/general/cli.test.ts` around lines 163 - 171, The test parses stdout into projects and currently uses an untyped callback with (p: any); update the code to avoid raw any by either typing the parsed JSON (e.g. declare projects as Project[] or a local type/interface matching expected fields) or add an inline explanatory comment justifying any usage; locate the test that calls runCli and assigns createdProjectId, then change the line const projects = JSON.parse(stdout); and the find callback (p: any) => ... to use the proper Project type (or a TypeScript cast with a short comment) so the find uses (p: Project) => p.id === createdProjectId and found.displayName is correctly typed.
20-26: Add type annotation or comment foranyusage.The
as anycast on line 24 bypasses type checking. Per coding guidelines,anyusage should include a comment explaining why it's necessary. TheExecExceptiontype fromchild_processalready has acodeproperty, so this could be typed more precisely.Proposed fix
+import { execFile, ExecException } from "child_process"; -import { execFile } from "child_process"; ... }, (error, stdout, stderr) => { resolve({ stdout: stdout.toString(), stderr: stderr.toString(), - exitCode: error ? (error as any).code ?? 1 : 0, + exitCode: error ? (error as ExecException).code ?? 1 : 0, }); });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/e2e/tests/general/cli.test.ts` around lines 20 - 26, The callback passed to exec uses an `any` cast for `error`—replace this with the proper `ExecException | null` type from `child_process` (or type the callback parameters explicitly) and then use `error?.code ?? 1` when computing exitCode; update the callback signature where resolve is called so TypeScript knows `error` has a `code` property instead of using `(error as any)`. Ensure you import `ExecException` if needed and remove the `as any` cast.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/stack-cli/src/commands/config-file.ts`:
- Line 73: The dynamic import using import(filePath) fails on Windows because
Node requires a file:// URL for absolute filesystem paths; update the code
around the import (where configModule is set from filePath) to convert filePath
to a file URL using pathToFileURL from the 'url' module (e.g., const fileUrl =
pathToFileURL(filePath).href) and then await import(fileUrl) so configModule
imports reliably across platforms.
In `@packages/stack-cli/src/commands/exec.ts`:
- Around line 32-37: The try-catch-all around formatExecApiHelp(), the
AsyncFunction creation, and user code execution must be narrowed: remove the
try/catch around formatExecApiHelp() since it is synchronous and shouldn’t be
wrapped, change the catch around the new AsyncFunction(...) creation to only
handle SyntaxError (and wrap that into CliError with the existing help message),
and for the block that executes user code (the function invocation where
user-provided code runs) only handle documented expected error types and rethrow
any unknown/other errors rather than blanket-wrapping; update the catch clauses
in exec.ts to reference formatExecApiHelp(), the AsyncFunction constructor, and
the user-code execution site by name so only specific errors are caught and
others are rethrown.
In `@packages/stack-cli/src/commands/init.ts`:
- Around line 10-14: Remove the unnecessary type cast on cmd.args inside the
.action callback: change the declaration using "const args = cmd.args as
string[]" to just use cmd.args (e.g., const args = cmd.args) so you rely on
Commander v13's built-in typing; ensure the rest of the call to
execFileSync("@stackframe/init-stack@latest", ...args) remains the same and
references execFileSync and the .action handler.
In `@packages/stack-cli/src/commands/project.ts`:
- Around line 57-66: The code only trims and validates displayName for the
interactive prompt path, letting opts.displayName = " " pass through; fix by
normalizing and validating displayName in one place: after assigning let
displayName = opts.displayName; immediately do displayName = typeof displayName
=== "string" ? displayName.trim() : displayName and then explicitly check if
displayName == null or displayName.length === 0 and throw new CliError("Display
name cannot be empty."); also apply the same explicit trim-and-validate logic to
the other occurrence that sets/uses displayName before calling createProject and
keep references to isNonInteractiveEnv(), prompt(), CliError, and createProject
to locate the relevant code paths.
In `@packages/stack-cli/src/lib/app.ts`:
- Line 23: Remove the redundant type assertion on the returned user in the
function that returns the current user: the app is typed as StackClientApp<true,
"internal"> and its overloaded getUser() already returns
ProjectCurrentUser<"internal"> (alias CurrentInternalUser), so replace the
forced cast `as CurrentInternalUser` with a plain `return user;` (i.e., remove
the cast) in the return statement to let TypeScript infer the correct type;
update the return in the function that calls getUser() accordingly.
In `@packages/stack-cli/src/lib/config.ts`:
- Around line 9-14: The readConfigFileRaw function currently swallows all errors
from fs.readFileSync; change the catch to inspect the thrown error (from
fs.readFileSync(CONFIG_PATH, "utf-8")) and only return [] when err.code ===
'ENOENT' (missing file); for any other error (permission denied, corruption,
etc.) rethrow the error so callers fail fast. Ensure the thrown error is
properly typed/guarded when checking err.code and reference the existing
readConfigFileRaw function and CONFIG_PATH in your change.
- Line 57: After writing the config/token file with fs.writeFileSync (e.g., the
call that writes CONFIG_PATH using result.join("\n") and the other write at the
second occurrence), call fs.chmodSync(CONFIG_PATH, 0o600) immediately after each
fs.writeFileSync so the file permissions are enforced on both newly created and
existing files; update the code paths that write the token/config (referencing
CONFIG_PATH and the functions or blocks that perform the writes) to always
invoke fs.chmodSync(CONFIG_PATH, 0o600) after the write.
---
Nitpick comments:
In `@apps/e2e/tests/general/cli.test.ts`:
- Around line 163-171: The test parses stdout into projects and currently uses
an untyped callback with (p: any); update the code to avoid raw any by either
typing the parsed JSON (e.g. declare projects as Project[] or a local
type/interface matching expected fields) or add an inline explanatory comment
justifying any usage; locate the test that calls runCli and assigns
createdProjectId, then change the line const projects = JSON.parse(stdout); and
the find callback (p: any) => ... to use the proper Project type (or a
TypeScript cast with a short comment) so the find uses (p: Project) => p.id ===
createdProjectId and found.displayName is correctly typed.
- Around line 20-26: The callback passed to exec uses an `any` cast for
`error`—replace this with the proper `ExecException | null` type from
`child_process` (or type the callback parameters explicitly) and then use
`error?.code ?? 1` when computing exitCode; update the callback signature where
resolve is called so TypeScript knows `error` has a `code` property instead of
using `(error as any)`. Ensure you import `ExecException` if needed and remove
the `as any` cast.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
packages/stack-cli/src/generated/exec-api-metadata.jsonis excluded by!**/generated/**pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (19)
apps/e2e/tests/general/cli.test.tspackages/stack-cli/.eslintrc.cjspackages/stack-cli/package.jsonpackages/stack-cli/scripts/generate-exec-api-metadata.tspackages/stack-cli/src/commands/config-file.tspackages/stack-cli/src/commands/exec.tspackages/stack-cli/src/commands/init.tspackages/stack-cli/src/commands/login.tspackages/stack-cli/src/commands/logout.tspackages/stack-cli/src/commands/project.tspackages/stack-cli/src/index.tspackages/stack-cli/src/lib/app.tspackages/stack-cli/src/lib/auth.tspackages/stack-cli/src/lib/config.tspackages/stack-cli/src/lib/errors.tspackages/stack-cli/src/lib/exec-api.tspackages/stack-cli/src/lib/interactive.tspackages/stack-cli/tsconfig.jsonpackages/stack-cli/tsup.config.ts
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 `@packages/stack-cli/src/lib/exec-api.ts`:
- Around line 33-36: formatExecApiHelp currently only lists
metadata.stackServerApp, omitting metadata.stackClientApp from the help output;
update formatExecApiHelp to include both API groups by adding a section for
metadata.stackClientApp (e.g. "StackClientApp methods") in addition to the
existing "StackServerApp methods" so the returned lines include both
metadata.stackClientApp and metadata.stackServerApp before joining and returning
the string.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (1)
packages/stack-cli/src/lib/exec-api.ts
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
apps/e2e/tests/general/cli.test.ts (2)
66-66: Use structured URL construction for the session endpoint.Avoid raw interpolation for URLs here; use
URL/urlStringconstruction for consistency and safer endpoint composition.Proposed fix
- const sessionRes = await niceFetch(`${STACK_BACKEND_BASE_URL}/api/v1/auth/sessions`, { + const sessionsUrl = new URL("/api/v1/auth/sessions", STACK_BACKEND_BASE_URL); + const sessionRes = await niceFetch(sessionsUrl, {As per coding guidelines, "Use
urlString`` orencodeURIComponent()` instead of normal string interpolation for URLs for consistency."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/e2e/tests/general/cli.test.ts` at line 66, The call that builds the sessions endpoint using string interpolation (the niceFetch invocation that assigns sessionRes with `${STACK_BACKEND_BASE_URL}/api/v1/auth/sessions`) should be changed to construct the URL with the URL API (or the project's urlString helper) instead of raw interpolation; replace the interpolated string with new URL('/api/v1/auth/sessions', STACK_BACKEND_BASE_URL).toString() (or equivalent urlString call) so the STACK_BACKEND_BASE_URL and the path are safely composed and encoded before passing to niceFetch.
167-170: Removeanyfrom project lookup assertions.
(p: any)bypasses type checks in a key verification path. Prefer a typed parsed value (or runtime guard) so regressions are caught by TS.Proposed fix
- const projects = JSON.parse(stdout); - const found = projects.find((p: any) => p.id === createdProjectId); + const projects: Array<{ id: string; displayName: string }> = JSON.parse(stdout); + const found = projects.find((p) => p.id === createdProjectId);As per coding guidelines, "Try to avoid the
anytype" and "Do NOT useas/any/type casts..."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/e2e/tests/general/cli.test.ts` around lines 167 - 170, The test currently uses an untyped callback `(p: any)` when searching parsed JSON (variables: stdout, projects, createdProjectId), which bypasses TypeScript checks; change to parse stdout into a typed array (e.g., const projects: Project[] = JSON.parse(stdout) or use a runtime guard) and use a properly typed predicate in the find call (e.g., (p: Project) => p.id === createdProjectId), or implement a small type-guard function isProject(obj): obj is Project and use it before asserting found.displayName; ensure you reference the existing Project interface/type (or declare one in the test) instead of using any.
🤖 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/e2e/tests/general/cli.test.ts`:
- Around line 82-86: Validate the session response body shape before storing
refreshToken: check sessionRes.body.refresh_token exists and is a non-empty
string (and optionally matches expected format) and throw a descriptive error
including the full body if validation fails; replace the direct assignment
refreshToken = sessionRes.body.refresh_token with an explicit assertion/guard
that fails setup immediately when the token is missing or invalid, referencing
sessionRes and refreshToken to locate the change.
- Around line 12-25: The runCli helper currently casts the execFile callback
error to any to produce exitCode; remove that cast and explicitly normalize
exitCode to a number: inside the execFile callback (in runCli) check if error is
null => exitCode = 0; otherwise inspect (error as NodeJS.ErrnoException).code
but without using any casts — if typeof error.code === "number" use that, if
it's a string (e.g. "ENOENT") map it to a numeric fallback (e.g. 1) or handle
known strings specially, and default to 1; return exitCode as a number (never a
string) and keep stderr/stdout as before.
---
Nitpick comments:
In `@apps/e2e/tests/general/cli.test.ts`:
- Line 66: The call that builds the sessions endpoint using string interpolation
(the niceFetch invocation that assigns sessionRes with
`${STACK_BACKEND_BASE_URL}/api/v1/auth/sessions`) should be changed to construct
the URL with the URL API (or the project's urlString helper) instead of raw
interpolation; replace the interpolated string with new
URL('/api/v1/auth/sessions', STACK_BACKEND_BASE_URL).toString() (or equivalent
urlString call) so the STACK_BACKEND_BASE_URL and the path are safely composed
and encoded before passing to niceFetch.
- Around line 167-170: The test currently uses an untyped callback `(p: any)`
when searching parsed JSON (variables: stdout, projects, createdProjectId),
which bypasses TypeScript checks; change to parse stdout into a typed array
(e.g., const projects: Project[] = JSON.parse(stdout) or use a runtime guard)
and use a properly typed predicate in the find call (e.g., (p: Project) => p.id
=== createdProjectId), or implement a small type-guard function isProject(obj):
obj is Project and use it before asserting found.displayName; ensure you
reference the existing Project interface/type (or declare one in the test)
instead of using any.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (1)
apps/e2e/tests/general/cli.test.ts
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (4)
apps/e2e/tests/general/cli.test.ts (2)
82-86:⚠️ Potential issue | 🟠 MajorAssert session response body shape before assigning
refreshToken.A 200 status alone does not guarantee
refresh_tokenexists and is valid.Proposed fix
if (sessionRes.status !== 200) { throw new Error(`Failed to create session: ${sessionRes.status} ${JSON.stringify(sessionRes.body)}`); } - refreshToken = sessionRes.body.refresh_token; + if ( + sessionRes.body == null || + typeof sessionRes.body !== "object" || + !("refresh_token" in sessionRes.body) || + typeof sessionRes.body.refresh_token !== "string" || + sessionRes.body.refresh_token.length === 0 + ) { + throw new Error(`Session response missing refresh_token: ${JSON.stringify(sessionRes.body)}`); + } + refreshToken = sessionRes.body.refresh_token;As per coding guidelines, "Any assumption you make should either be validated..." and "Fail early, fail loud."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/e2e/tests/general/cli.test.ts` around lines 82 - 86, The test assumes sessionRes.body.refresh_token exists after a 200 but doesn't validate the response shape; before assigning refreshToken, explicitly assert that sessionRes.body is an object and contains a non-empty string refresh_token (e.g., check typeof sessionRes.body.refresh_token === 'string' && sessionRes.body.refresh_token.length > 0) and throw a clear Error including sessionRes.status and JSON.stringify(sessionRes.body) if the check fails; update the assignment site where refreshToken is set from sessionRes.body.refresh_token so it only occurs after this validation.
20-25:⚠️ Potential issue | 🟠 MajorRemove
anycast inrunCliand normalizeexitCodeexplicitly.The cast bypasses type safety in a core test helper and can hide malformed process-error shapes.
Proposed fix
- }, (error, stdout, stderr) => { + }, (error: NodeJS.ErrnoException | null, stdout, stderr) => { + const exitCode = + error == null + ? 0 + : typeof error.code === "number" + ? error.code + : 1; resolve({ stdout: stdout.toString(), stderr: stderr.toString(), - exitCode: error ? (error as any).code ?? 1 : 0, + exitCode, }); });As per coding guidelines, "Try to avoid the
anytype...".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/e2e/tests/general/cli.test.ts` around lines 20 - 25, The test helper that resolves spawn results currently uses an unsafe any cast to extract exitCode; update the resolution in runCli (the callback that receives error, stdout, stderr) to remove the any cast and normalize exitCode via a proper type-guard: inspect error (typed as unknown or Error | null), check for presence of a numeric code (e.g., (error as NodeJS.ErrnoException).code) with typeof checks, coerce string codes to numbers when appropriate (parseInt) and fall back to 1 when absent, then return the normalized number along with stdout/stderr.packages/stack-cli/src/commands/exec.ts (1)
36-47:⚠️ Potential issue | 🟠 MajorNarrow broad catches in exec compile/execute flow and avoid implicit
any-typed control flow.Both catch blocks are catch-all wrappers; the compile path should only wrap syntax errors, and non-expected runtime throwables should be rethrown.
Proposed fix
- let fn; + let fn: (stackServerApp: unknown) => Promise<unknown>; try { fn = new AsyncFunction("stackServerApp", javascript); } catch (err: unknown) { - throw new CliError(`Syntax error in exec code: ${getErrorMessage(err)}`); + if (err instanceof SyntaxError) { + throw new CliError(`Syntax error in exec code: ${getErrorMessage(err)}`); + } + throw err; } - let result; + let result: unknown; try { result = await fn(project.app); } catch (err: unknown) { - throw new CliError(`Exec error: ${getErrorMessage(err)}`); + if (err instanceof Error || typeof err === "string" || (typeof err === "object" && err !== null)) { + throw new CliError(`Exec error: ${getErrorMessage(err)}`); + } + throw err; }Based on learnings, for
**/*.{ts,tsx}you should "NEVER try-catch-all" and "Code defensively. Prefer?? throwErr(...)over non-null assertions with good error messages explicitly stating the assumption."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/stack-cli/src/commands/exec.ts` around lines 36 - 47, The compile/execute try-catches are too broad: when constructing AsyncFunction("stackServerApp", javascript) only catch and wrap actual syntax errors (use err: unknown and check instanceof SyntaxError, otherwise rethrow), and when invoking fn(project.app) only catch expected runtime errors you intend to wrap (use err: unknown, narrow via instanceof or custom type guards, otherwise rethrow); replace any implicit assumptions with explicit checks or a small helper like throwErr(...) so you never swallow unexpected throwables—reference the AsyncFunction creation block, the two catch clauses around fn = new AsyncFunction(...) and result = await fn(project.app), getErrorMessage, and CliError when implementing these narrower catches.packages/stack-cli/src/lib/config.ts (1)
9-14:⚠️ Potential issue | 🟠 MajorDo not swallow all config read/parse failures; only treat missing file as empty and validate JSON shape.
Current behavior hides permission/corruption issues and silently continues with
{}.Proposed fix
+function isErrnoException(error: unknown): error is NodeJS.ErrnoException { + return typeof error === "object" && error !== null && "code" in error; +} + function readConfigJson(): Record<string, string> { + let raw: string; try { - return JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8")); - } catch { - return {}; + raw = fs.readFileSync(CONFIG_PATH, "utf-8"); + } catch (error: unknown) { + if (isErrnoException(error) && error.code === "ENOENT") { + return {}; + } + throw error; + } + + const parsed: unknown = JSON.parse(raw); + if (parsed == null || typeof parsed !== "object" || Array.isArray(parsed)) { + throw new Error(`Invalid config JSON in ${CONFIG_PATH}: expected an object`); + } + + const normalized: Record<string, string> = {}; + for (const [key, value] of Object.entries(parsed)) { + if (typeof value !== "string") { + throw new Error(`Invalid config value for '${key}' in ${CONFIG_PATH}: expected string`); + } + normalized[key] = value; } + return normalized; }As per coding guidelines, "NEVER try-catch-all", "Fail early, fail loud", and "NEVER silently use fallback values when type errors occur."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/stack-cli/src/lib/config.ts` around lines 9 - 14, The readConfigJson function currently swallows all errors; change it to only treat a missing file (fs.readFileSync error with code === 'ENOENT') as returning {} and rethrow any other fs errors; parse JSON separately and rethrow JSON.parse errors; finally validate the parsed value is an object mapping string->string (throw a TypeError if not) so the function still returns Record<string,string> when valid. Reference function: readConfigJson, constants: CONFIG_PATH, calls: fs.readFileSync and JSON.parse.
🧹 Nitpick comments (1)
apps/e2e/tests/general/cli.test.ts (1)
167-170: Avoidanyin project lookup; validate parsed output shape first.This keeps failures explicit when CLI output changes unexpectedly.
Proposed fix
const projects = JSON.parse(stdout); - const found = projects.find((p: any) => p.id === createdProjectId); + if (!Array.isArray(projects)) { + throw new Error(`Expected projects array, got: ${JSON.stringify(projects)}`); + } + const found = projects.find( + (p): p is { id: string; displayName: string } => + p != null && + typeof p === "object" && + "id" in p && + "displayName" in p && + typeof p.id === "string" && + typeof p.displayName === "string" && + p.id === createdProjectId, + );As per coding guidelines, "Try to avoid the
anytype..." and "Fail early, fail loud."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/e2e/tests/general/cli.test.ts` around lines 167 - 170, The test currently parses CLI JSON into an untyped value and uses (p: any) in the lookup; change it to parse into unknown, validate the shape (ensure parsed value is an array and each item has id and displayName) before casting to a typed Project[], then use that Project[] in the find (referencing projects, createdProjectId, and found) so the test fails early if the CLI output shape changes; implement a runtime check (Array.isArray and typeof checks for id/displayName) or a small type guard to avoid using any and then assert found.displayName === "CLI Test".
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/stack-cli/package.json`:
- Around line 13-17: The package.json for the CLI (which provides the "bin"
entry and uses the "build": "tsdown" script) lacks an engines.node field; add an
"engines": { "node": ">=20.0.0" } entry to this package.json to enforce the
workspace Node minimum and ensure tsdown targets an appropriate runtime;
additionally, configure tsdown's target (either via a "tsdown" field in this
package.json or the project's tsdown config) to downlevel output for Node 20
(e.g., "target": "node20") so the built CLI emits compatible syntax.
In `@packages/stack-cli/src/commands/exec.ts`:
- Around line 13-17: The current getErrorMessage implementation swallows all
throwables when calling JSON.stringify(err); change the try/catch so it only
catches the expected stringify failure (e.g., TypeError from circular
structures) and falls back to String(err), but rethrow any other unexpected
errors — i.e., replace the broad catch with a conditional catch that handles
TypeError (or checks the error message contains "circular" if you prefer) and
throws the caught error for any other error types; update references in
getErrorMessage to ensure only expected stringify errors are handled.
---
Duplicate comments:
In `@apps/e2e/tests/general/cli.test.ts`:
- Around line 82-86: The test assumes sessionRes.body.refresh_token exists after
a 200 but doesn't validate the response shape; before assigning refreshToken,
explicitly assert that sessionRes.body is an object and contains a non-empty
string refresh_token (e.g., check typeof sessionRes.body.refresh_token ===
'string' && sessionRes.body.refresh_token.length > 0) and throw a clear Error
including sessionRes.status and JSON.stringify(sessionRes.body) if the check
fails; update the assignment site where refreshToken is set from
sessionRes.body.refresh_token so it only occurs after this validation.
- Around line 20-25: The test helper that resolves spawn results currently uses
an unsafe any cast to extract exitCode; update the resolution in runCli (the
callback that receives error, stdout, stderr) to remove the any cast and
normalize exitCode via a proper type-guard: inspect error (typed as unknown or
Error | null), check for presence of a numeric code (e.g., (error as
NodeJS.ErrnoException).code) with typeof checks, coerce string codes to numbers
when appropriate (parseInt) and fall back to 1 when absent, then return the
normalized number along with stdout/stderr.
In `@packages/stack-cli/src/commands/exec.ts`:
- Around line 36-47: The compile/execute try-catches are too broad: when
constructing AsyncFunction("stackServerApp", javascript) only catch and wrap
actual syntax errors (use err: unknown and check instanceof SyntaxError,
otherwise rethrow), and when invoking fn(project.app) only catch expected
runtime errors you intend to wrap (use err: unknown, narrow via instanceof or
custom type guards, otherwise rethrow); replace any implicit assumptions with
explicit checks or a small helper like throwErr(...) so you never swallow
unexpected throwables—reference the AsyncFunction creation block, the two catch
clauses around fn = new AsyncFunction(...) and result = await fn(project.app),
getErrorMessage, and CliError when implementing these narrower catches.
In `@packages/stack-cli/src/lib/config.ts`:
- Around line 9-14: The readConfigJson function currently swallows all errors;
change it to only treat a missing file (fs.readFileSync error with code ===
'ENOENT') as returning {} and rethrow any other fs errors; parse JSON separately
and rethrow JSON.parse errors; finally validate the parsed value is an object
mapping string->string (throw a TypeError if not) so the function still returns
Record<string,string> when valid. Reference function: readConfigJson, constants:
CONFIG_PATH, calls: fs.readFileSync and JSON.parse.
---
Nitpick comments:
In `@apps/e2e/tests/general/cli.test.ts`:
- Around line 167-170: The test currently parses CLI JSON into an untyped value
and uses (p: any) in the lookup; change it to parse into unknown, validate the
shape (ensure parsed value is an array and each item has id and displayName)
before casting to a typed Project[], then use that Project[] in the find
(referencing projects, createdProjectId, and found) so the test fails early if
the CLI output shape changes; implement a runtime check (Array.isArray and
typeof checks for id/displayName) or a small type guard to avoid using any and
then assert found.displayName === "CLI Test".
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (4)
apps/e2e/tests/general/cli.test.tspackages/stack-cli/package.jsonpackages/stack-cli/src/commands/exec.tspackages/stack-cli/src/lib/config.ts
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> * **New Features** * Added Stack CLI with authentication (login/logout) commands. * Added project management commands to list and create projects. * Added configuration management to pull and push project settings. * Added code execution capability to run JavaScript expressions. * Added initialization command for Stack Auth setup. * **Tests** * Added comprehensive end-to-end test suite for CLI functionality. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
Summary by CodeRabbit
Release Notes
New Features
Tests