@arcote.tech/arc-cli 0.6.1 → 0.7.0

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.
@@ -3,7 +3,7 @@ import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync,
3
3
  import { dirname, join } from "path";
4
4
  import {
5
5
  buildContextPackages,
6
- buildModulesBundle,
6
+ buildModulesByChunks,
7
7
  buildStyles,
8
8
  buildTranslations,
9
9
  discoverPackages,
@@ -19,6 +19,9 @@ import {
19
19
  updateCache,
20
20
  type BuildCache,
21
21
  } from "../builder/build-cache";
22
+ import { extractAccessMap } from "../builder/access-extractor";
23
+ import { planChunks } from "../builder/chunk-planner";
24
+ import { collectFrameworkDeps } from "../builder/dependency-collector";
22
25
  import {
23
26
  mtimeOf,
24
27
  readInstalledVersion,
@@ -28,14 +31,9 @@ import {
28
31
  sha256OfJson,
29
32
  } from "../builder/hash";
30
33
  import { pAll } from "../builder/parallel";
31
- import {
32
- collectFrameworkDeps,
33
- collectModuleDeps,
34
- } from "../builder/dependency-collector";
35
- import { extractAccessMap } from "../builder/access-extractor";
36
34
 
37
35
  // Re-export for convenience
38
- export { buildContextPackages, buildModulesBundle, buildStyles, isContextPackage };
36
+ export { buildContextPackages, buildModulesByChunks, buildStyles, isContextPackage };
39
37
  export type { BuildManifest, ModuleDescriptor, WorkspacePackage };
40
38
 
41
39
  // ---------------------------------------------------------------------------
@@ -93,16 +91,15 @@ export function resolveWorkspace(): WorkspaceInfo {
93
91
 
94
92
  log("Scanning workspaces...");
95
93
  const packages = discoverPackages(rootDir);
96
- ok(
97
- `Found ${packages.length} package(s): ${packages.map((p) => p.name).join(", ")}`,
98
- );
99
-
100
- // Empty package list is allowed in pre-deploy runtime mode (container with
101
- // freshly-mounted volume, no user code pushed yet). The CLI entry that
102
- // forbids this (arc platform build/dev) checks explicitly.
103
- if (packages.length === 0 && process.env.ARC_DEPLOY_API !== "1") {
104
- err("No workspace packages found.");
105
- process.exit(1);
94
+ if (packages.length > 0) {
95
+ ok(
96
+ `Found ${packages.length} package(s): ${packages.map((p) => p.name).join(", ")}`,
97
+ );
98
+ } else {
99
+ // Zero packages is valid for `platform start` in image runtime mode —
100
+ // the workspace tree isn't shipped, only `.arc/platform/` is. Build
101
+ // commands enforce non-empty packages where they need them.
102
+ log("No workspace packages found — assuming image runtime mode.");
106
103
  }
107
104
 
108
105
  // Detect manifest.json or manifest.webmanifest in root dir
@@ -163,36 +160,55 @@ export async function buildAll(
163
160
 
164
161
  log(`Building (concurrency parallel${noCache ? ", no-cache" : ""})...`);
165
162
 
166
- // Promise.all over independent units. buildModulesBundle is the only one
167
- // that returns data we need (the manifest). The rest update the cache.
168
- const [, modulesResult] = await Promise.all([
169
- buildContextPackages(ws.rootDir, ws.packages, cache, noCache),
170
- buildModulesBundle(ws.rootDir, ws.modulesDir, ws.packages, cache, noCache),
163
+ // Phase 1 context packages must finish FIRST. The access-extractor
164
+ // subprocess imports workspace packages by name, which resolve through
165
+ // node_modules to packages' `main` field (typically `dist/server/main/`).
166
+ await buildContextPackages(ws.rootDir, ws.packages, cache, noCache);
167
+
168
+ // Phase 1b — relocate per-package server bundles into `.arc/platform/server/`
169
+ // so the deploy image can be self-contained (image COPY needs everything
170
+ // server-side under one root). loadServerContext reads from here in prod.
171
+ copyContextServerBundles(ws);
172
+
173
+ // Phase 2 — extract access metadata (token name + hasCheck per module) in
174
+ // an isolated subprocess. This MUST run before chunk planning so we know
175
+ // which token group each module belongs to.
176
+ const accessMap = await extractAccessMap(ws.rootDir, ws.packages);
177
+
178
+ // Persist access map for the runtime host (server.ts reads at startup to
179
+ // wire up moduleAccessMap for filterManifestForTokens / signed URLs).
180
+ mkdirSync(ws.arcDir, { recursive: true });
181
+ writeFileSync(
182
+ join(ws.arcDir, "access.json"),
183
+ JSON.stringify(accessMap, null, 2) + "\n",
184
+ );
185
+
186
+ // Phase 3 — group modules into chunks (one Bun.build per group).
187
+ const plan = planChunks(ws.packages, accessMap);
188
+ ok(
189
+ `Chunks: ${plan.chunks
190
+ .map((c) => `${c}(${plan.groups.get(c)?.length ?? 0})`)
191
+ .join(", ")}`,
192
+ );
193
+
194
+ // Phase 4 — independent parallel build units. Module chunks are the only
195
+ // unit whose output we need for the manifest; others update the cache.
196
+ const [modulesResult] = await Promise.all([
197
+ buildModulesByChunks(ws.rootDir, ws.modulesDir, plan, cache, noCache),
171
198
  buildShell(ws, cache, noCache),
172
199
  buildStyles(ws.rootDir, ws.arcDir, ws.packages, themePath, cache, noCache),
173
200
  copyBrowserAssets(ws, cache, noCache),
174
201
  buildTranslations(ws.rootDir, ws.arcDir, cache, noCache),
175
202
  ]);
176
203
 
177
- saveBuildCache(ws.arcDir, cache);
178
-
179
- // v0.6 deploy artifacts: framework + per-module dependency manifests + access
180
- // map. Generated unconditionally — runtime container needs them to bun-install
181
- // and to enforce protectBy rules. Order matters: deps before access (access
182
- // extraction subprocess imports server bundles whose own deps may matter).
204
+ // Phase 5 — framework peer manifest at `<arcDir>/package.json`. Used by
205
+ // the deploy image build (single `bun install` for all peers, one copy
206
+ // shared across module bundles).
183
207
  collectFrameworkDeps(ws.arcDir, ws.rootDir, ws.packages);
184
- for (const pkg of ws.packages) {
185
- collectModuleDeps(ws.arcDir, pkg);
186
- }
187
- try {
188
- await extractAccessMap(ws.arcDir, ws.packages);
189
- } catch (e) {
190
- err(`access-extractor failed: ${(e as Error).message}`);
191
- // Don't fail the build — protection rules will be empty server-side but
192
- // the rest of the deploy can still proceed.
193
- }
194
208
 
195
- const finalManifest = assembleManifest(ws, modulesResult.modules, cache);
209
+ saveBuildCache(ws.arcDir, cache);
210
+
211
+ const finalManifest = assembleManifest(ws, modulesResult.modules, plan.chunks, cache);
196
212
  writeFileSync(
197
213
  join(ws.modulesDir, "manifest.json"),
198
214
  JSON.stringify(finalManifest, null, 2),
@@ -206,6 +222,7 @@ export async function buildAll(
206
222
  function assembleManifest(
207
223
  ws: WorkspaceInfo,
208
224
  modules: ModuleDescriptor[],
225
+ chunks: readonly string[],
209
226
  cache: BuildCache,
210
227
  ): BuildManifest {
211
228
  // Aggregate shellHash from all shell:* unit output hashes.
@@ -220,12 +237,41 @@ function assembleManifest(
220
237
 
221
238
  return {
222
239
  modules,
240
+ chunks,
223
241
  shellHash,
224
242
  stylesHash,
225
243
  buildTime: new Date().toISOString(),
226
244
  };
227
245
  }
228
246
 
247
+ // ---------------------------------------------------------------------------
248
+ // Context server bundles — flatten to `<arcDir>/server/<safeName>.js`
249
+ // ---------------------------------------------------------------------------
250
+
251
+ /**
252
+ * Copy each context package's compiled server bundle from
253
+ * `packages/<pkg>/dist/server/main/index.js` to a flat location at
254
+ * `<arcDir>/server/<safeName>.js`. The flat layout makes the deploy image
255
+ * self-contained — `COPY .arc/platform/` pulls everything server-side, no
256
+ * need to drag the entire `packages/` tree into the image.
257
+ */
258
+ function copyContextServerBundles(ws: WorkspaceInfo): void {
259
+ const outDir = join(ws.arcDir, "server");
260
+ mkdirSync(outDir, { recursive: true });
261
+
262
+ for (const pkg of ws.packages) {
263
+ if (!isContextPackage(pkg.packageJson)) continue;
264
+ const src = join(pkg.path, "dist", "server", "main", "index.js");
265
+ if (!existsSync(src)) {
266
+ err(`Server bundle missing for ${pkg.name}: ${src}`);
267
+ continue;
268
+ }
269
+ const safeName = pkg.path.split("/").pop()!;
270
+ const dst = join(outDir, `${safeName}.js`);
271
+ copyFileSync(src, dst);
272
+ }
273
+ }
274
+
229
275
  // ---------------------------------------------------------------------------
230
276
  // Browser assets — @arcote.tech/* deps deklarują w `arc.browserAssets` jakie
231
277
  // pliki muszą być dostępne w przeglądarce (np. SQLite WASM worker + .wasm).
@@ -564,47 +610,60 @@ export async function buildShell(
564
610
  // ---------------------------------------------------------------------------
565
611
 
566
612
  export async function loadServerContext(
567
- packages: WorkspacePackage[],
613
+ ws: WorkspaceInfo,
568
614
  ): Promise<{ context: any | null; moduleAccess: Map<string, any> }> {
569
- const ctxPackages = packages.filter((p) => isContextPackage(p.packageJson));
570
- if (ctxPackages.length === 0) return { context: null, moduleAccess: new Map() };
571
-
572
615
  // Set globals for server context — framework packages (arc-auth etc.)
573
616
  // use these at runtime to tree-shake browser/server code paths.
574
617
  (globalThis as any).ONLY_SERVER = true;
575
618
  (globalThis as any).ONLY_BROWSER = false;
576
619
  (globalThis as any).ONLY_CLIENT = false;
577
620
 
578
- // Resolve platform from the project's node_modules using an absolute path
579
- // (see comment in original implementation for why).
621
+ // Resolve platform from the project's node_modules. Bun picks the `bun`
622
+ // export condition from package.json, which points at `index.server.ts`
623
+ // (server-safe, no React/JSX at top level).
580
624
  const platformDir = join(process.cwd(), "node_modules", "@arcote.tech", "platform");
581
625
  const platformPkg = JSON.parse(readFileSync(join(platformDir, "package.json"), "utf-8"));
582
626
  const platformEntry = join(platformDir, platformPkg.main ?? "src/index.ts");
583
627
 
584
628
  await import(platformEntry);
585
629
 
586
- for (const ctx of ctxPackages) {
587
- const serverDist = join(ctx.path, "dist", "server", "main", "index.js");
588
- if (!existsSync(serverDist)) {
589
- err(`Context server dist not found: ${serverDist}`);
590
- continue;
591
- }
592
-
593
- try {
594
- await import(serverDist);
595
- } catch (e) {
596
- err(`Failed to load server context from ${ctx.name}: ${e}`);
630
+ // Primary source: flattened server bundles at `<arcDir>/server/<safeName>.js`.
631
+ // The deploy image only has this directory — there's no workspace `packages/`
632
+ // tree. In dev, `copyContextServerBundles` populates this same location, so
633
+ // both modes go through the same code path.
634
+ const serverDir = join(ws.arcDir, "server");
635
+ const bundles = existsSync(serverDir)
636
+ ? readdirSync(serverDir).filter((f) => f.endsWith(".js"))
637
+ : [];
638
+
639
+ if (bundles.length > 0) {
640
+ for (const file of bundles) {
641
+ const bundlePath = join(serverDir, file);
642
+ try {
643
+ await import(bundlePath);
644
+ } catch (e) {
645
+ err(`Failed to load server bundle ${file}: ${e}`);
646
+ }
597
647
  }
598
- }
599
-
600
- const nonCtxPackages = packages.filter((p) => !isContextPackage(p.packageJson));
601
- for (const pkg of nonCtxPackages) {
602
- try {
603
- await import(pkg.entrypoint);
604
- } catch {
605
- // Non-context packages may fail on server (React components etc.) — that's OK,
606
- // module().protectedBy().build() runs synchronously before any rendering
648
+ } else if (ws.packages.length > 0) {
649
+ // Fallback for the "no .arc/platform/server/ yet" case (e.g. somebody
650
+ // wired up loadServerContext before running the build). This path goes
651
+ // through workspace packages directly — only meaningful in dev.
652
+ const ctxPackages = ws.packages.filter((p) => isContextPackage(p.packageJson));
653
+ for (const ctx of ctxPackages) {
654
+ const serverDist = join(ctx.path, "dist", "server", "main", "index.js");
655
+ if (!existsSync(serverDist)) {
656
+ err(`Context server dist not found: ${serverDist}`);
657
+ continue;
658
+ }
659
+ try {
660
+ await import(serverDist);
661
+ } catch (e) {
662
+ err(`Failed to load server context from ${ctx.name}: ${e}`);
663
+ }
607
664
  }
665
+ } else {
666
+ return { context: null, moduleAccess: new Map() };
608
667
  }
609
668
 
610
669
  const { getContext, getAllModuleAccess } = await import(platformEntry);
@@ -0,0 +1,160 @@
1
+ import { existsSync, readFileSync, watch } from "fs";
2
+ import { join } from "path";
3
+ import { startPlatformServer, type PlatformServer } from "./server";
4
+ import type { BuildManifest } from "./shared";
5
+ import {
6
+ buildAll,
7
+ collectArcPeerDeps,
8
+ err,
9
+ loadServerContext,
10
+ log,
11
+ ok,
12
+ type WorkspaceInfo,
13
+ } from "./shared";
14
+
15
+ // ---------------------------------------------------------------------------
16
+ // startup — single entry point for `arc platform start` and `arc platform dev`.
17
+ //
18
+ // The runtime host code path is IDENTICAL in both modes. Only two things
19
+ // differ:
20
+ // - dev mode runs `buildAll` on startup (and rebuilds on file changes)
21
+ // - dev mode wires up the file watcher + SSE notify on rebuild
22
+ //
23
+ // Production mode reads a pre-built manifest from disk (in the deploy image,
24
+ // that manifest was baked in at image build time) and never rebuilds.
25
+ //
26
+ // Module access map and signed URL semantics, chunk-aware static serving,
27
+ // /api/modules filtering — all identical. This is the "dev↔prod parity"
28
+ // promise that the v0.7 refactor was built around.
29
+ // ---------------------------------------------------------------------------
30
+
31
+ export interface StartPlatformOptions {
32
+ ws: WorkspaceInfo;
33
+ /** Listening port. Defaults to PORT env var or 5005. */
34
+ port?: number;
35
+ /** SQLite database path. Defaults to `<ws.rootDir>/.arc/data/<mode>.db`. */
36
+ dbPath?: string;
37
+ /** Dev mode: rebuild on startup, watch for changes, SSE-notify clients on rebuild. */
38
+ devMode: boolean;
39
+ }
40
+
41
+ export async function startPlatform(
42
+ opts: StartPlatformOptions,
43
+ ): Promise<void> {
44
+ const { ws, devMode } = opts;
45
+ const port = opts.port ?? parseInt(process.env.PORT || "5005", 10);
46
+ const dbPath =
47
+ opts.dbPath ??
48
+ join(ws.rootDir, ".arc", "data", devMode ? "dev.db" : "prod.db");
49
+
50
+ // 1. Acquire the manifest. In dev we always rebuild (fast — cache makes
51
+ // subsequent passes incremental). In prod we read the pre-built one;
52
+ // missing manifest is a hard error.
53
+ let manifest: BuildManifest;
54
+ if (devMode) {
55
+ manifest = await buildAll(ws);
56
+ } else {
57
+ const manifestPath = join(ws.modulesDir, "manifest.json");
58
+ if (!existsSync(manifestPath)) {
59
+ err("No build found. Run `arc platform build` first.");
60
+ process.exit(1);
61
+ }
62
+ manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
63
+ }
64
+
65
+ // 2. Server context — same code path in both modes; loadServerContext
66
+ // prefers .arc/platform/server/*.js (the canonical location after the
67
+ // copyContextServerBundles step in buildAll).
68
+ log("Loading server context...");
69
+ const { context, moduleAccess } = await loadServerContext(ws);
70
+ if (context) {
71
+ ok("Context loaded");
72
+ } else {
73
+ log("No context — server endpoints skipped");
74
+ }
75
+
76
+ // 3. Start the platform server. Cache headers + SSE behaviour are
77
+ // controlled inside the server by `devMode`; we just pass the flag.
78
+ const arcEntries = collectArcPeerDeps(ws.packages);
79
+ const platform = await startPlatformServer({
80
+ ws,
81
+ port,
82
+ manifest,
83
+ context,
84
+ moduleAccess,
85
+ dbPath,
86
+ devMode,
87
+ arcEntries,
88
+ });
89
+
90
+ ok(`Server on http://localhost:${port}`);
91
+ if (platform.contextHandler) {
92
+ ok("Commands, queries, WebSocket — all on same port");
93
+ }
94
+
95
+ // 4. Dev-only: file watcher + debounced rebuild + SSE notify.
96
+ if (devMode) {
97
+ attachDevWatcher(ws, platform);
98
+ }
99
+
100
+ // 5. Graceful shutdown for both modes.
101
+ const cleanup = () => {
102
+ platform.stop();
103
+ process.exit(0);
104
+ };
105
+ process.on("SIGTERM", cleanup);
106
+ process.on("SIGINT", cleanup);
107
+ }
108
+
109
+ function attachDevWatcher(ws: WorkspaceInfo, platform: PlatformServer): void {
110
+ log("Watching for changes...");
111
+ let rebuildTimer: ReturnType<typeof setTimeout> | null = null;
112
+ let isRebuilding = false;
113
+
114
+ const triggerRebuild = () => {
115
+ if (rebuildTimer) clearTimeout(rebuildTimer);
116
+ rebuildTimer = setTimeout(async () => {
117
+ if (isRebuilding) return;
118
+ isRebuilding = true;
119
+ log("Rebuilding...");
120
+ try {
121
+ const next = await buildAll(ws);
122
+ platform.setManifest(next);
123
+ platform.notifyReload(next);
124
+ ok(`Rebuilt — ${next.modules.length} module(s)`);
125
+ } catch (e) {
126
+ console.error(`Rebuild failed: ${e}`);
127
+ } finally {
128
+ isRebuilding = false;
129
+ }
130
+ }, 300);
131
+ };
132
+
133
+ for (const pkg of ws.packages) {
134
+ const srcDir = join(pkg.path, "src");
135
+ if (!existsSync(srcDir)) continue;
136
+
137
+ watch(srcDir, { recursive: true }, (_event, filename) => {
138
+ if (
139
+ !filename ||
140
+ filename.includes(".arc") ||
141
+ filename.endsWith(".d.ts") ||
142
+ filename.includes("node_modules") ||
143
+ filename.includes("dist")
144
+ )
145
+ return;
146
+ if (!filename.endsWith(".ts") && !filename.endsWith(".tsx")) return;
147
+ triggerRebuild();
148
+ });
149
+ }
150
+
151
+ // .po files — translations are a separate cache unit, but buildAll picks
152
+ // them up via finalizeTranslations after extraction. Treat the same as src.
153
+ const localesDir = join(ws.rootDir, "locales");
154
+ if (existsSync(localesDir)) {
155
+ watch(localesDir, { recursive: false }, (_event, filename) => {
156
+ if (!filename?.endsWith(".po")) return;
157
+ triggerRebuild();
158
+ });
159
+ }
160
+ }
@@ -1,29 +0,0 @@
1
- # arcote/runtime — generic Arc platform runtime container.
2
- #
3
- # Image is intentionally version-agnostic. CLI version comes from the
4
- # ARC_CLI_VERSION env var set by docker-compose (generated per-deploy by
5
- # `arc platform deploy`), so a single `arcote/runtime:1` tag serves every
6
- # CLI release. Bumping CLI only requires `bun publish` — no image rebuild.
7
- #
8
- # Layout in container:
9
- # /app/.arc/cli/<ARC_CLI_VERSION>/ ← arc-cli + direct deps (installed by entrypoint, cached)
10
- # /app/.arc/platform/ ← user volume: code, framework + per-module deps
11
- # /app/.arc/data/ ← user volume: sqlite
12
- # /root/.bun/install/cache/ ← shared bun store (volume)
13
-
14
- FROM oven/bun:1-alpine
15
-
16
- # tini for proper signal handling. build-base/python3/git in case any user dep
17
- # has a native postinstall step (e.g. better-sqlite3). curl for healthchecks.
18
- RUN apk add --no-cache tini ca-certificates build-base python3 git curl
19
-
20
- WORKDIR /app
21
-
22
- COPY entrypoint.sh /entrypoint.sh
23
- RUN chmod +x /entrypoint.sh
24
-
25
- EXPOSE 5005
26
- ENV PORT=5005 \
27
- ARC_DEPLOY_API=1
28
-
29
- ENTRYPOINT ["tini", "--", "/entrypoint.sh"]
@@ -1,23 +0,0 @@
1
- #!/usr/bin/env bash
2
- # build-and-push.sh — manual multi-arch publish of pkrasinski/arc-runtime to Docker Hub.
3
- #
4
- # Image is generic (no CLI version baked in) — one tag covers every CLI release.
5
- # Re-run only when the entrypoint script or base image needs to change.
6
- #
7
- # Prereqs: docker buildx + multi-arch builder configured, `docker login` done.
8
- #
9
- # Usage: bash build-and-push.sh [tag=1]
10
-
11
- set -euo pipefail
12
-
13
- TAG="${1:-1}"
14
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
15
-
16
- docker buildx build \
17
- --platform linux/amd64,linux/arm64 \
18
- --tag "pkrasinski/arc-runtime:${TAG}" \
19
- --tag "pkrasinski/arc-runtime:latest" \
20
- --push \
21
- "${SCRIPT_DIR}"
22
-
23
- echo "✓ Published pkrasinski/arc-runtime:${TAG} (multi-arch)"
@@ -1,58 +0,0 @@
1
- #!/bin/sh
2
- # entrypoint.sh — installs arc-cli at ARC_CLI_VERSION (cached per version),
3
- # then hands off to `arc platform start`. The CLI itself decides between
4
- # pre-deploy mode (no manifest yet) and full mode based on volume state —
5
- # no logic for that lives here.
6
- #
7
- # bun install of framework peers and per-module deps is done by the CLI in
8
- # response to /api/deploy/framework and /api/deploy/modules/:name — this
9
- # script only ensures the CLI binary itself is present.
10
-
11
- set -e
12
-
13
- : "${ARC_CLI_VERSION:?ARC_CLI_VERSION env var required (set by docker-compose)}"
14
-
15
- CLI_DIR="/app/.arc/cli/${ARC_CLI_VERSION}"
16
- CLI_BIN="${CLI_DIR}/node_modules/@arcote.tech/arc-cli/dist/index.js"
17
-
18
- if [ ! -f "$CLI_BIN" ]; then
19
- echo "[entrypoint] installing @arcote.tech/arc-cli@${ARC_CLI_VERSION}..."
20
- mkdir -p "$CLI_DIR"
21
- cd "$CLI_DIR"
22
- echo '{"name":"arc-runtime-cli","private":true,"type":"module"}' > package.json
23
- bun add "@arcote.tech/arc-cli@${ARC_CLI_VERSION}"
24
- fi
25
-
26
- # resolveWorkspace() in arc platform start exits hard if /app has no
27
- # package.json. In runtime mode the workspace lives in /app/.arc/platform/
28
- # but the CLI still walks up from cwd. Drop a stub manifest here so the
29
- # walk-up resolves to /app/.arc/platform (or to a stable "no workspace"
30
- # state in pre-deploy).
31
- if [ ! -f /app/package.json ]; then
32
- cat > /app/package.json <<'EOF'
33
- {
34
- "name": "arc-runtime",
35
- "private": true,
36
- "type": "module",
37
- "workspaces": []
38
- }
39
- EOF
40
- fi
41
-
42
- # Make /app/.arc/platform the working directory — that's where deployed user
43
- # code, deps and node_modules live (volume mount).
44
- mkdir -p /app/.arc/platform
45
- cd /app/.arc/platform
46
- if [ ! -f package.json ]; then
47
- cat > package.json <<'EOF'
48
- {
49
- "name": "arc-platform-runtime",
50
- "private": true,
51
- "type": "module",
52
- "workspaces": []
53
- }
54
- EOF
55
- fi
56
-
57
- echo "[entrypoint] starting arc platform (cli=${ARC_CLI_VERSION})"
58
- exec bun run "$CLI_BIN" platform start