@arcote.tech/arc-cli 0.3.0 → 0.4.1

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.
@@ -0,0 +1,353 @@
1
+ import {
2
+ cleanupClientSubs,
3
+ cleanupStreams,
4
+ createArcServer,
5
+ ContextHandler,
6
+ ConnectionManager,
7
+ type ArcHttpHandler,
8
+ type ArcRequestContext,
9
+ type ArcServer,
10
+ } from "@arcote.tech/arc-host";
11
+ import { existsSync, mkdirSync } from "fs";
12
+ import { join } from "path";
13
+ import { readTranslationsConfig } from "../i18n";
14
+ import type { BuildManifest, WorkspaceInfo } from "./shared";
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // Types
18
+ // ---------------------------------------------------------------------------
19
+
20
+ export interface PlatformServerOptions {
21
+ ws: WorkspaceInfo;
22
+ port: number;
23
+ manifest: BuildManifest;
24
+ /** Server context (from loadServerContext). If null, static-only mode. */
25
+ context?: any;
26
+ /** Path to SQLite database file */
27
+ dbPath?: string;
28
+ /** If true, enables SSE reload stream + mutable manifest (dev mode) */
29
+ devMode?: boolean;
30
+ }
31
+
32
+ export interface PlatformServer {
33
+ server: ReturnType<typeof Bun.serve>;
34
+ contextHandler: ContextHandler | null;
35
+ connectionManager: ConnectionManager | null;
36
+ /** Update manifest at runtime (dev mode rebuild) */
37
+ setManifest: (m: BuildManifest) => void;
38
+ /** Notify SSE clients of rebuild (dev mode only) */
39
+ notifyReload: (m: BuildManifest) => void;
40
+ /** Cleanup streams and stop server */
41
+ stop: () => void;
42
+ }
43
+
44
+ // ---------------------------------------------------------------------------
45
+ // ContextHandler initialization
46
+ // ---------------------------------------------------------------------------
47
+
48
+ export async function initContextHandler(
49
+ context: any,
50
+ dbPath: string,
51
+ ): Promise<ContextHandler> {
52
+ const { createBunSQLiteAdapterFactory } = await import(
53
+ "@arcote.tech/arc-adapter-db-sqlite"
54
+ );
55
+
56
+ const dbDir = dbPath.substring(0, dbPath.lastIndexOf("/"));
57
+ if (dbDir) mkdirSync(dbDir, { recursive: true });
58
+
59
+ const dbAdapter = createBunSQLiteAdapterFactory(dbPath)(context);
60
+ const handler = new ContextHandler(context, dbAdapter);
61
+ await handler.init();
62
+ return handler;
63
+ }
64
+
65
+ // ---------------------------------------------------------------------------
66
+ // Shell HTML
67
+ // ---------------------------------------------------------------------------
68
+
69
+ export function generateShellHtml(appName: string, manifest?: { title: string; favicon?: string }): string {
70
+ const importMap = {
71
+ imports: {
72
+ react: "/shell/react.js",
73
+ "react/jsx-runtime": "/shell/jsx-runtime.js",
74
+ "react/jsx-dev-runtime": "/shell/jsx-dev-runtime.js",
75
+ "react-dom": "/shell/react-dom.js",
76
+ "react-dom/client": "/shell/react-dom-client.js",
77
+ "@arcote.tech/arc": "/shell/arc.js",
78
+ "@arcote.tech/arc-ds": "/shell/arc-ds.js",
79
+ "@arcote.tech/arc-react": "/shell/arc-react.js",
80
+ "@arcote.tech/platform": "/shell/platform.js",
81
+ },
82
+ };
83
+
84
+ return `<!doctype html>
85
+ <html lang="en">
86
+ <head>
87
+ <meta charset="UTF-8" />
88
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
89
+ <title>${manifest?.title ?? appName}</title>${manifest?.favicon ? `\n <link rel="icon" href="${manifest.favicon}">` : ""}${manifest ? `\n <link rel="manifest" href="/manifest.json">` : ""}
90
+ <link rel="stylesheet" href="/styles.css" />
91
+ <link rel="stylesheet" href="/theme.css" />
92
+ <script type="importmap">${JSON.stringify(importMap)}</script>
93
+ </head>
94
+ <body>
95
+ <div id="root"></div>
96
+ <script type="module">
97
+ import { createRoot } from "react-dom/client";
98
+ import { createElement } from "react";
99
+ import { PlatformApp } from "@arcote.tech/platform";
100
+
101
+ createRoot(document.getElementById("root")).render(
102
+ createElement(PlatformApp)
103
+ );
104
+ </script>
105
+ </body>
106
+ </html>`;
107
+ }
108
+
109
+ // ---------------------------------------------------------------------------
110
+ // Static file serving
111
+ // ---------------------------------------------------------------------------
112
+
113
+ const MIME: Record<string, string> = {
114
+ ".html": "text/html",
115
+ ".js": "application/javascript",
116
+ ".mjs": "application/javascript",
117
+ ".css": "text/css",
118
+ ".json": "application/json",
119
+ ".svg": "image/svg+xml",
120
+ ".png": "image/png",
121
+ ".jpg": "image/jpeg",
122
+ ".ico": "image/x-icon",
123
+ ".woff2": "font/woff2",
124
+ ".woff": "font/woff",
125
+ ".ttf": "font/ttf",
126
+ };
127
+
128
+ function getMime(path: string): string {
129
+ const ext = path.substring(path.lastIndexOf("."));
130
+ return MIME[ext] ?? "application/octet-stream";
131
+ }
132
+
133
+ function serveFile(
134
+ filePath: string,
135
+ headers: Record<string, string> = {},
136
+ ): Response {
137
+ if (!existsSync(filePath))
138
+ return new Response("Not Found", { status: 404 });
139
+ return new Response(Bun.file(filePath), {
140
+ headers: { "Content-Type": getMime(filePath), ...headers },
141
+ });
142
+ }
143
+
144
+ // ---------------------------------------------------------------------------
145
+ // Platform-specific HTTP handlers
146
+ // ---------------------------------------------------------------------------
147
+
148
+ function staticFilesHandler(ws: WorkspaceInfo, devMode: boolean): ArcHttpHandler {
149
+ return (_req, url, ctx) => {
150
+ const path = url.pathname;
151
+ if (path.startsWith("/shell/"))
152
+ return serveFile(join(ws.shellDir, path.slice(7)), ctx.corsHeaders);
153
+ if (path.startsWith("/modules/"))
154
+ return serveFile(join(ws.modulesDir, path.slice(9)), {
155
+ ...ctx.corsHeaders,
156
+ "Cache-Control": devMode ? "no-cache" : "max-age=31536000,immutable",
157
+ });
158
+ if (path.startsWith("/locales/"))
159
+ return serveFile(join(ws.arcDir, path.slice(1)), ctx.corsHeaders);
160
+ if (path === "/styles.css")
161
+ return serveFile(join(ws.arcDir, "styles.css"), ctx.corsHeaders);
162
+ if (path === "/theme.css")
163
+ return serveFile(join(ws.arcDir, "theme.css"), ctx.corsHeaders);
164
+
165
+ // Serve manifest.json from root dir
166
+ if ((path === "/manifest.json" || path === "/manifest.webmanifest") && ws.manifest) {
167
+ return serveFile(ws.manifest.path, ctx.corsHeaders);
168
+ }
169
+
170
+ // Public files (files with extensions)
171
+ if (path.lastIndexOf(".") > path.lastIndexOf("/")) {
172
+ const publicFile = join(ws.publicDir, path.slice(1));
173
+ if (existsSync(publicFile))
174
+ return serveFile(publicFile, ctx.corsHeaders);
175
+ }
176
+
177
+ return null;
178
+ };
179
+ }
180
+
181
+ function apiEndpointsHandler(
182
+ ws: WorkspaceInfo,
183
+ getManifest: () => BuildManifest,
184
+ cm: ConnectionManager | null,
185
+ ): ArcHttpHandler {
186
+ return (_req, url, ctx) => {
187
+ if (url.pathname === "/api/modules")
188
+ return Response.json(getManifest(), { headers: ctx.corsHeaders });
189
+
190
+ if (url.pathname === "/api/translations") {
191
+ const config = readTranslationsConfig(ws.rootDir);
192
+ return Response.json(config ?? { locales: [], sourceLocale: "" }, {
193
+ headers: ctx.corsHeaders,
194
+ });
195
+ }
196
+
197
+ if (url.pathname === "/health") {
198
+ return Response.json(
199
+ {
200
+ status: "ok",
201
+ modules: getManifest().modules.length,
202
+ clients: cm?.clientCount ?? 0,
203
+ },
204
+ { headers: ctx.corsHeaders },
205
+ );
206
+ }
207
+
208
+ return null;
209
+ };
210
+ }
211
+
212
+ function devReloadHandler(
213
+ sseClients: Set<ReadableStreamDefaultController>,
214
+ ): ArcHttpHandler {
215
+ return (_req, url, ctx) => {
216
+ if (url.pathname !== "/api/reload-stream") return null;
217
+
218
+ const stream = new ReadableStream({
219
+ start(controller) {
220
+ sseClients.add(controller);
221
+ controller.enqueue("data: connected\n\n");
222
+ },
223
+ cancel(controller) {
224
+ sseClients.delete(controller);
225
+ },
226
+ });
227
+
228
+ return new Response(stream, {
229
+ headers: {
230
+ ...ctx.corsHeaders,
231
+ "Content-Type": "text/event-stream",
232
+ "Cache-Control": "no-cache",
233
+ Connection: "keep-alive",
234
+ },
235
+ });
236
+ };
237
+ }
238
+
239
+ function spaFallbackHandler(shellHtml: string): ArcHttpHandler {
240
+ return (_req, _url, ctx) => {
241
+ return new Response(shellHtml, {
242
+ headers: { ...ctx.corsHeaders, "Content-Type": "text/html" },
243
+ });
244
+ };
245
+ }
246
+
247
+ // ---------------------------------------------------------------------------
248
+ // Start platform server — uses createArcServer + platform handlers
249
+ // ---------------------------------------------------------------------------
250
+
251
+ export async function startPlatformServer(
252
+ opts: PlatformServerOptions,
253
+ ): Promise<PlatformServer> {
254
+ const { ws, port, devMode, context } = opts;
255
+ let manifest = opts.manifest;
256
+ const getManifest = () => manifest;
257
+
258
+ const shellHtml = generateShellHtml(ws.appName, ws.manifest);
259
+ const sseClients = new Set<ReadableStreamDefaultController>();
260
+
261
+ if (!context) {
262
+ // No context — serve static files only (no WS/commands/queries)
263
+ const cors = {
264
+ "Access-Control-Allow-Origin": "*",
265
+ "Access-Control-Allow-Methods":
266
+ "GET, POST, PUT, DELETE, PATCH, OPTIONS",
267
+ "Access-Control-Allow-Headers": "Content-Type, Authorization",
268
+ };
269
+
270
+ const server = Bun.serve({
271
+ port,
272
+ fetch(req) {
273
+ const url = new URL(req.url);
274
+ if (req.method === "OPTIONS")
275
+ return new Response(null, { headers: cors });
276
+
277
+ const ctx: ArcRequestContext = {
278
+ rawToken: null,
279
+ tokenPayload: null,
280
+ corsHeaders: cors,
281
+ };
282
+
283
+ // Platform handlers only
284
+ const handlers: ArcHttpHandler[] = [
285
+ apiEndpointsHandler(ws, getManifest, null),
286
+ ...(devMode ? [devReloadHandler(sseClients)] : []),
287
+ staticFilesHandler(ws, !!devMode),
288
+ spaFallbackHandler(shellHtml),
289
+ ];
290
+
291
+ for (const handler of handlers) {
292
+ const response = handler(req, url, ctx);
293
+ if (response instanceof Promise) return response;
294
+ if (response) return response;
295
+ }
296
+
297
+ return new Response("Not Found", { status: 404, headers: cors });
298
+ },
299
+ });
300
+
301
+ return {
302
+ server,
303
+ contextHandler: null,
304
+ connectionManager: null,
305
+ setManifest: (m) => { manifest = m; },
306
+ notifyReload: (m) => {
307
+ const data = JSON.stringify(m);
308
+ for (const c of sseClients) {
309
+ try { c.enqueue(`data: ${data}\n\n`); }
310
+ catch { sseClients.delete(c); }
311
+ }
312
+ },
313
+ stop: () => server.stop(),
314
+ };
315
+ }
316
+
317
+ // Context available — use createArcServer with platform handlers on top
318
+ const { createBunSQLiteAdapterFactory } = await import(
319
+ "@arcote.tech/arc-adapter-db-sqlite"
320
+ );
321
+ const dbPath = opts.dbPath || join(ws.arcDir, "data", "arc.db");
322
+ const dbDir = dbPath.substring(0, dbPath.lastIndexOf("/"));
323
+ if (dbDir) mkdirSync(dbDir, { recursive: true });
324
+
325
+ const arcServer = await createArcServer({
326
+ context,
327
+ dbAdapterFactory: createBunSQLiteAdapterFactory(dbPath),
328
+ port,
329
+ httpHandlers: [
330
+ // Platform-specific handlers (checked AFTER arc handlers)
331
+ apiEndpointsHandler(ws, getManifest, null),
332
+ ...(devMode ? [devReloadHandler(sseClients)] : []),
333
+ staticFilesHandler(ws, !!devMode),
334
+ spaFallbackHandler(shellHtml),
335
+ ],
336
+ onWsClose: (clientId) => cleanupClientSubs(clientId),
337
+ });
338
+
339
+ return {
340
+ server: arcServer.server,
341
+ contextHandler: arcServer.contextHandler,
342
+ connectionManager: arcServer.connectionManager,
343
+ setManifest: (m) => { manifest = m; },
344
+ notifyReload: (m) => {
345
+ const data = JSON.stringify(m);
346
+ for (const c of sseClients) {
347
+ try { c.enqueue(`data: ${data}\n\n`); }
348
+ catch { sseClients.delete(c); }
349
+ }
350
+ },
351
+ stop: () => arcServer.stop(),
352
+ };
353
+ }
@@ -0,0 +1,280 @@
1
+ import { findUpSync } from "find-up";
2
+ import { copyFileSync, existsSync, mkdirSync, readFileSync } from "fs";
3
+ import { dirname, join } from "path";
4
+ import {
5
+ buildPackages,
6
+ buildStyles,
7
+ discoverPackages,
8
+ isContextPackage,
9
+ type BuildManifest,
10
+ type WorkspacePackage,
11
+ } from "../builder/module-builder";
12
+
13
+ // Re-export for convenience
14
+ export { buildPackages, buildStyles, isContextPackage };
15
+ export type { BuildManifest, WorkspacePackage };
16
+
17
+ // ---------------------------------------------------------------------------
18
+ // Logging
19
+ // ---------------------------------------------------------------------------
20
+
21
+ const C = {
22
+ reset: "\x1b[0m",
23
+ green: "\x1b[32m",
24
+ red: "\x1b[31m",
25
+ cyan: "\x1b[36m",
26
+ };
27
+
28
+ export const log = (msg: string) =>
29
+ console.log(`${C.cyan}[arc]${C.reset} ${msg}`);
30
+ export const ok = (msg: string) => console.log(`${C.green} ✓${C.reset} ${msg}`);
31
+ export const err = (msg: string) =>
32
+ console.error(`${C.red} ✗${C.reset} ${msg}`);
33
+
34
+ // ---------------------------------------------------------------------------
35
+ // Workspace resolution
36
+ // ---------------------------------------------------------------------------
37
+
38
+ export interface ManifestInfo {
39
+ path: string;
40
+ title: string;
41
+ favicon?: string;
42
+ }
43
+
44
+ export interface WorkspaceInfo {
45
+ rootDir: string;
46
+ rootPkg: Record<string, any>;
47
+ appName: string;
48
+ arcDir: string;
49
+ modulesDir: string;
50
+ shellDir: string;
51
+ publicDir: string;
52
+ packages: WorkspacePackage[];
53
+ manifest?: ManifestInfo;
54
+ }
55
+
56
+ export function resolveWorkspace(): WorkspaceInfo {
57
+ const packageJsonPath = findUpSync("package.json", { cwd: process.cwd() });
58
+ if (!packageJsonPath) {
59
+ err("No package.json found");
60
+ process.exit(1);
61
+ }
62
+
63
+ const rootDir = dirname(packageJsonPath);
64
+ const rootPkg = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
65
+ const appName = rootPkg.name ?? "Arc App";
66
+ const arcDir = join(rootDir, ".arc", "platform");
67
+
68
+ log("Scanning workspaces...");
69
+ const packages = discoverPackages(rootDir);
70
+ ok(
71
+ `Found ${packages.length} package(s): ${packages.map((p) => p.name).join(", ")}`,
72
+ );
73
+
74
+ if (packages.length === 0) {
75
+ err("No workspace packages found.");
76
+ process.exit(1);
77
+ }
78
+
79
+ // Detect manifest.json or manifest.webmanifest in root dir
80
+ let manifest: ManifestInfo | undefined;
81
+ for (const name of ["manifest.json", "manifest.webmanifest"]) {
82
+ const manifestPath = join(rootDir, name);
83
+ if (existsSync(manifestPath)) {
84
+ try {
85
+ const data = JSON.parse(readFileSync(manifestPath, "utf-8"));
86
+ const icons = data.icons as { src: string; sizes?: string }[] | undefined;
87
+ manifest = {
88
+ path: manifestPath,
89
+ title: data.name ?? appName,
90
+ favicon: icons?.[0]?.src,
91
+ };
92
+ ok(`Found ${name}: "${manifest.title}"`);
93
+ } catch {
94
+ err(`Failed to parse ${name}`);
95
+ }
96
+ break;
97
+ }
98
+ }
99
+
100
+ return {
101
+ rootDir,
102
+ rootPkg,
103
+ appName,
104
+ arcDir,
105
+ modulesDir: join(arcDir, "modules"),
106
+ shellDir: join(arcDir, "shell"),
107
+ publicDir: join(rootDir, "public"),
108
+ packages,
109
+ manifest,
110
+ };
111
+ }
112
+
113
+ // ---------------------------------------------------------------------------
114
+ // Build pipeline — packages, CSS, theme, shell
115
+ // ---------------------------------------------------------------------------
116
+
117
+ export async function buildAll(ws: WorkspaceInfo): Promise<BuildManifest> {
118
+ log("Building packages...");
119
+ const manifest = await buildPackages(ws.rootDir, ws.modulesDir, ws.packages);
120
+ ok(`Built ${manifest.modules.length} module(s)`);
121
+
122
+ log("Building styles...");
123
+ await buildStyles(ws.rootDir, ws.arcDir);
124
+ ok("Styles built");
125
+
126
+ // Copy theme if configured
127
+ const themePath = ws.rootPkg.arc?.theme;
128
+ if (themePath) {
129
+ const src = join(ws.rootDir, themePath);
130
+ if (existsSync(src)) {
131
+ copyFileSync(src, join(ws.arcDir, "theme.css"));
132
+ ok("Theme copied");
133
+ }
134
+ }
135
+
136
+ log("Building shell...");
137
+ await buildShell(ws.shellDir);
138
+ ok("Shell built");
139
+
140
+ return manifest;
141
+ }
142
+
143
+ // ---------------------------------------------------------------------------
144
+ // Shell builder — framework packages for import map
145
+ // ---------------------------------------------------------------------------
146
+
147
+ async function buildShell(outDir: string): Promise<void> {
148
+ mkdirSync(outDir, { recursive: true });
149
+ const tmpDir = join(outDir, "_tmp");
150
+ mkdirSync(tmpDir, { recursive: true });
151
+
152
+ // Step 1: Build React layer (bundles React CJS → ESM)
153
+ const reactEntries: [string, string][] = [
154
+ [
155
+ "react",
156
+ `import React from "react";
157
+ export default React;
158
+ export const {
159
+ Children, Component, Fragment, Profiler, PureComponent, StrictMode, Suspense,
160
+ cloneElement, createContext, createElement, createRef, forwardRef, isValidElement,
161
+ lazy, memo, startTransition, use, useCallback, useContext, useDebugValue,
162
+ useDeferredValue, useEffect, useId, useImperativeHandle, useInsertionEffect,
163
+ useLayoutEffect, useMemo, useReducer, useRef, useState, useSyncExternalStore,
164
+ useTransition, version, useActionState, useOptimistic,
165
+ } = React;`,
166
+ ],
167
+ ["jsx-runtime", `export { jsx, jsxs, Fragment } from "react/jsx-runtime";`],
168
+ [
169
+ "jsx-dev-runtime",
170
+ `export { jsxDEV, Fragment } from "react/jsx-dev-runtime";`,
171
+ ],
172
+ [
173
+ "react-dom",
174
+ `import ReactDOM from "react-dom";
175
+ export default ReactDOM;
176
+ export const { createPortal, flushSync } = ReactDOM;`,
177
+ ],
178
+ [
179
+ "react-dom-client",
180
+ `export { createRoot, hydrateRoot } from "react-dom/client";`,
181
+ ],
182
+ ];
183
+
184
+ const reactEps: string[] = [];
185
+ for (const [name, code] of reactEntries) {
186
+ const f = join(tmpDir, `${name}.ts`);
187
+ Bun.write(f, code);
188
+ reactEps.push(f);
189
+ }
190
+
191
+ const r1 = await Bun.build({
192
+ entrypoints: reactEps,
193
+ outdir: outDir,
194
+ splitting: true,
195
+ format: "esm",
196
+ target: "browser",
197
+ naming: "[name].[ext]",
198
+ });
199
+ if (!r1.success) {
200
+ for (const l of r1.logs) console.error(l);
201
+ throw new Error("Shell React build failed");
202
+ }
203
+
204
+ // Step 2: Build Arc layer (react is EXTERNAL — resolved via import map)
205
+ const arcEntries: [string, string][] = [
206
+ ["arc", "@arcote.tech/arc"],
207
+ ["arc-ds", "@arcote.tech/arc-ds"],
208
+ ["arc-react", "@arcote.tech/arc-react"],
209
+ ["platform", "@arcote.tech/platform"],
210
+ ];
211
+
212
+ const arcEps: string[] = [];
213
+ for (const [name, pkg] of arcEntries) {
214
+ const f = join(tmpDir, `${name}.ts`);
215
+ Bun.write(f, `export * from "${pkg}";\n`);
216
+ arcEps.push(f);
217
+ }
218
+
219
+ const r2 = await Bun.build({
220
+ entrypoints: arcEps,
221
+ outdir: outDir,
222
+ splitting: true,
223
+ format: "esm",
224
+ target: "browser",
225
+ naming: "[name].[ext]",
226
+ external: [
227
+ "react",
228
+ "react-dom",
229
+ "react/jsx-runtime",
230
+ "react/jsx-dev-runtime",
231
+ "react-dom/client",
232
+ ],
233
+ });
234
+ if (!r2.success) {
235
+ for (const l of r2.logs) console.error(l);
236
+ throw new Error("Shell Arc build failed");
237
+ }
238
+
239
+ // Clean tmp
240
+ const { rmSync } = await import("fs");
241
+ rmSync(tmpDir, { recursive: true, force: true });
242
+ }
243
+
244
+ // ---------------------------------------------------------------------------
245
+ // Server context loading
246
+ // ---------------------------------------------------------------------------
247
+
248
+ export async function loadServerContext(
249
+ packages: WorkspacePackage[],
250
+ ): Promise<any | null> {
251
+ const ctxPackages = packages.filter((p) => isContextPackage(p.packageJson));
252
+ if (ctxPackages.length === 0) return null;
253
+
254
+ // Set globals for server context — framework packages (arc-auth etc.)
255
+ // use these at runtime to tree-shake browser/server code paths.
256
+ (globalThis as any).ONLY_SERVER = true;
257
+ (globalThis as any).ONLY_BROWSER = false;
258
+ (globalThis as any).ONLY_CLIENT = false;
259
+
260
+ // Import all context packages — side effects from module().build()
261
+ // register context elements into the global registry via setContext().
262
+ for (const ctx of ctxPackages) {
263
+ const serverDist = join(ctx.path, "dist", "server", "main", "index.js");
264
+ if (!existsSync(serverDist)) {
265
+ err(`Context server dist not found: ${serverDist}`);
266
+ continue;
267
+ }
268
+
269
+ try {
270
+ await import(serverDist);
271
+ } catch (e) {
272
+ err(`Failed to load server context from ${ctx.name}: ${e}`);
273
+ }
274
+ }
275
+
276
+ // After all imports, module().build() side effects have called setContext()
277
+ // in the arc-ui registry. Retrieve the merged context from there.
278
+ const { getContext } = await import("@arcote.tech/platform");
279
+ return getContext() ?? null;
280
+ }