Skip to content

CLI local dev functions#249

Open
artemdemo wants to merge 10 commits intomainfrom
cli-local-dev-functions
Open

CLI local dev functions#249
artemdemo wants to merge 10 commits intomainfrom
cli-local-dev-functions

Conversation

@artemdemo
Copy link
Contributor

@artemdemo artemdemo commented Feb 16, 2026

Note

Description

This PR adds local development support for Base44 backend functions by introducing a Deno runtime integration. When running base44 dev, functions are automatically started in Deno subprocesses and proxied through the dev server, allowing developers to test function deployments locally without deploying to production.

Related Issue

None

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Refactoring (no functional changes)
  • Other (please describe):

Changes Made

  • Created deno-runtime/ directory with a Deno-specific wrapper that patches Deno.serve to inject dynamic ports
  • Added FunctionManager class to spawn, manage, and proxy Deno function processes with port allocation and lifecycle management
  • Integrated function routing into dev server at /api/apps/:appId/functions/:functionName
  • Added DependencyNotFoundError to handle missing Deno installation gracefully with helpful error messages
  • Created createDevLogger utility for consistent dev server logging with color-coded output
  • Updated build pipeline to bundle the Deno runtime wrapper alongside the CLI
  • Added separate tsconfig.json for Deno runtime to use @types/deno instead of Node types, preventing type conflicts
  • Verify Deno installation at dev server startup and provide clear error messages if missing
  • Updated biome.json to exclude entire dist/ and node_modules/ directories

Testing

  • I have tested these changes locally
  • I have added/updated tests as needed
  • All tests pass (npm test)

Checklist

  • My code follows the project's style guidelines
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation (if applicable)
  • My changes generate no new warnings
  • I have updated docs/ (AGENTS.md) if I made architectural changes

Additional Notes

  • The Deno wrapper uses environment variables (FUNCTION_PATH, FUNCTION_PORT, FUNCTION_NAME) to configure function execution
  • Functions are started on-demand when first accessed and remain running until dev server shutdown
  • The FunctionManager handles process lifecycle, stdout/stderr piping, and graceful shutdown on SIGINT/SIGTERM
  • Updated docs/error-handling.md to document the new DEPENDENCY_NOT_FOUND error code
  • Added README.md in deno-runtime/ explaining the separation between Node.js CLI and Deno function runtime

🤖 Generated by Claude | 2026-02-18 16:45 UTC

@artemdemo artemdemo force-pushed the cli-local-dev-functions branch from fb1e5d0 to 1a49abd Compare February 16, 2026 11:11
@github-actions
Copy link
Contributor

github-actions bot commented Feb 16, 2026

🚀 Package Preview Available!


Install this PR's preview build with npm:

npm i @base44-preview/cli@0.0.32-pr.249.2d6f02e

Prefer not to change any import paths? Install using npm alias so your code still imports base44:

npm i "base44@npm:@base44-preview/cli@0.0.32-pr.249.2d6f02e"

Or add it to your package.json dependencies:

{
  "dependencies": {
    "base44": "npm:@base44-preview/cli@0.0.32-pr.249.2d6f02e"
  }
}

Preview published to npm registry — try new features instantly!

"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"typeRoots": ["../node_modules/@types"],
"types": ["deno"]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Deno types are different from Node and in order to handle it without collision between types I need separate tsconfig

import type { BackendFunction } from "@/core/resources/function/schema.js";
import type { Logger } from "../createDevLogger";

const __dirname = dirname(fileURLToPath(import.meta.url));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is the way to figure out where will be bundled deno runtime wrapper that I use for functions.

import.meta.url will be something like file:///Users/username/Development/test-project/cli/dist/cli/index.js

and I know that output of the wrapper will be next to cli folder

Comment on lines 70 to 72
clackLog.info(
`Loaded functions: ${functionManager.functionNames().join(", ")}`,
);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

here I'm using clack.log because server is not ready yet and dev command didn't send outro of clack

@base44 base44 deleted a comment from claude bot Feb 17, 2026
@base44 base44 deleted a comment from claude bot Feb 17, 2026
@base44 base44 deleted a comment from claude bot Feb 17, 2026
@base44 base44 deleted a comment from claude bot Feb 17, 2026
@base44 base44 deleted a comment from claude bot Feb 17, 2026
@base44 base44 deleted a comment from claude bot Feb 17, 2026
@base44 base44 deleted a comment from claude bot Feb 17, 2026
@base44 base44 deleted a comment from claude bot Feb 17, 2026
@base44 base44 deleted a comment from claude bot Feb 17, 2026
@artemdemo artemdemo force-pushed the cli-local-dev-functions branch 3 times, most recently from b23b5c7 to 1d65858 Compare February 17, 2026 08:59
@@ -1,5 +1,5 @@
{
"$schema": "https://biomejs.dev/schemas/2.3.13/schema.json",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

no related to PR, but now schema is aligned to our version

}
},
"files": {
"includes": ["**", "!**/dist/**", "!**/node_modules/**", "!**/tests/fixtures/**", "!**/*.d.ts"]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

no related to PR, but the correct syntaxis is not to mention ** at the end

@base44 base44 deleted a comment from claude bot Feb 17, 2026
@base44 base44 deleted a comment from claude bot Feb 17, 2026
@base44 base44 deleted a comment from claude bot Feb 17, 2026
@artemdemo artemdemo marked this pull request as ready for review February 17, 2026 09:02
@base44 base44 deleted a comment from claude bot Feb 17, 2026
@artemdemo artemdemo force-pushed the cli-local-dev-functions branch from 8c9635d to c910d1d Compare February 17, 2026 10:58
@base44 base44 deleted a comment from claude bot Feb 17, 2026
@base44 base44 deleted a comment from claude bot Feb 17, 2026
@base44 base44 deleted a comment from claude bot Feb 17, 2026
@claude
Copy link

claude bot commented Feb 17, 2026

Claude finished @artemdemo's task in 1m 35s —— View job


PR Review Complete ✅

I've reviewed PR #249 and the implementation looks solid overall. Here's my comprehensive feedback:


Strengths

  1. Clean Architecture - Excellent separation between Node.js CLI (src/) and Deno runtime (deno-runtime/). The TypeScript config split prevents type conflicts.

  2. Proper Error Handling - Good use of structured errors (DependencyNotFoundError, InternalError, InvalidInputError) following project patterns. The Deno installation check (verifyDenoIsInstalled()) provides helpful hints.

  3. Process Management - Well-implemented lifecycle management with:

    • Port allocation using get-port to avoid conflicts
    • 30s timeout for readiness detection
    • Graceful shutdown with SIGINT/SIGTERM handlers
    • Proper cleanup of running processes
  4. Logging Architecture - createDevLogger provides timestamped, colorized output separate from CLI logging, which is appropriate for long-running dev server processes.

  5. Build System - Build correctly bundles both CLI and Deno runtime, maintaining proper watch mode support.


🔍 Issues & Suggestions

Critical Issues

1. Security: --allow-all flag (function-manager.ts:136)

spawn("deno", ["run", "--allow-all", WRAPPER_PATH], {

This grants unrestricted permissions to user functions. Consider using granular permissions:

spawn("deno", ["run", "--allow-net", "--allow-env", "--allow-read", WRAPPER_PATH], {

Or document why full permissions are necessary for local dev. Fix this →

2. Missing Response Validation (routes/functions.ts:30)
The proxy forwards responses without validating content. If a function crashes or returns malformed data, errors might be opaque to users. Consider adding response validation or at least logging response status codes for debugging.

3. Race Condition in waitForReady() (function-manager.ts:179-221)
The exit handler is registered after the timeout is set. If the process exits immediately (before onData listener is attached), the timeout might leak. Consider:

// Register exit handler BEFORE setting timeout
runningFunc.process.on("exit", (code) => { ... });
const timeout = setTimeout(() => { ... }, READY_TIMEOUT);

Code Quality Issues

4. Console.log Usage (createDevLogger.ts:29-36)
The logger uses raw console.log/console.error/console.warn, which is fine for dev server but inconsistent with project guidelines:

@clack/prompts only - For all user interaction (prompts, spinners, logs). No console.log

However, I see the PR description mentions "server is not ready yet and dev command didn't send outro of clack" (review comment on main.ts:72). This is a valid exception for long-running dev server output, but should be documented. The createDevLogger docstring should explain why it bypasses @clack/prompts.

5. Incomplete Zod Validation
According to CLAUDE.md:

Zod validation - Required for all external data (API responses, config files)

The proxy forwards user function responses without schema validation. While functions are user-controlled, consider validating at least the response structure (status code, headers) for early error detection during development.

6. Cross-platform Path Handling (function-manager.ts:14-16)

const __dirname = dirname(fileURLToPath(import.meta.url));
const WRAPPER_PATH = join(__dirname, "../deno-runtime/main.js");

This assumes the bundled structure. If the build output changes, this path breaks silently. Consider:

  • Adding a build-time check that main.js exists at the expected location
  • Or using a more robust path resolution (e.g., relative to package root)

7. Missing Error Context in Proxy (routes/functions.ts:92-103)
When proxy requests fail, the error handler checks !res.headersSent but doesn't log which function or request failed. Add context:

logger.error(`Function proxy error for "${functionName}":`, error);

8. Unused isPrefixed Parameter (createDevLogger.ts:24)
The isPrefixed parameter defaults to true, but in main.ts:64 it's explicitly set to false:

const devLogger = createDevLogger(false);

This suggests the prefixed variant isn't used. Consider removing it or documenting when each variant should be used.

Minor Issues

9. Biome Config Changes (biome.json)
The changes are unrelated to this PR (schema version update, glob pattern cleanup). While beneficial, these should ideally be in a separate commit for clearer git history.

10. Magic String in Readiness Detection (function-manager.ts:200)

if (output.includes("Listening on")) {

This string is duplicated between deno-runtime/main.ts:39 and function-manager.ts:200. Consider extracting to a shared constant or documenting the contract more clearly in both files.

11. Function Process Stderr Handling (function-manager.ts:159-164)
All stderr output is logged as errors. Deno might write informational messages to stderr (warnings, deprecations). Consider differentiating between stderr content types or documenting that all stderr is treated as error-level.

12. Missing Tests
The PR checklist shows:

  • I have added/updated tests as needed
  • All tests pass (npm test)

No test files were added. Consider adding:

  • Unit tests for FunctionManager (port allocation, process spawning, readiness detection)
  • Integration tests for function routes (request proxying, error handling)

📋 Documentation

13. Error Handling Docs Updated
Good that docs/error-handling.md was updated to include DependencyNotFoundError. The table at line 117 correctly documents the new error code.

14. Deno Runtime README
deno-runtime/README.md is concise and explains the purpose well. Consider adding:

  • Security model (why --allow-all or granular permissions)
  • Example of a minimal function that works with the wrapper

🎯 Recommendations

  1. Address the security concern with --allow-all (highest priority)
  2. Fix the race condition in waitForReady()
  3. Add documentation to createDevLogger explaining why it uses console.* instead of @clack/prompts
  4. Add tests for the new functionality
  5. Extract magic strings to constants for better maintainability
  6. Consider Zod validation for function responses (at least basic structure validation)

Summary

This is a well-structured implementation that follows most project conventions. The core architecture is sound, and the code is generally high quality. The main concerns are:

  • Security: overly permissive Deno flags
  • Reliability: race condition in readiness detection
  • Testing: no tests included

Once these are addressed, this will be a solid addition to the CLI. Nice work on the clean separation of concerns and thoughtful error handling! 🎉


@artemdemo artemdemo requested a review from kfirstri February 17, 2026 11:23
@artemdemo artemdemo changed the title Cli local dev functions CLI local dev functions Feb 17, 2026
@base44 base44 deleted a comment from claude bot Feb 17, 2026
Copy link
Collaborator

@kfirstri kfirstri left a comment

Choose a reason for hiding this comment

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

Amazing job 💪
I think we can continue talking about this tomorrow

const { port: userPort } = options;
const port = userPort ?? (await getPort({ port: DEFAULT_PORT }));

const { project } = await readProjectConfig();
Copy link
Collaborator

Choose a reason for hiding this comment

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

What do you think about injecting the project config object, and the function data as an argument to the createDevServer function? it will help us test this more easily..

  1. you can grab the project / function at the dev.ts command
  2. pass it to the createDevServer
  3. less coupling for the dev-server 💪

sharing some thoughts: ideally we would have a coreSDK that would've been passed into createDevServer, and then when you'll do stuff like const project = coreSDK.project or const functions = coreSDK.functions.getAll() we would be able to mock the SDK in tests..

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sure, moved

@base44 base44 deleted a comment from claude bot Feb 18, 2026
@artemdemo artemdemo force-pushed the cli-local-dev-functions branch from c2f12e1 to fd91937 Compare February 18, 2026 12:03
@kfirstri kfirstri moved this from Backlog to In review in CLI Development Feb 18, 2026
@base44 base44 deleted a comment from claude bot Feb 18, 2026
@base44 base44 deleted a comment from claude bot Feb 18, 2026
@claude
Copy link

claude bot commented Feb 18, 2026

README check ran. 5 issue(s) found and applied: added missing commands (eject, connectors pull/push, types generate), updated deploy description to match codebase, and improved logout/whoami descriptions for consistency. README.md has been updated in this branch.

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

Labels

None yet

Projects

Status: In review

Development

Successfully merging this pull request may close these issues.

2 participants

Comments