@base44-preview/cli 0.1.5-pr.577.1701370 → 0.1.6-pr.555.928bd24
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.
- package/bin/binary-entry.ts +6 -2
- package/dist/assets/backend-runtime/base44-runtime.ts +62 -0
- package/dist/assets/backend-runtime/import-map.json +5 -0
- package/dist/assets/backend-runtime/main.ts +169 -0
- package/dist/cli/index.js +5436 -1587
- package/dist/cli/index.js.map +44 -17
- package/package.json +6 -1
- package/dist/assets/deno-runtime/main.ts +0 -80
- /package/dist/assets/{deno-runtime → backend-runtime}/exec.ts +0 -0
package/bin/binary-entry.ts
CHANGED
|
@@ -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 +
|
|
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
|
-
|
|
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 `import-map.json` maps the specifier onto this file and
|
|
7
|
+
* `base44 dev` hands that import map to the Deno subprocess.
|
|
8
|
+
*
|
|
9
|
+
* Like the deployed module, this is only a delegator: it holds no state and
|
|
10
|
+
* reads no environment. Everything is served by the `globalThis.Base44` bridge
|
|
11
|
+
* that the host installs before loading the function — `main.ts` locally, the
|
|
12
|
+
* generated Worker entry when deployed. Keeping the two the same shape means a
|
|
13
|
+
* change to what a secret *is* only touches the host, never this file.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** Contract the host installs on `globalThis.Base44`. */
|
|
17
|
+
export interface Base44Bridge {
|
|
18
|
+
waitUntil(promise: Promise<unknown>): void;
|
|
19
|
+
secrets: { get(name: string): string | undefined };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function bridge(): Base44Bridge {
|
|
23
|
+
const installed = (globalThis as { Base44?: Base44Bridge }).Base44;
|
|
24
|
+
if (!installed) {
|
|
25
|
+
throw new Error(
|
|
26
|
+
'base44:runtime was imported without a Base44 function host. It is only available inside a backend function — "base44 dev" installs the bridge before loading your code.',
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
return installed;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* App secrets.
|
|
34
|
+
*
|
|
35
|
+
* Deployed, these come from the Worker env binding of the current request.
|
|
36
|
+
* Locally the host reads them from the environment `base44 dev` was started
|
|
37
|
+
* with, so a production secret is never copied onto a developer machine —
|
|
38
|
+
* export the variable in your shell to exercise a function that reads it.
|
|
39
|
+
*
|
|
40
|
+
* Returns `undefined` for an unset name rather than throwing, matching the
|
|
41
|
+
* deployed signature. Code needing a secret should construct inside the
|
|
42
|
+
* handler: at module scope a client built from `undefined` throws during boot,
|
|
43
|
+
* where no try/catch is reached and nothing is logged.
|
|
44
|
+
*/
|
|
45
|
+
export const secrets: { get(name: string): string | undefined } = {
|
|
46
|
+
get(name: string): string | undefined {
|
|
47
|
+
return bridge().secrets.get(name);
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Continue work after the response has been sent.
|
|
53
|
+
*
|
|
54
|
+
* Deployed, this rides `ctx.waitUntil` to extend the invocation until the
|
|
55
|
+
* promise settles. Locally the function is a long-lived server process, so the
|
|
56
|
+
* host has nothing to hold open and only reports rejections. Returns the same
|
|
57
|
+
* promise either way, so it composes.
|
|
58
|
+
*/
|
|
59
|
+
export function waitUntil<T>(promise: Promise<T>): Promise<T> {
|
|
60
|
+
bridge().waitUntil(promise);
|
|
61
|
+
return promise;
|
|
62
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deno Function Wrapper
|
|
3
|
+
*
|
|
4
|
+
* This script is executed by Deno to run user functions.
|
|
5
|
+
* It patches Deno.serve to inject a dynamic port before importing the user's function.
|
|
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
|
+
*
|
|
14
|
+
* Environment variables:
|
|
15
|
+
* - FUNCTION_PATH: Absolute path to the user's function entry file
|
|
16
|
+
* - FUNCTION_PORT: Port number for the function to listen on
|
|
17
|
+
* - FUNCTION_NAME: Name of the function (for logging)
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
// Make this file a module for top-level await support
|
|
21
|
+
export {};
|
|
22
|
+
|
|
23
|
+
import type { Base44Bridge } from "./base44-runtime.ts";
|
|
24
|
+
|
|
25
|
+
const functionPath = Deno.env.get("FUNCTION_PATH");
|
|
26
|
+
const port = parseInt(Deno.env.get("FUNCTION_PORT") || "8000", 10);
|
|
27
|
+
const functionName = Deno.env.get("FUNCTION_NAME") || "unknown";
|
|
28
|
+
|
|
29
|
+
if (!functionPath) {
|
|
30
|
+
console.error("[wrapper] FUNCTION_PATH environment variable is required");
|
|
31
|
+
Deno.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Store the original Deno.serve
|
|
35
|
+
const originalServe = Deno.serve.bind(Deno);
|
|
36
|
+
|
|
37
|
+
// Set when the user's module calls Deno.serve while it is being imported.
|
|
38
|
+
// Such a module serves itself, so its default export (if any) is ignored.
|
|
39
|
+
let servedDuringImport = false;
|
|
40
|
+
|
|
41
|
+
// Patch Deno.serve to inject our port and add onListen callback.
|
|
42
|
+
const patchedServe = (
|
|
43
|
+
optionsOrHandler:
|
|
44
|
+
| Deno.ServeOptions
|
|
45
|
+
| Deno.ServeHandler
|
|
46
|
+
| (Deno.ServeOptions & { handler: Deno.ServeHandler }),
|
|
47
|
+
maybeHandler?: Deno.ServeHandler,
|
|
48
|
+
): Deno.HttpServer<Deno.NetAddr> => {
|
|
49
|
+
servedDuringImport = true;
|
|
50
|
+
|
|
51
|
+
const onListen = () => {
|
|
52
|
+
// This message is used by FunctionManager to detect when the function is ready
|
|
53
|
+
console.log(`[${functionName}] Listening on http://localhost:${port}`);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// Handle the different Deno.serve signatures:
|
|
57
|
+
// 1. Deno.serve(handler)
|
|
58
|
+
// 2. Deno.serve(options, handler)
|
|
59
|
+
// 3. Deno.serve({ ...options, handler })
|
|
60
|
+
if (typeof optionsOrHandler === "function") {
|
|
61
|
+
// Signature: Deno.serve(handler)
|
|
62
|
+
return originalServe({ port, onListen }, optionsOrHandler);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (maybeHandler) {
|
|
66
|
+
// Signature: Deno.serve(options, handler)
|
|
67
|
+
return originalServe({ ...optionsOrHandler, port, onListen }, maybeHandler);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Signature: Deno.serve({ ...options, handler })
|
|
71
|
+
const options = optionsOrHandler as Deno.ServeOptions & {
|
|
72
|
+
handler: Deno.ServeHandler;
|
|
73
|
+
};
|
|
74
|
+
return originalServe({ ...options, port, onListen });
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// Deno 2.8 exposes `Deno.serve` as a getter-only property, so a plain
|
|
78
|
+
// `Deno.serve = ...` assignment throws. Use defineProperty to override it
|
|
79
|
+
// (works on both the old writable property and the new accessor).
|
|
80
|
+
Object.defineProperty(Deno, "serve", {
|
|
81
|
+
value: patchedServe,
|
|
82
|
+
writable: true,
|
|
83
|
+
configurable: true,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Local stand-in for the bridge the deployed Worker entry installs. It must be
|
|
87
|
+
// in place before the function is imported, because module-scope code may read
|
|
88
|
+
// a secret. Deployed, secrets come from the request's Worker env binding and
|
|
89
|
+
// `waitUntil` rides `ctx.waitUntil`; locally secrets come from this process's
|
|
90
|
+
// environment and the server is long-lived, so there is nothing to hold open —
|
|
91
|
+
// in-flight work is only tracked so a rejection is reported against the
|
|
92
|
+
// function instead of surfacing as an unhandled rejection.
|
|
93
|
+
const inFlight = new Set<Promise<unknown>>();
|
|
94
|
+
|
|
95
|
+
const base44Bridge: Base44Bridge = {
|
|
96
|
+
secrets: {
|
|
97
|
+
get: (name: string) => Deno.env.get(name),
|
|
98
|
+
},
|
|
99
|
+
waitUntil: (promise: Promise<unknown>) => {
|
|
100
|
+
const tracked = Promise.resolve(promise)
|
|
101
|
+
.catch((error: unknown) => {
|
|
102
|
+
console.error(`[${functionName}] waitUntil task failed:`, error);
|
|
103
|
+
})
|
|
104
|
+
.finally(() => {
|
|
105
|
+
inFlight.delete(tracked);
|
|
106
|
+
});
|
|
107
|
+
inFlight.add(tracked);
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
Object.defineProperty(globalThis, "Base44", {
|
|
112
|
+
value: base44Bridge,
|
|
113
|
+
writable: false,
|
|
114
|
+
configurable: true,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
type FetchHandler = (req: Request) => Response | Promise<Response>;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Pull the request handler off the user's module namespace, accepting both
|
|
121
|
+
* `export default async function (req)` and the `export default { fetch }`
|
|
122
|
+
* object form.
|
|
123
|
+
*/
|
|
124
|
+
const resolveDefaultHandler = (
|
|
125
|
+
module: Record<string, unknown>,
|
|
126
|
+
): FetchHandler | null => {
|
|
127
|
+
const exported = module.default;
|
|
128
|
+
|
|
129
|
+
if (typeof exported === "function") {
|
|
130
|
+
return exported as FetchHandler;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (exported && typeof exported === "object") {
|
|
134
|
+
const { fetch } = exported as { fetch?: unknown };
|
|
135
|
+
if (typeof fetch === "function") {
|
|
136
|
+
return (fetch as FetchHandler).bind(exported);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return null;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
console.log(`[${functionName}] Starting function from ${functionPath}`);
|
|
144
|
+
|
|
145
|
+
// Dynamically import the user's function. A legacy function calls Deno.serve
|
|
146
|
+
// during import, which is now patched to use our port.
|
|
147
|
+
let functionModule: Record<string, unknown>;
|
|
148
|
+
try {
|
|
149
|
+
functionModule = await import(functionPath);
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.error(`[${functionName}] Failed to load function:`, error);
|
|
152
|
+
Deno.exit(1);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Nothing served itself during import, so the module is expected to export a
|
|
156
|
+
// handler. Serving it here goes through the same patched Deno.serve, so the
|
|
157
|
+
// readiness line still gets printed exactly once.
|
|
158
|
+
if (!servedDuringImport) {
|
|
159
|
+
const handler = resolveDefaultHandler(functionModule);
|
|
160
|
+
|
|
161
|
+
if (!handler) {
|
|
162
|
+
console.error(
|
|
163
|
+
`[${functionName}] The function must export default a request handler or call Deno.serve()`,
|
|
164
|
+
);
|
|
165
|
+
Deno.exit(1);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
Deno.serve(handler);
|
|
169
|
+
}
|