@base44-preview/cli 0.1.5-pr.575.88555e2 → 0.1.5-pr.576.7190826

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Entry point for standalone compiled binaries (built with `bun build --compile`).
3
3
  *
4
- * This file embeds a single assets tarball (templates + deno-runtime) into the
4
+ * This file embeds a single assets tarball (templates + backend-runtime) into the
5
5
  * binary and extracts it to ~/.base44/assets/<version>/ on first run.
6
6
  * The npm distribution uses bin/run.js instead — this file is only used
7
7
  * for the compiled binary path.
@@ -25,7 +25,11 @@ const assetsDir = join(homedir(), ".base44", "assets", VERSION);
25
25
  // Extract assets if this version hasn't been unpacked yet.
26
26
  // Bun.file() reads from the virtual $bunfs, then we write to real disk
27
27
  // so the rest of the CLI can access these files normally.
28
- if (!existsSync(assetsDir)) {
28
+ //
29
+ // The directory existing is not proof its contents are current: an install
30
+ // that predates a renamed or added asset directory would otherwise keep the
31
+ // stale layout forever, so check for an expected directory too.
32
+ if (!existsSync(assetsDir) || !existsSync(join(assetsDir, "backend-runtime"))) {
29
33
  mkdirSync(assetsDir, { recursive: true });
30
34
  const bytes = await Bun.file(assetsTarball).bytes();
31
35
  const archive = new Bun.Archive(bytes);
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Local implementation of the `base44:runtime` module.
3
+ *
4
+ * Deployed backend functions import this module by its bare specifier
5
+ * (`import { secrets } from "base44:runtime"`). No such module exists on a
6
+ * developer machine, so `deno.json` maps the specifier onto this file and
7
+ * `base44 dev` hands that import map to the Deno subprocess.
8
+ *
9
+ * The surface mirrors production; the deliberate differences are called out on
10
+ * each export below.
11
+ */
12
+
13
+ interface Secrets {
14
+ get(name: string): string;
15
+ }
16
+
17
+ /**
18
+ * Secrets configured for the app.
19
+ *
20
+ * In production these come from the app's environment variables. Locally they
21
+ * come from the environment `base44 dev` was started with — values stored with
22
+ * `base44 secrets set` are deliberately not fetched, so a production secret is
23
+ * never copied onto a developer machine. Export the variable in your shell to
24
+ * exercise a function that reads it.
25
+ *
26
+ * Reading an unset secret throws, matching production, where a missing secret
27
+ * read at module scope crashes boot rather than surfacing as a 500.
28
+ */
29
+ export const secrets: Secrets = {
30
+ get(name: string): string {
31
+ const value = Deno.env.get(name);
32
+ if (value === undefined) {
33
+ throw new Error(
34
+ `Secret "${name}" is not set. Locally, secrets are read from the environment that \`base44 dev\` was started with, not from \`base44 secrets set\` — export ${name} and restart the dev server.`,
35
+ );
36
+ }
37
+ return value;
38
+ },
39
+ };
40
+
41
+ // Holds a strong reference to in-flight work so a floating promise cannot be
42
+ // collected before it settles.
43
+ const pending = new Set<Promise<unknown>>();
44
+
45
+ /**
46
+ * Continue work after the response has been sent.
47
+ *
48
+ * In production this keeps the Worker alive until the promise settles. Locally
49
+ * the function is a long-lived server process, so there is nothing to hold
50
+ * open; the promise is tracked only so a rejection is reported against the
51
+ * function instead of surfacing as an unhandled rejection.
52
+ */
53
+ export function waitUntil(promise: Promise<unknown>): void {
54
+ const tracked = Promise.resolve(promise)
55
+ .catch((error: unknown) => {
56
+ console.error("[base44:runtime] waitUntil task failed:", error);
57
+ })
58
+ .finally(() => {
59
+ pending.delete(tracked);
60
+ });
61
+ pending.add(tracked);
62
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "imports": {
3
+ "base44:runtime": "./base44-runtime.ts"
4
+ }
5
+ }
@@ -4,6 +4,13 @@
4
4
  * This script is executed by Deno to run user functions.
5
5
  * It patches Deno.serve to inject a dynamic port before importing the user's function.
6
6
  *
7
+ * Two handler shapes are supported:
8
+ * - `export default async function (req) { ... }` — the shape the deployed
9
+ * runtime expects. The wrapper serves it after importing the module.
10
+ * - `Deno.serve(handler)` — the legacy shape. A module that calls Deno.serve
11
+ * while being imported serves itself and its default export is ignored,
12
+ * mirroring the deployed bundler's precedence.
13
+ *
7
14
  * Environment variables:
8
15
  * - FUNCTION_PATH: Absolute path to the user's function entry file
9
16
  * - FUNCTION_PORT: Port number for the function to listen on
@@ -25,6 +32,10 @@ if (!functionPath) {
25
32
  // Store the original Deno.serve
26
33
  const originalServe = Deno.serve.bind(Deno);
27
34
 
35
+ // Set when the user's module calls Deno.serve while it is being imported.
36
+ // Such a module serves itself, so its default export (if any) is ignored.
37
+ let servedDuringImport = false;
38
+
28
39
  // Patch Deno.serve to inject our port and add onListen callback.
29
40
  const patchedServe = (
30
41
  optionsOrHandler:
@@ -33,6 +44,8 @@ const patchedServe = (
33
44
  | (Deno.ServeOptions & { handler: Deno.ServeHandler }),
34
45
  maybeHandler?: Deno.ServeHandler,
35
46
  ): Deno.HttpServer<Deno.NetAddr> => {
47
+ servedDuringImport = true;
48
+
36
49
  const onListen = () => {
37
50
  // This message is used by FunctionManager to detect when the function is ready
38
51
  console.log(`[${functionName}] Listening on http://localhost:${port}`);
@@ -68,13 +81,56 @@ Object.defineProperty(Deno, "serve", {
68
81
  configurable: true,
69
82
  });
70
83
 
84
+ type FetchHandler = (req: Request) => Response | Promise<Response>;
85
+
86
+ /**
87
+ * Pull the request handler off the user's module namespace, accepting both
88
+ * `export default async function (req)` and the `export default { fetch }`
89
+ * object form.
90
+ */
91
+ const resolveDefaultHandler = (
92
+ module: Record<string, unknown>,
93
+ ): FetchHandler | null => {
94
+ const exported = module.default;
95
+
96
+ if (typeof exported === "function") {
97
+ return exported as FetchHandler;
98
+ }
99
+
100
+ if (exported && typeof exported === "object") {
101
+ const { fetch } = exported as { fetch?: unknown };
102
+ if (typeof fetch === "function") {
103
+ return (fetch as FetchHandler).bind(exported);
104
+ }
105
+ }
106
+
107
+ return null;
108
+ };
109
+
71
110
  console.log(`[${functionName}] Starting function from ${functionPath}`);
72
111
 
73
- // Dynamically import the user's function
74
- // The function will call Deno.serve which is now patched to use our port
112
+ // Dynamically import the user's function. A legacy function calls Deno.serve
113
+ // during import, which is now patched to use our port.
114
+ let functionModule: Record<string, unknown>;
75
115
  try {
76
- await import(functionPath);
116
+ functionModule = await import(functionPath);
77
117
  } catch (error) {
78
118
  console.error(`[${functionName}] Failed to load function:`, error);
79
119
  Deno.exit(1);
80
120
  }
121
+
122
+ // Nothing served itself during import, so the module is expected to export a
123
+ // handler. Serving it here goes through the same patched Deno.serve, so the
124
+ // readiness line still gets printed exactly once.
125
+ if (!servedDuringImport) {
126
+ const handler = resolveDefaultHandler(functionModule);
127
+
128
+ if (!handler) {
129
+ console.error(
130
+ `[${functionName}] No request handler found. Export one with \`export default async function (req) { ... }\`.`,
131
+ );
132
+ Deno.exit(1);
133
+ }
134
+
135
+ Deno.serve(handler);
136
+ }
@@ -8,7 +8,6 @@
8
8
  "allowed_operations": ["read", "create", "update", "delete"]
9
9
  }
10
10
  ],
11
- "selected_skill_names": ["weekly-report"],
12
11
  // Optional: let the agent remember facts across conversations.
13
12
  // scope: "user" (per end-user), "global" (shared), or "both".
14
13
  "memory_config": {