From 20a9ba430f540a5a69e910e22137cb5e090b0e82 Mon Sep 17 00:00:00 2001 From: "base44-builder-dev[bot]" <249543574+base44-builder-dev[bot]@users.noreply.github.com> Date: Thu, 29 Oct 2026 14:07:49 +0000 Subject: [PATCH] fix(dev): reload functions when base44/shared/ changes base44 dev watched only the functions and entities directories, so editing a file under base44/shared/ - which functions import as ../../shared/... - left the running functions on the old code until something in functions/ changed. Watch the shared dir (resolved as function config does, via a new getSharedDir) and reload functions on a change there too. Co-Authored-By: Claude Opus 5.5 --- packages/cli/src/cli/dev/dev-server/main.ts | 8 +++-- .../cli/src/core/resources/function/config.ts | 7 ++++- packages/cli/tests/cli/dev.spec.ts | 30 +++++++++++++++++++ .../with-shared-code/base44/.app.jsonc | 4 +++ .../with-shared-code/base44/config.jsonc | 3 ++ .../base44/functions/greet/entry.ts | 3 ++ .../base44/shared/greeting.ts | 1 + 7 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 packages/cli/tests/fixtures/with-shared-code/base44/.app.jsonc create mode 100644 packages/cli/tests/fixtures/with-shared-code/base44/config.jsonc create mode 100644 packages/cli/tests/fixtures/with-shared-code/base44/functions/greet/entry.ts create mode 100644 packages/cli/tests/fixtures/with-shared-code/base44/shared/greeting.ts diff --git a/packages/cli/src/cli/dev/dev-server/main.ts b/packages/cli/src/cli/dev/dev-server/main.ts index d067f97e8..4da5fbe73 100644 --- a/packages/cli/src/cli/dev/dev-server/main.ts +++ b/packages/cli/src/cli/dev/dev-server/main.ts @@ -11,6 +11,7 @@ import { createFunctionRuntime } from "@/cli/dev/dev-server/function-runtime.js" import { createFunctionRouter } from "@/cli/dev/dev-server/routes/functions.js"; import { theme } from "@/cli/utils/index.js"; import type { ProjectData } from "@/core/project/types.js"; +import { getSharedDir } from "@/core/resources/function/config.js"; import { Database } from "./db/database.js"; import { type BroadcastEntityEvent, @@ -205,9 +206,11 @@ export async function createDevServer( broadcastEntityEvent(io, appId, entityName, event); }; + const functionsDir = join(dirname(project.configPath), project.functionsDir); const base44ConfigWatcher = new WatchBase44( { - functions: join(dirname(project.configPath), project.functionsDir), + functions: functionsDir, + shared: getSharedDir(functionsDir), entities: join(dirname(project.configPath), project.entitiesDir), }, devLogger, @@ -216,7 +219,8 @@ export async function createDevServer( try { const { functions, entities } = await options.loadResources(); - if (name === "functions") { + // Functions import base44/shared/, so a change there restarts them too. + if (name === "functions" || name === "shared") { const previousFunctionCount = functionManager.getFunctionNames().length; await functionManager.reload(functions); diff --git a/packages/cli/src/core/resources/function/config.ts b/packages/cli/src/core/resources/function/config.ts index a3500f37b..885c24663 100644 --- a/packages/cli/src/core/resources/function/config.ts +++ b/packages/cli/src/core/resources/function/config.ts @@ -18,13 +18,18 @@ import type { import { FunctionConfigSchema } from "@/core/resources/function/schema.js"; import { pathExists, readJsonFile } from "@/core/utils/fs.js"; +/** The shared-code directory, `base44/shared/`: the sibling of the functions dir. */ +export function getSharedDir(functionsDir: string): string { + return resolve(functionsDir, "..", "shared"); +} + /** * Collect every file under `base44/shared/`. These are uploaded alongside each * function so shared modules imported via `../../shared/...` bundle server-side. * The bundler tree-shakes anything a given function doesn't import. */ async function readSharedFiles(functionsDir: string): Promise { - const sharedDir = resolve(functionsDir, "..", "shared"); + const sharedDir = getSharedDir(functionsDir); if (!(await pathExists(sharedDir))) { return []; } diff --git a/packages/cli/tests/cli/dev.spec.ts b/packages/cli/tests/cli/dev.spec.ts index 929599373..b4d0da4fc 100644 --- a/packages/cli/tests/cli/dev.spec.ts +++ b/packages/cli/tests/cli/dev.spec.ts @@ -1,3 +1,5 @@ +import { writeFile } from "node:fs/promises"; +import { join } from "node:path"; import jwt from "jsonwebtoken"; import { describe, expect, it } from "vitest"; import { @@ -368,6 +370,34 @@ describe("dev command", () => { }); }); + it("serves a function importing base44/shared/ and reloads when it changes", async () => { + await t.givenLoggedInWithProject(fixture("with-shared-code")); + + const handle = await t.runLive("dev"); + const devServerUrl = await waitForDevServer(handle); + const callGreet = async () => { + const response = await fetch( + `${devServerUrl}/api/apps/${t.api.appId}/functions/greet`, + { headers: { "X-App-Id": t.api.appId } }, + ); + expect(response.status).toBe(200); + return (await response.json()) as Record; + }; + + expect(await callGreet()).toEqual({ greeting: "hello" }); + + await writeFile( + join(t.getTempDir(), "project", "base44", "shared", "greeting.ts"), + 'export const GREETING = "changed";\n', + ); + await handle.waitForOutput(/Reloaded functions: greet/, 10000); + + expect(await callGreet()).toEqual({ greeting: "changed" }); + + const result = await handle.stop(); + t.expectResult(result).toSucceed(); + }); + it("allows service-role JWTs to bypass denied entity create RLS", async () => { await t.givenLoggedInWithProject(fixture("with-private-note-entity")); diff --git a/packages/cli/tests/fixtures/with-shared-code/base44/.app.jsonc b/packages/cli/tests/fixtures/with-shared-code/base44/.app.jsonc new file mode 100644 index 000000000..d7852426c --- /dev/null +++ b/packages/cli/tests/fixtures/with-shared-code/base44/.app.jsonc @@ -0,0 +1,4 @@ +// Base44 App Configuration +{ + "id": "test-app-id" +} diff --git a/packages/cli/tests/fixtures/with-shared-code/base44/config.jsonc b/packages/cli/tests/fixtures/with-shared-code/base44/config.jsonc new file mode 100644 index 000000000..0b5a7d8a2 --- /dev/null +++ b/packages/cli/tests/fixtures/with-shared-code/base44/config.jsonc @@ -0,0 +1,3 @@ +{ + "name": "Shared Code App" +} diff --git a/packages/cli/tests/fixtures/with-shared-code/base44/functions/greet/entry.ts b/packages/cli/tests/fixtures/with-shared-code/base44/functions/greet/entry.ts new file mode 100644 index 000000000..354ec422e --- /dev/null +++ b/packages/cli/tests/fixtures/with-shared-code/base44/functions/greet/entry.ts @@ -0,0 +1,3 @@ +import { GREETING } from "../../shared/greeting.ts"; + +Deno.serve(() => Response.json({ greeting: GREETING })); diff --git a/packages/cli/tests/fixtures/with-shared-code/base44/shared/greeting.ts b/packages/cli/tests/fixtures/with-shared-code/base44/shared/greeting.ts new file mode 100644 index 000000000..02d3d0a77 --- /dev/null +++ b/packages/cli/tests/fixtures/with-shared-code/base44/shared/greeting.ts @@ -0,0 +1 @@ +export const GREETING = "hello";