@arcote.tech/arc-cli 0.6.2 → 0.7.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.
- package/dist/index.js +1696 -1663
- package/package.json +7 -7
- package/src/builder/access-extractor.ts +64 -46
- package/src/builder/build-cache.ts +3 -1
- package/src/builder/chunk-planner.ts +107 -0
- package/src/builder/dependency-collector.ts +83 -41
- package/src/builder/framework-peers.ts +81 -0
- package/src/builder/module-builder.ts +322 -106
- package/src/commands/platform-build.ts +2 -1
- package/src/commands/platform-deploy.ts +121 -64
- package/src/commands/platform-dev.ts +11 -100
- package/src/commands/platform-start.ts +4 -90
- package/src/deploy/ansible.ts +23 -3
- package/src/deploy/assets/ansible/site.yml +23 -7
- package/src/deploy/assets.ts +23 -7
- package/src/deploy/bootstrap.ts +270 -10
- package/src/deploy/caddyfile.ts +19 -23
- package/src/deploy/compose.ts +44 -27
- package/src/deploy/config.ts +67 -3
- package/src/deploy/deploy-env.ts +129 -0
- package/src/deploy/env-file.ts +103 -0
- package/src/deploy/htpasswd.ts +28 -0
- package/src/deploy/image-template.ts +74 -0
- package/src/deploy/image.ts +243 -0
- package/src/deploy/registry.ts +79 -0
- package/src/deploy/ssh.ts +52 -122
- package/src/deploy/survey.ts +64 -0
- package/src/index.ts +20 -13
- package/src/platform/server.ts +119 -94
- package/src/platform/shared.ts +139 -292
- package/src/platform/startup.ts +159 -0
- package/runtime/Dockerfile +0 -29
- package/runtime/build-and-push.sh +0 -23
- package/runtime/entrypoint.sh +0 -58
- package/src/commands/build-shell.ts +0 -152
- package/src/deploy/remote-sync.ts +0 -321
- package/src/platform/deploy-api.ts +0 -400
package/src/platform/shared.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { findUpSync } from "find-up";
|
|
2
|
-
import { copyFileSync, existsSync, mkdirSync,
|
|
2
|
+
import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "fs";
|
|
3
3
|
import { dirname, join } from "path";
|
|
4
4
|
import {
|
|
5
|
+
buildBrowserApp,
|
|
5
6
|
buildContextPackages,
|
|
6
|
-
buildModulesBundle,
|
|
7
7
|
buildStyles,
|
|
8
8
|
buildTranslations,
|
|
9
9
|
discoverPackages,
|
|
10
10
|
isContextPackage,
|
|
11
|
+
type BrowserAppResult,
|
|
11
12
|
type BuildManifest,
|
|
12
13
|
type ModuleDescriptor,
|
|
13
14
|
type WorkspacePackage,
|
|
@@ -19,23 +20,18 @@ import {
|
|
|
19
20
|
updateCache,
|
|
20
21
|
type BuildCache,
|
|
21
22
|
} from "../builder/build-cache";
|
|
23
|
+
import { extractAccessMap } from "../builder/access-extractor";
|
|
24
|
+
import { planChunks } from "../builder/chunk-planner";
|
|
25
|
+
import { collectFrameworkDeps } from "../builder/dependency-collector";
|
|
22
26
|
import {
|
|
23
27
|
mtimeOf,
|
|
24
|
-
readInstalledVersion,
|
|
25
28
|
sha256Hex,
|
|
26
|
-
sha256OfDir,
|
|
27
29
|
sha256OfFiles,
|
|
28
30
|
sha256OfJson,
|
|
29
31
|
} from "../builder/hash";
|
|
30
|
-
import { pAll } from "../builder/parallel";
|
|
31
|
-
import {
|
|
32
|
-
collectFrameworkDeps,
|
|
33
|
-
collectModuleDeps,
|
|
34
|
-
} from "../builder/dependency-collector";
|
|
35
|
-
import { extractAccessMap } from "../builder/access-extractor";
|
|
36
32
|
|
|
37
33
|
// Re-export for convenience
|
|
38
|
-
export { buildContextPackages,
|
|
34
|
+
export { buildContextPackages, buildStyles, isContextPackage };
|
|
39
35
|
export type { BuildManifest, ModuleDescriptor, WorkspacePackage };
|
|
40
36
|
|
|
41
37
|
// ---------------------------------------------------------------------------
|
|
@@ -70,8 +66,8 @@ export interface WorkspaceInfo {
|
|
|
70
66
|
rootPkg: Record<string, any>;
|
|
71
67
|
appName: string;
|
|
72
68
|
arcDir: string;
|
|
73
|
-
|
|
74
|
-
|
|
69
|
+
/** Single Bun.build output dir for browser app: `<arcDir>/browser/`. */
|
|
70
|
+
browserDir: string;
|
|
75
71
|
/** Static assets generated z arc.browserAssets workspace deps. Serwowane pod /assets/*. */
|
|
76
72
|
assetsDir: string;
|
|
77
73
|
publicDir: string;
|
|
@@ -93,16 +89,15 @@ export function resolveWorkspace(): WorkspaceInfo {
|
|
|
93
89
|
|
|
94
90
|
log("Scanning workspaces...");
|
|
95
91
|
const packages = discoverPackages(rootDir);
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
process.exit(1);
|
|
92
|
+
if (packages.length > 0) {
|
|
93
|
+
ok(
|
|
94
|
+
`Found ${packages.length} package(s): ${packages.map((p) => p.name).join(", ")}`,
|
|
95
|
+
);
|
|
96
|
+
} else {
|
|
97
|
+
// Zero packages is valid for `platform start` in image runtime mode —
|
|
98
|
+
// the workspace tree isn't shipped, only `.arc/platform/` is. Build
|
|
99
|
+
// commands enforce non-empty packages where they need them.
|
|
100
|
+
log("No workspace packages found — assuming image runtime mode.");
|
|
106
101
|
}
|
|
107
102
|
|
|
108
103
|
// Detect manifest.json or manifest.webmanifest in root dir
|
|
@@ -131,8 +126,7 @@ export function resolveWorkspace(): WorkspaceInfo {
|
|
|
131
126
|
rootPkg,
|
|
132
127
|
appName,
|
|
133
128
|
arcDir,
|
|
134
|
-
|
|
135
|
-
shellDir: join(arcDir, "shell"),
|
|
129
|
+
browserDir: join(arcDir, "browser"),
|
|
136
130
|
assetsDir: join(arcDir, "assets"),
|
|
137
131
|
publicDir: join(rootDir, "public"),
|
|
138
132
|
packages,
|
|
@@ -163,69 +157,115 @@ export async function buildAll(
|
|
|
163
157
|
|
|
164
158
|
log(`Building (concurrency parallel${noCache ? ", no-cache" : ""})...`);
|
|
165
159
|
|
|
166
|
-
//
|
|
167
|
-
//
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
160
|
+
// Phase 1 — context packages must finish FIRST. The access-extractor
|
|
161
|
+
// subprocess imports workspace packages by name, which resolve through
|
|
162
|
+
// node_modules to packages' `main` field (typically `dist/server/main/`).
|
|
163
|
+
await buildContextPackages(ws.rootDir, ws.packages, cache, noCache);
|
|
164
|
+
|
|
165
|
+
// Phase 1b — relocate per-package server bundles into `.arc/platform/server/`
|
|
166
|
+
// so the deploy image can be self-contained (image COPY needs everything
|
|
167
|
+
// server-side under one root). loadServerContext reads from here in prod.
|
|
168
|
+
copyContextServerBundles(ws);
|
|
169
|
+
|
|
170
|
+
// Phase 2 — extract access metadata (token name + hasCheck per module) in
|
|
171
|
+
// an isolated subprocess. This MUST run before chunk planning so we know
|
|
172
|
+
// which token group each module belongs to.
|
|
173
|
+
const accessMap = await extractAccessMap(ws.rootDir, ws.packages);
|
|
174
|
+
|
|
175
|
+
// Persist access map for the runtime host (server.ts reads at startup to
|
|
176
|
+
// wire up moduleAccessMap for filterManifestForTokens / signed URLs).
|
|
177
|
+
mkdirSync(ws.arcDir, { recursive: true });
|
|
178
|
+
writeFileSync(
|
|
179
|
+
join(ws.arcDir, "access.json"),
|
|
180
|
+
JSON.stringify(accessMap, null, 2) + "\n",
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
// Phase 3 — group modules into chunks (one Bun.build per group).
|
|
184
|
+
const plan = planChunks(ws.packages, accessMap);
|
|
185
|
+
ok(
|
|
186
|
+
`Chunks: ${plan.chunks
|
|
187
|
+
.map((c) => `${c}(${plan.groups.get(c)?.length ?? 0})`)
|
|
188
|
+
.join(", ")}`,
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
// Phase 4 — independent parallel build units. The browser app is a single
|
|
192
|
+
// Bun.build call covering initial + all token groups (replaces ~25 separate
|
|
193
|
+
// Bun.builds for shell + per-chunk + per-package).
|
|
194
|
+
const i18nCollector = new Map<string, Set<string>>();
|
|
195
|
+
|
|
196
|
+
const [browserResult] = await Promise.all([
|
|
197
|
+
buildBrowserApp(ws.rootDir, ws.browserDir, plan, cache, noCache, i18nCollector),
|
|
172
198
|
buildStyles(ws.rootDir, ws.arcDir, ws.packages, themePath, cache, noCache),
|
|
173
199
|
copyBrowserAssets(ws, cache, noCache),
|
|
174
200
|
buildTranslations(ws.rootDir, ws.arcDir, cache, noCache),
|
|
175
201
|
]);
|
|
176
202
|
|
|
177
|
-
|
|
203
|
+
// Finalize i18n catalogs once after all chunks + initial bundle collected.
|
|
204
|
+
const { finalizeTranslations } = await import("../i18n");
|
|
205
|
+
await finalizeTranslations(ws.rootDir, ws.arcDir, i18nCollector);
|
|
178
206
|
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
//
|
|
182
|
-
// extraction subprocess imports server bundles whose own deps may matter).
|
|
207
|
+
// Phase 5 — framework peer manifest at `<arcDir>/package.json`. Used by
|
|
208
|
+
// the deploy image build (single `bun install` for all peers, one copy
|
|
209
|
+
// shared across module bundles).
|
|
183
210
|
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
211
|
|
|
195
|
-
|
|
212
|
+
saveBuildCache(ws.arcDir, cache);
|
|
213
|
+
|
|
214
|
+
const finalManifest = assembleManifest(ws, browserResult, cache);
|
|
196
215
|
writeFileSync(
|
|
197
|
-
join(ws.
|
|
216
|
+
join(ws.arcDir, "manifest.json"),
|
|
198
217
|
JSON.stringify(finalManifest, null, 2),
|
|
199
218
|
);
|
|
200
219
|
return finalManifest;
|
|
201
220
|
}
|
|
202
221
|
|
|
203
222
|
/**
|
|
204
|
-
* Assemble the platform manifest from
|
|
223
|
+
* Assemble the platform manifest from the browser app result + style hash.
|
|
205
224
|
*/
|
|
206
225
|
function assembleManifest(
|
|
207
226
|
ws: WorkspaceInfo,
|
|
208
|
-
|
|
227
|
+
browser: BrowserAppResult,
|
|
209
228
|
cache: BuildCache,
|
|
210
229
|
): BuildManifest {
|
|
211
|
-
// Aggregate shellHash from all shell:* unit output hashes.
|
|
212
|
-
const shellEntries: Record<string, string> = {};
|
|
213
|
-
for (const [unitId, entry] of Object.entries(cache.units)) {
|
|
214
|
-
if (unitId.startsWith("shell:") && entry.outputHash) {
|
|
215
|
-
shellEntries[unitId] = entry.outputHash;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
const shellHash = sha256OfJson(shellEntries);
|
|
219
230
|
const stylesHash = cache.units["styles"]?.outputHash ?? "";
|
|
220
231
|
|
|
221
232
|
return {
|
|
222
|
-
|
|
223
|
-
|
|
233
|
+
initial: browser.initial,
|
|
234
|
+
groups: browser.groups,
|
|
235
|
+
sharedChunks: browser.sharedChunks,
|
|
224
236
|
stylesHash,
|
|
225
237
|
buildTime: new Date().toISOString(),
|
|
226
238
|
};
|
|
227
239
|
}
|
|
228
240
|
|
|
241
|
+
// ---------------------------------------------------------------------------
|
|
242
|
+
// Context server bundles — flatten to `<arcDir>/server/<safeName>.js`
|
|
243
|
+
// ---------------------------------------------------------------------------
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Copy each context package's compiled server bundle from
|
|
247
|
+
* `packages/<pkg>/dist/server/main/index.js` to a flat location at
|
|
248
|
+
* `<arcDir>/server/<safeName>.js`. The flat layout makes the deploy image
|
|
249
|
+
* self-contained — `COPY .arc/platform/` pulls everything server-side, no
|
|
250
|
+
* need to drag the entire `packages/` tree into the image.
|
|
251
|
+
*/
|
|
252
|
+
function copyContextServerBundles(ws: WorkspaceInfo): void {
|
|
253
|
+
const outDir = join(ws.arcDir, "server");
|
|
254
|
+
mkdirSync(outDir, { recursive: true });
|
|
255
|
+
|
|
256
|
+
for (const pkg of ws.packages) {
|
|
257
|
+
if (!isContextPackage(pkg.packageJson)) continue;
|
|
258
|
+
const src = join(pkg.path, "dist", "server", "main", "index.js");
|
|
259
|
+
if (!existsSync(src)) {
|
|
260
|
+
err(`Server bundle missing for ${pkg.name}: ${src}`);
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
const safeName = pkg.path.split("/").pop()!;
|
|
264
|
+
const dst = join(outDir, `${safeName}.js`);
|
|
265
|
+
copyFileSync(src, dst);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
229
269
|
// ---------------------------------------------------------------------------
|
|
230
270
|
// Browser assets — @arcote.tech/* deps deklarują w `arc.browserAssets` jakie
|
|
231
271
|
// pliki muszą być dostępne w przeglądarce (np. SQLite WASM worker + .wasm).
|
|
@@ -353,258 +393,65 @@ async function copyBrowserAssets(
|
|
|
353
393
|
updateCache(cache, unitId, inputHash, { outputHashes });
|
|
354
394
|
}
|
|
355
395
|
|
|
356
|
-
// ---------------------------------------------------------------------------
|
|
357
|
-
// Shell builder — framework packages for import map
|
|
358
|
-
// ---------------------------------------------------------------------------
|
|
359
|
-
|
|
360
|
-
/** Collect all @arcote.tech/* peerDependencies from workspace packages. */
|
|
361
|
-
export function collectArcPeerDeps(packages: WorkspacePackage[]): [string, string][] {
|
|
362
|
-
const seen = new Set<string>();
|
|
363
|
-
for (const pkg of ["@arcote.tech/arc", "@arcote.tech/arc-ds", "@arcote.tech/arc-react", "@arcote.tech/platform"]) {
|
|
364
|
-
seen.add(pkg);
|
|
365
|
-
}
|
|
366
|
-
for (const wp of packages) {
|
|
367
|
-
const peerDeps = wp.packageJson.peerDependencies ?? {};
|
|
368
|
-
for (const dep of Object.keys(peerDeps)) {
|
|
369
|
-
if (dep.startsWith("@arcote.tech/")) seen.add(dep);
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
return [...seen].map((pkg) => {
|
|
373
|
-
const short = pkg === "@arcote.tech/platform"
|
|
374
|
-
? "platform"
|
|
375
|
-
: pkg.replace("@arcote.tech/", "");
|
|
376
|
-
return [short, pkg];
|
|
377
|
-
});
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
const REACT_ENTRIES: [string, string][] = [
|
|
381
|
-
[
|
|
382
|
-
"react",
|
|
383
|
-
`import React from "react";
|
|
384
|
-
export default React;
|
|
385
|
-
export const {
|
|
386
|
-
Children, Component, Fragment, Profiler, PureComponent, StrictMode, Suspense,
|
|
387
|
-
cloneElement, createContext, createElement, createRef, forwardRef, isValidElement,
|
|
388
|
-
lazy, memo, startTransition, use, useCallback, useContext, useDebugValue,
|
|
389
|
-
useDeferredValue, useEffect, useId, useImperativeHandle, useInsertionEffect,
|
|
390
|
-
useLayoutEffect, useMemo, useReducer, useRef, useState, useSyncExternalStore,
|
|
391
|
-
useTransition, version, useActionState, useOptimistic,
|
|
392
|
-
} = React;`,
|
|
393
|
-
],
|
|
394
|
-
["jsx-runtime", `export { jsx, jsxs, Fragment } from "react/jsx-runtime";`],
|
|
395
|
-
[
|
|
396
|
-
"jsx-dev-runtime",
|
|
397
|
-
`export { jsxDEV, Fragment } from "react/jsx-dev-runtime";`,
|
|
398
|
-
],
|
|
399
|
-
[
|
|
400
|
-
"react-dom",
|
|
401
|
-
`import ReactDOM from "react-dom";
|
|
402
|
-
export default ReactDOM;
|
|
403
|
-
export const { createPortal, flushSync } = ReactDOM;`,
|
|
404
|
-
],
|
|
405
|
-
[
|
|
406
|
-
"react-dom-client",
|
|
407
|
-
`export { createRoot, hydrateRoot } from "react-dom/client";`,
|
|
408
|
-
],
|
|
409
|
-
];
|
|
410
|
-
|
|
411
|
-
const REACT_OUTPUT_FILES = REACT_ENTRIES.map(([n]) => `${n}.js`);
|
|
412
|
-
|
|
413
|
-
const SHELL_BASE_EXTERNAL = [
|
|
414
|
-
"react",
|
|
415
|
-
"react-dom",
|
|
416
|
-
"react/jsx-runtime",
|
|
417
|
-
"react/jsx-dev-runtime",
|
|
418
|
-
"react-dom/client",
|
|
419
|
-
];
|
|
420
|
-
|
|
421
|
-
const sourceFilter = (rel: string): boolean => {
|
|
422
|
-
if (rel.startsWith("dist/") || rel.startsWith("dist")) return false;
|
|
423
|
-
if (rel.includes("/node_modules/") || rel.startsWith("node_modules")) return false;
|
|
424
|
-
if (rel.startsWith(".arc/") || rel.startsWith(".arc")) return false;
|
|
425
|
-
return true;
|
|
426
|
-
};
|
|
427
|
-
|
|
428
|
-
function arcPkgSrcHash(rootDir: string, pkg: string): string {
|
|
429
|
-
// Prefer src/ tree (workspace links), fallback to whole package dir.
|
|
430
|
-
const srcDir = join(rootDir, "node_modules", pkg, "src");
|
|
431
|
-
if (existsSync(srcDir)) return sha256OfDir(srcDir, sourceFilter);
|
|
432
|
-
return sha256OfDir(join(rootDir, "node_modules", pkg), sourceFilter);
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
async function buildShellReact(
|
|
436
|
-
shellDir: string,
|
|
437
|
-
tmpDir: string,
|
|
438
|
-
rootDir: string,
|
|
439
|
-
cache: BuildCache,
|
|
440
|
-
noCache: boolean,
|
|
441
|
-
): Promise<void> {
|
|
442
|
-
const unitId = "shell:react";
|
|
443
|
-
const inputHash = sha256OfJson({
|
|
444
|
-
react: readInstalledVersion(rootDir, "react"),
|
|
445
|
-
"react-dom": readInstalledVersion(rootDir, "react-dom"),
|
|
446
|
-
entries: REACT_ENTRIES.map(([k, v]) => [k, v]),
|
|
447
|
-
});
|
|
448
|
-
|
|
449
|
-
const requiredOutputs = REACT_OUTPUT_FILES.map((f) => join(shellDir, f));
|
|
450
|
-
if (!noCache && isCacheHit(cache, unitId, inputHash, requiredOutputs)) {
|
|
451
|
-
console.log(` ✓ cached: shell:react`);
|
|
452
|
-
return;
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
console.log(` building: shell:react`);
|
|
456
|
-
|
|
457
|
-
const reactEps: string[] = [];
|
|
458
|
-
for (const [name, code] of REACT_ENTRIES) {
|
|
459
|
-
const f = join(tmpDir, `${name}.ts`);
|
|
460
|
-
await Bun.write(f, code);
|
|
461
|
-
reactEps.push(f);
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
const r = await Bun.build({
|
|
465
|
-
entrypoints: reactEps,
|
|
466
|
-
outdir: shellDir,
|
|
467
|
-
splitting: true,
|
|
468
|
-
format: "esm",
|
|
469
|
-
target: "browser",
|
|
470
|
-
naming: "[name].[ext]",
|
|
471
|
-
});
|
|
472
|
-
if (!r.success) {
|
|
473
|
-
for (const l of r.logs) console.error(l);
|
|
474
|
-
throw new Error("Shell React build failed");
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
const outputHash = sha256OfFiles(requiredOutputs);
|
|
478
|
-
updateCache(cache, unitId, inputHash, { outputHash });
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
async function buildShellArcEntry(
|
|
482
|
-
shortName: string,
|
|
483
|
-
pkg: string,
|
|
484
|
-
allArcPkgs: string[],
|
|
485
|
-
shellDir: string,
|
|
486
|
-
tmpDir: string,
|
|
487
|
-
rootDir: string,
|
|
488
|
-
cache: BuildCache,
|
|
489
|
-
noCache: boolean,
|
|
490
|
-
): Promise<void> {
|
|
491
|
-
const unitId = `shell:arc:${shortName}`;
|
|
492
|
-
const otherExternals = allArcPkgs.filter((p) => p !== pkg);
|
|
493
|
-
const inputHash = sha256OfJson({
|
|
494
|
-
pkg,
|
|
495
|
-
version: readInstalledVersion(rootDir, pkg),
|
|
496
|
-
src: arcPkgSrcHash(rootDir, pkg),
|
|
497
|
-
base: SHELL_BASE_EXTERNAL,
|
|
498
|
-
others: [...otherExternals].sort(),
|
|
499
|
-
});
|
|
500
|
-
|
|
501
|
-
const outputFile = join(shellDir, `${shortName}.js`);
|
|
502
|
-
if (!noCache && isCacheHit(cache, unitId, inputHash, [outputFile])) {
|
|
503
|
-
console.log(` ✓ cached: ${unitId}`);
|
|
504
|
-
return;
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
console.log(` building: ${unitId}`);
|
|
508
|
-
|
|
509
|
-
const f = join(tmpDir, `${shortName}.ts`);
|
|
510
|
-
await Bun.write(f, `export * from "${pkg}";\n`);
|
|
511
|
-
|
|
512
|
-
const r = await Bun.build({
|
|
513
|
-
entrypoints: [f],
|
|
514
|
-
outdir: shellDir,
|
|
515
|
-
format: "esm",
|
|
516
|
-
target: "browser",
|
|
517
|
-
naming: "[name].[ext]",
|
|
518
|
-
external: [...SHELL_BASE_EXTERNAL, ...otherExternals],
|
|
519
|
-
define: {
|
|
520
|
-
ONLY_SERVER: "false",
|
|
521
|
-
ONLY_BROWSER: "true",
|
|
522
|
-
ONLY_CLIENT: "true",
|
|
523
|
-
},
|
|
524
|
-
});
|
|
525
|
-
if (!r.success) {
|
|
526
|
-
for (const l of r.logs) console.error(l);
|
|
527
|
-
throw new Error(`Shell build failed for ${pkg}`);
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
const outputHash = sha256OfFiles([outputFile]);
|
|
531
|
-
updateCache(cache, unitId, inputHash, { outputHash });
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
/**
|
|
535
|
-
* Build the framework shell — react layer + each @arcote.tech/* package as a
|
|
536
|
-
* separate cacheable unit. Tasks run in parallel via pAll.
|
|
537
|
-
*/
|
|
538
|
-
export async function buildShell(
|
|
539
|
-
ws: WorkspaceInfo,
|
|
540
|
-
cache: BuildCache,
|
|
541
|
-
noCache: boolean,
|
|
542
|
-
): Promise<void> {
|
|
543
|
-
mkdirSync(ws.shellDir, { recursive: true });
|
|
544
|
-
const tmpDir = join(ws.shellDir, "_tmp");
|
|
545
|
-
mkdirSync(tmpDir, { recursive: true });
|
|
546
|
-
|
|
547
|
-
const arcEntries = collectArcPeerDeps(ws.packages);
|
|
548
|
-
const allArcPkgs = arcEntries.map(([, pkg]) => pkg);
|
|
549
|
-
|
|
550
|
-
const tasks: Array<() => Promise<void>> = [
|
|
551
|
-
() => buildShellReact(ws.shellDir, tmpDir, ws.rootDir, cache, noCache),
|
|
552
|
-
...arcEntries.map(([short, pkg]) => () =>
|
|
553
|
-
buildShellArcEntry(short, pkg, allArcPkgs, ws.shellDir, tmpDir, ws.rootDir, cache, noCache),
|
|
554
|
-
),
|
|
555
|
-
];
|
|
556
|
-
|
|
557
|
-
await pAll(tasks);
|
|
558
|
-
|
|
559
|
-
rmSync(tmpDir, { recursive: true, force: true });
|
|
560
|
-
}
|
|
561
|
-
|
|
562
396
|
// ---------------------------------------------------------------------------
|
|
563
397
|
// Server context loading
|
|
564
398
|
// ---------------------------------------------------------------------------
|
|
565
399
|
|
|
566
400
|
export async function loadServerContext(
|
|
567
|
-
|
|
401
|
+
ws: WorkspaceInfo,
|
|
568
402
|
): 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
403
|
// Set globals for server context — framework packages (arc-auth etc.)
|
|
573
404
|
// use these at runtime to tree-shake browser/server code paths.
|
|
574
405
|
(globalThis as any).ONLY_SERVER = true;
|
|
575
406
|
(globalThis as any).ONLY_BROWSER = false;
|
|
576
407
|
(globalThis as any).ONLY_CLIENT = false;
|
|
577
408
|
|
|
578
|
-
// Resolve platform from the project's node_modules
|
|
579
|
-
// (
|
|
409
|
+
// Resolve platform from the project's node_modules. Platform exports a
|
|
410
|
+
// single entry (./src/index.ts) — React imports at top level are benign
|
|
411
|
+
// in a non-render context (createContext, function defs only).
|
|
580
412
|
const platformDir = join(process.cwd(), "node_modules", "@arcote.tech", "platform");
|
|
581
413
|
const platformPkg = JSON.parse(readFileSync(join(platformDir, "package.json"), "utf-8"));
|
|
582
414
|
const platformEntry = join(platformDir, platformPkg.main ?? "src/index.ts");
|
|
583
415
|
|
|
584
416
|
await import(platformEntry);
|
|
585
417
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
418
|
+
// Primary source: flattened server bundles at `<arcDir>/server/<safeName>.js`.
|
|
419
|
+
// The deploy image only has this directory — there's no workspace `packages/`
|
|
420
|
+
// tree. In dev, `copyContextServerBundles` populates this same location, so
|
|
421
|
+
// both modes go through the same code path.
|
|
422
|
+
const serverDir = join(ws.arcDir, "server");
|
|
423
|
+
const bundles = existsSync(serverDir)
|
|
424
|
+
? readdirSync(serverDir).filter((f) => f.endsWith(".js"))
|
|
425
|
+
: [];
|
|
426
|
+
|
|
427
|
+
if (bundles.length > 0) {
|
|
428
|
+
for (const file of bundles) {
|
|
429
|
+
const bundlePath = join(serverDir, file);
|
|
430
|
+
try {
|
|
431
|
+
await import(bundlePath);
|
|
432
|
+
} catch (e) {
|
|
433
|
+
err(`Failed to load server bundle ${file}: ${e}`);
|
|
434
|
+
}
|
|
597
435
|
}
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
436
|
+
} else if (ws.packages.length > 0) {
|
|
437
|
+
// Fallback for the "no .arc/platform/server/ yet" case (e.g. somebody
|
|
438
|
+
// wired up loadServerContext before running the build). This path goes
|
|
439
|
+
// through workspace packages directly — only meaningful in dev.
|
|
440
|
+
const ctxPackages = ws.packages.filter((p) => isContextPackage(p.packageJson));
|
|
441
|
+
for (const ctx of ctxPackages) {
|
|
442
|
+
const serverDist = join(ctx.path, "dist", "server", "main", "index.js");
|
|
443
|
+
if (!existsSync(serverDist)) {
|
|
444
|
+
err(`Context server dist not found: ${serverDist}`);
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
try {
|
|
448
|
+
await import(serverDist);
|
|
449
|
+
} catch (e) {
|
|
450
|
+
err(`Failed to load server context from ${ctx.name}: ${e}`);
|
|
451
|
+
}
|
|
607
452
|
}
|
|
453
|
+
} else {
|
|
454
|
+
return { context: null, moduleAccess: new Map() };
|
|
608
455
|
}
|
|
609
456
|
|
|
610
457
|
const { getContext, getAllModuleAccess } = await import(platformEntry);
|