@arcote.tech/arc-cli 0.7.20 → 0.7.21
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 +116 -55
- package/package.json +9 -9
- package/src/builder/access-extractor.ts +11 -15
- package/src/builder/module-builder.ts +210 -35
- package/src/deploy/observability-configs.ts +14 -0
- package/src/platform/shared.ts +34 -73
- package/src/platform/startup.ts +2 -2
package/dist/index.js
CHANGED
|
@@ -853,7 +853,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
853
853
|
this._exitCallback = (err) => {
|
|
854
854
|
if (err.code !== "commander.executeSubCommandAsync") {
|
|
855
855
|
throw err;
|
|
856
|
-
}
|
|
856
|
+
}
|
|
857
857
|
};
|
|
858
858
|
}
|
|
859
859
|
return this;
|
|
@@ -34723,6 +34723,17 @@ function serverExternalsPlugin() {
|
|
|
34723
34723
|
}
|
|
34724
34724
|
};
|
|
34725
34725
|
}
|
|
34726
|
+
function workspaceSourcePlugin(srcByName) {
|
|
34727
|
+
return {
|
|
34728
|
+
name: "workspace-source",
|
|
34729
|
+
setup(build2) {
|
|
34730
|
+
build2.onResolve({ filter: /^[^./]/ }, (args) => {
|
|
34731
|
+
const src = srcByName.get(args.path);
|
|
34732
|
+
return src ? { path: src, sideEffects: true } : null;
|
|
34733
|
+
});
|
|
34734
|
+
}
|
|
34735
|
+
};
|
|
34736
|
+
}
|
|
34726
34737
|
function jsxDevShimPlugin() {
|
|
34727
34738
|
return {
|
|
34728
34739
|
name: "jsx-dev-runtime-shim",
|
|
@@ -34743,9 +34754,10 @@ export { Fragment };
|
|
|
34743
34754
|
};
|
|
34744
34755
|
}
|
|
34745
34756
|
var CONTEXT_CLIENTS = [
|
|
34746
|
-
{ name: "server", target: "bun", defines: { ONLY_SERVER: "true", ONLY_BROWSER: "false", ONLY_CLIENT: "false" } },
|
|
34747
34757
|
{ name: "browser", target: "browser", defines: { ONLY_SERVER: "false", ONLY_BROWSER: "true", ONLY_CLIENT: "true" } }
|
|
34748
34758
|
];
|
|
34759
|
+
var SERVER_DEFINES = { ONLY_SERVER: "true", ONLY_BROWSER: "false", ONLY_CLIENT: "false" };
|
|
34760
|
+
var SERVER_ENTRY_FILE = "_server.js";
|
|
34749
34761
|
function discoverPackages(rootDir) {
|
|
34750
34762
|
const rootPkg = JSON.parse(readFileSync7(join8(rootDir, "package.json"), "utf-8"));
|
|
34751
34763
|
const workspaceGlobs = rootPkg.workspaces ?? [];
|
|
@@ -34838,9 +34850,7 @@ async function buildContextClient(pkg, rootDir, client, cache, noCache) {
|
|
|
34838
34850
|
console.log(` building: ${pkg.name} (${client.name})`);
|
|
34839
34851
|
const peerDeps = Object.keys(pkg.packageJson.peerDependencies ?? {});
|
|
34840
34852
|
const allDeps = pkg.packageJson.dependencies ?? {};
|
|
34841
|
-
const
|
|
34842
|
-
const workspaceDeps = isBrowser2 ? Object.keys(allDeps) : Object.entries(allDeps).filter(([, spec]) => !spec.startsWith("workspace:")).map(([name]) => name);
|
|
34843
|
-
const externals = [...peerDeps, ...workspaceDeps];
|
|
34853
|
+
const externals = [...peerDeps, ...Object.keys(allDeps)];
|
|
34844
34854
|
const result = await Bun.build({
|
|
34845
34855
|
entrypoints: [pkg.entrypoint],
|
|
34846
34856
|
outdir: join8(outDir, "main"),
|
|
@@ -34848,7 +34858,7 @@ async function buildContextClient(pkg, rootDir, client, cache, noCache) {
|
|
|
34848
34858
|
format: "esm",
|
|
34849
34859
|
naming: "index.[ext]",
|
|
34850
34860
|
external: externals,
|
|
34851
|
-
plugins:
|
|
34861
|
+
plugins: [jsxDevShimPlugin()],
|
|
34852
34862
|
define: client.defines
|
|
34853
34863
|
});
|
|
34854
34864
|
if (!result.success) {
|
|
@@ -34911,6 +34921,80 @@ async function buildContextPackages(rootDir, packages, cache, noCache) {
|
|
|
34911
34921
|
}
|
|
34912
34922
|
return { declarationErrors };
|
|
34913
34923
|
}
|
|
34924
|
+
async function buildServerApp(rootDir, serverDir, packages, cache, noCache) {
|
|
34925
|
+
const contexts = packages.filter((p) => isContextPackage(p.packageJson));
|
|
34926
|
+
mkdirSync6(serverDir, { recursive: true });
|
|
34927
|
+
const srcByName = new Map(packages.map((p) => [p.name, p.entrypoint]));
|
|
34928
|
+
const externalSet = new Set(FRAMEWORK_PEERS);
|
|
34929
|
+
for (const p of packages) {
|
|
34930
|
+
for (const name of Object.keys(p.packageJson.peerDependencies ?? {})) {
|
|
34931
|
+
externalSet.add(name);
|
|
34932
|
+
}
|
|
34933
|
+
for (const [name, spec] of Object.entries(p.packageJson.dependencies ?? {})) {
|
|
34934
|
+
if (!spec.startsWith("workspace:"))
|
|
34935
|
+
externalSet.add(name);
|
|
34936
|
+
}
|
|
34937
|
+
}
|
|
34938
|
+
const external = [...externalSet];
|
|
34939
|
+
const unitId = "server-app";
|
|
34940
|
+
const inputHash = sha256OfJson({
|
|
34941
|
+
members: packages.map((p) => ({ name: p.name, src: pkgSourceHash(p) })).sort((a, b) => a.name.localeCompare(b.name)),
|
|
34942
|
+
contexts: contexts.map((p) => p.name).sort(),
|
|
34943
|
+
external: [...external].sort(),
|
|
34944
|
+
defines: SERVER_DEFINES
|
|
34945
|
+
});
|
|
34946
|
+
const entryFileAbs = join8(serverDir, SERVER_ENTRY_FILE);
|
|
34947
|
+
if (!noCache && isCacheHit(cache, unitId, inputHash, [entryFileAbs])) {
|
|
34948
|
+
console.log(` \u2713 cached: ${unitId}`);
|
|
34949
|
+
return { entryFile: SERVER_ENTRY_FILE, cached: true };
|
|
34950
|
+
}
|
|
34951
|
+
console.log(` building: ${unitId} (${contexts.length} server modules)`);
|
|
34952
|
+
for (const f of readdirSync4(serverDir)) {
|
|
34953
|
+
if (f.endsWith(".js"))
|
|
34954
|
+
rmSync(join8(serverDir, f), { force: true });
|
|
34955
|
+
}
|
|
34956
|
+
const tmpDir = join8(serverDir, "_entries");
|
|
34957
|
+
mkdirSync6(tmpDir, { recursive: true });
|
|
34958
|
+
const entrySrc = join8(tmpDir, SERVER_ENTRY_FILE.replace(/\.js$/, ".ts"));
|
|
34959
|
+
writeFileSync6(entrySrc, contexts.map((p) => `import "${p.name}";`).join(`
|
|
34960
|
+
`) + `
|
|
34961
|
+
`);
|
|
34962
|
+
let result;
|
|
34963
|
+
try {
|
|
34964
|
+
result = await Bun.build({
|
|
34965
|
+
entrypoints: [entrySrc],
|
|
34966
|
+
outdir: serverDir,
|
|
34967
|
+
target: "bun",
|
|
34968
|
+
format: "esm",
|
|
34969
|
+
splitting: true,
|
|
34970
|
+
naming: "[name].[ext]",
|
|
34971
|
+
external,
|
|
34972
|
+
plugins: [
|
|
34973
|
+
jsxDevShimPlugin(),
|
|
34974
|
+
serverExternalsPlugin(),
|
|
34975
|
+
workspaceSourcePlugin(srcByName)
|
|
34976
|
+
],
|
|
34977
|
+
define: SERVER_DEFINES
|
|
34978
|
+
});
|
|
34979
|
+
} finally {
|
|
34980
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
34981
|
+
}
|
|
34982
|
+
if (!result.success) {
|
|
34983
|
+
for (const log2 of result.logs)
|
|
34984
|
+
console.error(log2);
|
|
34985
|
+
throw new Error("Server app build failed");
|
|
34986
|
+
}
|
|
34987
|
+
const entryOut = result.outputs.find((o) => o.kind === "entry-point");
|
|
34988
|
+
if (!entryOut) {
|
|
34989
|
+
throw new Error("Server app build: entry not found in outputs");
|
|
34990
|
+
}
|
|
34991
|
+
if (basename2(entryOut.path) !== SERVER_ENTRY_FILE) {
|
|
34992
|
+
throw new Error(`Server app build: unexpected entry name ${basename2(entryOut.path)} (wanted ${SERVER_ENTRY_FILE})`);
|
|
34993
|
+
}
|
|
34994
|
+
const outputHash = sha256OfDir(serverDir);
|
|
34995
|
+
updateCache(cache, unitId, inputHash, { outputHash });
|
|
34996
|
+
return { entryFile: SERVER_ENTRY_FILE, cached: false };
|
|
34997
|
+
}
|
|
34914
34998
|
async function buildBrowserApp(rootDir, outDir, plan, cache, noCache, i18nCollector) {
|
|
34915
34999
|
mkdirSync6(outDir, { recursive: true });
|
|
34916
35000
|
const publicMembers = plan.groups.get("public") ?? [];
|
|
@@ -35216,11 +35300,8 @@ import {
|
|
|
35216
35300
|
writeFileSync as writeFileSync7
|
|
35217
35301
|
} from "fs";
|
|
35218
35302
|
import { join as join9 } from "path";
|
|
35219
|
-
async function extractAccessMap(rootDir,
|
|
35220
|
-
const serverBundles =
|
|
35221
|
-
name: p.name,
|
|
35222
|
-
path: join9(p.path, "dist", "server", "main", "index.js")
|
|
35223
|
-
})).filter((b) => existsSync8(b.path));
|
|
35303
|
+
async function extractAccessMap(rootDir, serverBundlePath) {
|
|
35304
|
+
const serverBundles = existsSync8(serverBundlePath) ? [{ name: "server", path: serverBundlePath }] : [];
|
|
35224
35305
|
const workerDir = join9(rootDir, ".arc", ".tmp");
|
|
35225
35306
|
mkdirSync7(workerDir, { recursive: true });
|
|
35226
35307
|
const workerPath = join9(workerDir, `access-extractor-${Date.now()}.mjs`);
|
|
@@ -35549,8 +35630,9 @@ async function buildAll(ws, opts = {}) {
|
|
|
35549
35630
|
log2(`Building (concurrency parallel${noCache ? ", no-cache" : ""})...`);
|
|
35550
35631
|
assertOneModulePerPackage(ws.packages);
|
|
35551
35632
|
await buildContextPackages(ws.rootDir, ws.packages, cache, noCache);
|
|
35552
|
-
|
|
35553
|
-
const
|
|
35633
|
+
const serverDir = join12(ws.arcDir, "server");
|
|
35634
|
+
const { entryFile: serverEntry } = await buildServerApp(ws.rootDir, serverDir, ws.packages, cache, noCache);
|
|
35635
|
+
const accessMap = await extractAccessMap(ws.rootDir, join12(serverDir, serverEntry));
|
|
35554
35636
|
mkdirSync9(ws.arcDir, { recursive: true });
|
|
35555
35637
|
writeFileSync9(join12(ws.arcDir, "access.json"), JSON.stringify(accessMap, null, 2) + `
|
|
35556
35638
|
`);
|
|
@@ -35581,22 +35663,6 @@ function assembleManifest(ws, browser, cache) {
|
|
|
35581
35663
|
buildTime: new Date().toISOString()
|
|
35582
35664
|
};
|
|
35583
35665
|
}
|
|
35584
|
-
function copyContextServerBundles(ws) {
|
|
35585
|
-
const outDir = join12(ws.arcDir, "server");
|
|
35586
|
-
mkdirSync9(outDir, { recursive: true });
|
|
35587
|
-
for (const pkg of ws.packages) {
|
|
35588
|
-
if (!isContextPackage(pkg.packageJson))
|
|
35589
|
-
continue;
|
|
35590
|
-
const src = join12(pkg.path, "dist", "server", "main", "index.js");
|
|
35591
|
-
if (!existsSync10(src)) {
|
|
35592
|
-
err(`Server bundle missing for ${pkg.name}: ${src}`);
|
|
35593
|
-
continue;
|
|
35594
|
-
}
|
|
35595
|
-
const safeName = pkg.path.split("/").pop();
|
|
35596
|
-
const dst = join12(outDir, `${safeName}.js`);
|
|
35597
|
-
copyFileSync(src, dst);
|
|
35598
|
-
}
|
|
35599
|
-
}
|
|
35600
35666
|
function resolveAssetSource(from, pkgDir, rootDir) {
|
|
35601
35667
|
if (from.startsWith("./") || from.startsWith("../")) {
|
|
35602
35668
|
const resolved = join12(pkgDir, from);
|
|
@@ -35692,34 +35758,15 @@ async function loadServerContext(ws) {
|
|
|
35692
35758
|
const platformPkg = JSON.parse(readFileSync11(join12(platformDir, "package.json"), "utf-8"));
|
|
35693
35759
|
const platformEntry = join12(platformDir, platformPkg.main ?? "src/index.ts");
|
|
35694
35760
|
await import(platformEntry);
|
|
35695
|
-
const
|
|
35696
|
-
|
|
35697
|
-
if (bundles.length > 0) {
|
|
35698
|
-
for (const file of bundles) {
|
|
35699
|
-
const bundlePath = join12(serverDir, file);
|
|
35700
|
-
try {
|
|
35701
|
-
await import(bundlePath);
|
|
35702
|
-
} catch (e) {
|
|
35703
|
-
err(`Failed to load server bundle ${file}: ${e}`);
|
|
35704
|
-
}
|
|
35705
|
-
}
|
|
35706
|
-
} else if (ws.packages.length > 0) {
|
|
35707
|
-
const ctxPackages = ws.packages.filter((p) => isContextPackage(p.packageJson));
|
|
35708
|
-
for (const ctx of ctxPackages) {
|
|
35709
|
-
const serverDist = join12(ctx.path, "dist", "server", "main", "index.js");
|
|
35710
|
-
if (!existsSync10(serverDist)) {
|
|
35711
|
-
err(`Context server dist not found: ${serverDist}`);
|
|
35712
|
-
continue;
|
|
35713
|
-
}
|
|
35714
|
-
try {
|
|
35715
|
-
await import(serverDist);
|
|
35716
|
-
} catch (e) {
|
|
35717
|
-
err(`Failed to load server context from ${ctx.name}: ${e}`);
|
|
35718
|
-
}
|
|
35719
|
-
}
|
|
35720
|
-
} else {
|
|
35761
|
+
const serverEntry = join12(ws.arcDir, "server", SERVER_ENTRY_FILE);
|
|
35762
|
+
if (!existsSync10(serverEntry)) {
|
|
35721
35763
|
return { context: null, moduleAccess: new Map };
|
|
35722
35764
|
}
|
|
35765
|
+
try {
|
|
35766
|
+
await import(serverEntry);
|
|
35767
|
+
} catch (e) {
|
|
35768
|
+
err(`Failed to load server bundle ${SERVER_ENTRY_FILE}: ${e}`);
|
|
35769
|
+
}
|
|
35723
35770
|
const { getContext, getAllModuleAccess } = await import(platformEntry);
|
|
35724
35771
|
return {
|
|
35725
35772
|
context: getContext() ?? null,
|
|
@@ -36553,8 +36600,13 @@ ${envNames.map((name) => ` - "https://${cfg.envs[name].domain}"`).joi
|
|
|
36553
36600
|
|
|
36554
36601
|
# Per-container CPU / memory / network / block-IO + restarts straight from
|
|
36555
36602
|
# the Docker daemon (socket bind-mounted read-only, see compose).
|
|
36603
|
+
# api_version pinned: the receiver defaults to Docker API 1.25, which modern
|
|
36604
|
+
# daemons (Engine 25+ require >= 1.40) reject \u2014 without this the receiver
|
|
36605
|
+
# fails to start and takes the whole collector down. Quoted so YAML doesn't
|
|
36606
|
+
# parse 1.40 \u2192 1.4. Must be <= the daemon's max; 1.40 is the safe floor.
|
|
36556
36607
|
docker_stats:
|
|
36557
36608
|
endpoint: unix:///var/run/docker.sock
|
|
36609
|
+
api_version: "1.40"
|
|
36558
36610
|
collection_interval: 30s
|
|
36559
36611
|
metrics:
|
|
36560
36612
|
container.restarts:
|
|
@@ -36824,6 +36876,15 @@ function generateAlloyConfig() {
|
|
|
36824
36876
|
discovery.docker "containers" {
|
|
36825
36877
|
host = "unix:///var/run/docker.sock"
|
|
36826
36878
|
refresh_interval = "15s"
|
|
36879
|
+
|
|
36880
|
+
// Only containers managed by a compose project (our stack). Ad-hoc / rogue
|
|
36881
|
+
// containers (manual debug runs, other stacks) are excluded \u2014 one bad
|
|
36882
|
+
// stream (e.g. log entries older than Loki's reject window) otherwise 400s
|
|
36883
|
+
// the whole loki.write batch and drops good app logs with it.
|
|
36884
|
+
filter {
|
|
36885
|
+
name = "label"
|
|
36886
|
+
values = ["com.docker.compose.project"]
|
|
36887
|
+
}
|
|
36827
36888
|
}
|
|
36828
36889
|
|
|
36829
36890
|
discovery.relabel "containers" {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arcote.tech/arc-cli",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.21",
|
|
4
4
|
"description": "CLI tool for Arc framework",
|
|
5
5
|
"module": "index.ts",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -12,13 +12,13 @@
|
|
|
12
12
|
"build": "bun build --target=bun ./src/index.ts --outdir=dist --external @arcote.tech/arc --external @arcote.tech/arc-ds --external @arcote.tech/arc-react --external @arcote.tech/platform --external '@opentelemetry/*' && chmod +x dist/index.js"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@arcote.tech/arc": "^0.7.
|
|
16
|
-
"@arcote.tech/arc-ds": "^0.7.
|
|
17
|
-
"@arcote.tech/arc-react": "^0.7.
|
|
18
|
-
"@arcote.tech/arc-host": "^0.7.
|
|
19
|
-
"@arcote.tech/arc-adapter-db-sqlite": "^0.7.
|
|
20
|
-
"@arcote.tech/arc-adapter-db-postgres": "^0.7.
|
|
21
|
-
"@arcote.tech/arc-otel": "^0.7.
|
|
15
|
+
"@arcote.tech/arc": "^0.7.21",
|
|
16
|
+
"@arcote.tech/arc-ds": "^0.7.21",
|
|
17
|
+
"@arcote.tech/arc-react": "^0.7.21",
|
|
18
|
+
"@arcote.tech/arc-host": "^0.7.21",
|
|
19
|
+
"@arcote.tech/arc-adapter-db-sqlite": "^0.7.21",
|
|
20
|
+
"@arcote.tech/arc-adapter-db-postgres": "^0.7.21",
|
|
21
|
+
"@arcote.tech/arc-otel": "^0.7.21",
|
|
22
22
|
"@opentelemetry/api": "^1.9.0",
|
|
23
23
|
"@opentelemetry/api-logs": "^0.57.0",
|
|
24
24
|
"@opentelemetry/core": "^1.30.0",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"@opentelemetry/sdk-trace-base": "^1.30.0",
|
|
32
32
|
"@opentelemetry/sdk-trace-node": "^1.30.0",
|
|
33
33
|
"@opentelemetry/semantic-conventions": "^1.27.0",
|
|
34
|
-
"@arcote.tech/platform": "^0.7.
|
|
34
|
+
"@arcote.tech/platform": "^0.7.21",
|
|
35
35
|
"@clack/prompts": "^0.9.0",
|
|
36
36
|
"commander": "^11.1.0",
|
|
37
37
|
"chokidar": "^3.5.3",
|
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
writeFileSync,
|
|
8
8
|
} from "fs";
|
|
9
9
|
import { join } from "path";
|
|
10
|
-
import { isContextPackage, type WorkspacePackage } from "./module-builder";
|
|
11
10
|
|
|
12
11
|
// ---------------------------------------------------------------------------
|
|
13
12
|
// access-extractor — discovers per-module access rules (`protectedBy(...)`)
|
|
@@ -27,8 +26,8 @@ import { isContextPackage, type WorkspacePackage } from "./module-builder";
|
|
|
27
26
|
// non-context (pure-browser) packages are skipped. This is sensible: access
|
|
28
27
|
// checks logically belong with server state, not display components.
|
|
29
28
|
//
|
|
30
|
-
// Order requirement:
|
|
31
|
-
//
|
|
29
|
+
// Order requirement: buildServerApp MUST run before extractAccessMap — the
|
|
30
|
+
// combined server bundle at `<arcDir>/server/_server.js` must exist.
|
|
32
31
|
// ---------------------------------------------------------------------------
|
|
33
32
|
|
|
34
33
|
export interface SerializedAccessRule {
|
|
@@ -44,19 +43,16 @@ export type SerializedAccessMap = Record<string, SerializedModuleAccess>;
|
|
|
44
43
|
|
|
45
44
|
export async function extractAccessMap(
|
|
46
45
|
rootDir: string,
|
|
47
|
-
|
|
46
|
+
serverBundlePath: string,
|
|
48
47
|
): Promise<SerializedAccessMap> {
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
path: join(p.path, "dist", "server", "main", "index.js"),
|
|
58
|
-
}))
|
|
59
|
-
.filter((b) => existsSync(b.path));
|
|
48
|
+
// The combined server bundle (`<arcDir>/server/_server.js`) side-effect-
|
|
49
|
+
// registers every module via the platform singleton when imported. The
|
|
50
|
+
// worker imports just this entry; Bun resolves its `chunk-<hash>.js` siblings
|
|
51
|
+
// and external peers (`@arcote.tech/platform` etc.) by walking node_modules
|
|
52
|
+
// from the bundle's directory.
|
|
53
|
+
const serverBundles = existsSync(serverBundlePath)
|
|
54
|
+
? [{ name: "server", path: serverBundlePath }]
|
|
55
|
+
: [];
|
|
60
56
|
|
|
61
57
|
// Worker must live INSIDE the workspace tree so Bun's module resolver can
|
|
62
58
|
// walk up to <rootDir>/node_modules and find @arcote.tech/platform via the
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
type BuildCache,
|
|
19
19
|
} from "./build-cache";
|
|
20
20
|
import type { ChunkPlan } from "./chunk-planner";
|
|
21
|
-
import { SHELL_EXTERNALS } from "./framework-peers";
|
|
21
|
+
import { SHELL_EXTERNALS, FRAMEWORK_PEERS } from "./framework-peers";
|
|
22
22
|
import {
|
|
23
23
|
readInstalledVersion,
|
|
24
24
|
sha256Hex,
|
|
@@ -125,6 +125,43 @@ function serverExternalsPlugin(): import("bun").BunPlugin {
|
|
|
125
125
|
};
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
/**
|
|
129
|
+
* SERVER bundles inline their workspace (`@ndt/*`) deps so each flattened
|
|
130
|
+
* `.arc/platform/server/<pkg>.js` is self-contained (the deploy image has no
|
|
131
|
+
* `node_modules/@ndt/*` tree). Bun would resolve a bare `@ndt/x` import through
|
|
132
|
+
* package.json `exports` to that dep's PRE-BUILT `dist/server/main/index.js` —
|
|
133
|
+
* an already-fully-inlined bundle. Nesting one fully-inlined bundle inside
|
|
134
|
+
* every consumer is catastrophic:
|
|
135
|
+
* - diamonds duplicate the shared dep per graph edge (2^depth copies),
|
|
136
|
+
* - dist/ is never wiped, so stale bombs from a previous dep graph keep
|
|
137
|
+
* getting re-inlined across rebuilds.
|
|
138
|
+
* Result: bundles ballooned into the hundreds-of-MB / GB range (content =
|
|
139
|
+
* 1.1 GB, sizes lining up as powers of two by graph depth).
|
|
140
|
+
*
|
|
141
|
+
* This plugin pins every workspace package name to its SOURCE entrypoint
|
|
142
|
+
* (`src/index.ts`). The single Bun.build pass then includes each transitive
|
|
143
|
+
* module exactly once (Bun dedups by resolved path), so a server bundle is the
|
|
144
|
+
* size of its UNIQUE source closure — no nesting, no stale re-inlining.
|
|
145
|
+
*
|
|
146
|
+
* `sideEffects: true` keeps the dep's top-level `module(...).build()`
|
|
147
|
+
* registration from being tree-shaken when it's imported only for a named
|
|
148
|
+
* symbol (workspace packages ship `"sideEffects": false`).
|
|
149
|
+
*/
|
|
150
|
+
function workspaceSourcePlugin(srcByName: Map<string, string>): import("bun").BunPlugin {
|
|
151
|
+
return {
|
|
152
|
+
name: "workspace-source",
|
|
153
|
+
setup(build) {
|
|
154
|
+
// Bare specifiers only (skip relative `./` and absolute `/`). Non-
|
|
155
|
+
// workspace bare imports (react, @arcote.tech/*, npm deps) miss the map
|
|
156
|
+
// and fall through to the `external` list unchanged.
|
|
157
|
+
build.onResolve({ filter: /^[^./]/ }, (args) => {
|
|
158
|
+
const src = srcByName.get(args.path);
|
|
159
|
+
return src ? { path: src, sideEffects: true } : null;
|
|
160
|
+
});
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
128
165
|
function jsxDevShimPlugin(): import("bun").BunPlugin {
|
|
129
166
|
return {
|
|
130
167
|
name: "jsx-dev-runtime-shim",
|
|
@@ -145,12 +182,25 @@ export { Fragment };
|
|
|
145
182
|
};
|
|
146
183
|
}
|
|
147
184
|
|
|
148
|
-
|
|
185
|
+
// Per-package context build is BROWSER-only. The server side is produced by a
|
|
186
|
+
// single combined `buildServerApp` pass (see below) — bundling each package's
|
|
187
|
+
// server context separately and inlining its deps' pre-built dist caused
|
|
188
|
+
// catastrophic nested duplication (content.js hit 1.1 GB) and turned the
|
|
189
|
+
// consumer's undeclared cyclic `@ndt/*` imports into broken init order. One
|
|
190
|
+
// shared pass dedups every module once and is cycle-safe (same as the browser
|
|
191
|
+
// app build).
|
|
149
192
|
const CONTEXT_CLIENTS = [
|
|
150
|
-
{ name: "server", target: "bun" as const, defines: { ONLY_SERVER: "true", ONLY_BROWSER: "false", ONLY_CLIENT: "false" } },
|
|
151
193
|
{ name: "browser", target: "browser" as const, defines: { ONLY_SERVER: "false", ONLY_BROWSER: "true", ONLY_CLIENT: "true" } },
|
|
152
194
|
];
|
|
153
195
|
|
|
196
|
+
/** Server-side build defines — applied by the combined `buildServerApp` pass. */
|
|
197
|
+
const SERVER_DEFINES = { ONLY_SERVER: "true", ONLY_BROWSER: "false", ONLY_CLIENT: "false" } as const;
|
|
198
|
+
|
|
199
|
+
/** Filename of the combined server bundle entry under `<arcDir>/server/`.
|
|
200
|
+
* loadServerContext + the access extractor import exactly this file; Bun's
|
|
201
|
+
* auto-emitted `chunk-<hash>.js` siblings are pulled in transitively. */
|
|
202
|
+
export const SERVER_ENTRY_FILE = "_server.js";
|
|
203
|
+
|
|
154
204
|
export interface WorkspacePackage {
|
|
155
205
|
name: string;
|
|
156
206
|
path: string;
|
|
@@ -305,32 +355,16 @@ async function buildContextClient(
|
|
|
305
355
|
|
|
306
356
|
console.log(` building: ${pkg.name} (${client.name})`);
|
|
307
357
|
|
|
308
|
-
//
|
|
309
|
-
//
|
|
310
|
-
//
|
|
311
|
-
//
|
|
312
|
-
//
|
|
313
|
-
//
|
|
314
|
-
//
|
|
315
|
-
//
|
|
316
|
-
// - SERVER client: workspace deps MUST be bundled inline. The deploy
|
|
317
|
-
// image flattens each package's server bundle to
|
|
318
|
-
// `.arc/platform/server/<pkg>.js` and runs them via a single
|
|
319
|
-
// `loadServerContext()` import loop. There is no `node_modules/@ndt/*`
|
|
320
|
-
// tree inside the image, so bare `@ndt/workspace` specifiers would
|
|
321
|
-
// fail to resolve at startup. Inline duplication is harmless on the
|
|
322
|
-
// server: it's a single Node/Bun process and Arc modules register via
|
|
323
|
-
// a shared platform registry singleton (registry.ts), so two physical
|
|
324
|
-
// copies of the workspace module still merge into one context.
|
|
358
|
+
// BROWSER client: every dep (workspace + npm + framework peers) is external.
|
|
359
|
+
// Inlining workspace deps per package would make each context package's dist
|
|
360
|
+
// ship its own copy of every workspace dep — the top-level browser Bun.build
|
|
361
|
+
// (`buildBrowserApp`) would then see N pre-inlined copies of context
|
|
362
|
+
// singletons (`WorkspaceContext = createContext`) and could not dedupe them,
|
|
363
|
+
// breaking provider lookup. The server side is handled separately by the
|
|
364
|
+
// combined `buildServerApp` pass.
|
|
325
365
|
const peerDeps = Object.keys(pkg.packageJson.peerDependencies ?? {});
|
|
326
366
|
const allDeps = (pkg.packageJson.dependencies ?? {}) as Record<string, string>;
|
|
327
|
-
const
|
|
328
|
-
const workspaceDeps = isBrowser
|
|
329
|
-
? Object.keys(allDeps)
|
|
330
|
-
: Object.entries(allDeps)
|
|
331
|
-
.filter(([, spec]) => !spec.startsWith("workspace:"))
|
|
332
|
-
.map(([name]) => name);
|
|
333
|
-
const externals = [...peerDeps, ...workspaceDeps];
|
|
367
|
+
const externals = [...peerDeps, ...Object.keys(allDeps)];
|
|
334
368
|
|
|
335
369
|
const result = await Bun.build({
|
|
336
370
|
entrypoints: [pkg.entrypoint],
|
|
@@ -339,9 +373,7 @@ async function buildContextClient(
|
|
|
339
373
|
format: "esm",
|
|
340
374
|
naming: "index.[ext]",
|
|
341
375
|
external: externals,
|
|
342
|
-
plugins:
|
|
343
|
-
? [jsxDevShimPlugin()]
|
|
344
|
-
: [jsxDevShimPlugin(), serverExternalsPlugin()],
|
|
376
|
+
plugins: [jsxDevShimPlugin()],
|
|
345
377
|
define: client.defines,
|
|
346
378
|
});
|
|
347
379
|
|
|
@@ -385,11 +417,13 @@ export async function buildContextPackages(
|
|
|
385
417
|
const contexts = packages.filter((p) => isContextPackage(p.packageJson));
|
|
386
418
|
if (contexts.length === 0) return { declarationErrors: [] };
|
|
387
419
|
|
|
388
|
-
// Topological order
|
|
389
|
-
//
|
|
390
|
-
//
|
|
391
|
-
//
|
|
392
|
-
//
|
|
420
|
+
// Topological order by declared `workspace:` deps. The browser BUNDLE
|
|
421
|
+
// externalizes every dep (order-independent), but the type-declaration pass
|
|
422
|
+
// (tsc) resolves `@ndt/x` types through that package's emitted
|
|
423
|
+
// `dist/browser/index.d.ts` — so a dep's declarations must exist before a
|
|
424
|
+
// dependent's decl build runs, else tsc reports "Cannot find module @ndt/x".
|
|
425
|
+
// The declared dep graph is acyclic; undeclared cyclic imports live in source
|
|
426
|
+
// only and don't affect this ordering. Inside a layer, packages build in parallel.
|
|
393
427
|
const byName = new Map(contexts.map((p) => [p.name, p]));
|
|
394
428
|
const remaining = new Set(contexts.map((p) => p.name));
|
|
395
429
|
const done = new Set<string>();
|
|
@@ -440,6 +474,147 @@ export async function buildContextPackages(
|
|
|
440
474
|
return { declarationErrors };
|
|
441
475
|
}
|
|
442
476
|
|
|
477
|
+
// ---------------------------------------------------------------------------
|
|
478
|
+
// Combined server bundle — ONE Bun.build for ALL context packages' server side.
|
|
479
|
+
//
|
|
480
|
+
// Replaces N per-package server builds that each inlined their deps' PRE-BUILT
|
|
481
|
+
// dist (`@ndt/x` → `x/dist/server/main/index.js`). That nested an already-
|
|
482
|
+
// fully-inlined bundle inside every consumer: shared deps duplicated per graph
|
|
483
|
+
// edge (content's server bundle reached 1.1 GB) and the consumer's undeclared
|
|
484
|
+
// cyclic `@ndt/*` imports turned into broken module-init order.
|
|
485
|
+
//
|
|
486
|
+
// One shared pass with `splitting: true` — the same recipe as buildBrowserApp:
|
|
487
|
+
// - every module is bundled from SOURCE exactly once (Bun dedups by path),
|
|
488
|
+
// - `@ndt/*` workspace deps resolve to their `src/` entry (workspaceSourcePlugin)
|
|
489
|
+
// so nothing pulls a pre-built dist,
|
|
490
|
+
// - npm deps + framework peers stay external — the deploy image installs them
|
|
491
|
+
// into /app/node_modules via `.arc/platform/package.json` (collectFrameworkDeps),
|
|
492
|
+
// - splitting makes Bun emit eager top-level init, which is cycle-safe; the
|
|
493
|
+
// old non-split per-package build emitted lazy `__esm` wrappers that broke
|
|
494
|
+
// under the consumer's import cycles (`workspace.ids` was undefined).
|
|
495
|
+
//
|
|
496
|
+
// Output under `<arcDir>/server/`:
|
|
497
|
+
// _server.js ← entry: side-effect-imports every context module
|
|
498
|
+
// chunk-<hash>.js × N ← auto-shared module chunks
|
|
499
|
+
// loadServerContext + the access extractor import `_server.js`; chunks ride along.
|
|
500
|
+
// ---------------------------------------------------------------------------
|
|
501
|
+
|
|
502
|
+
export interface ServerAppResult {
|
|
503
|
+
readonly entryFile: string;
|
|
504
|
+
readonly cached: boolean;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
export async function buildServerApp(
|
|
508
|
+
rootDir: string,
|
|
509
|
+
serverDir: string,
|
|
510
|
+
packages: WorkspacePackage[],
|
|
511
|
+
cache: BuildCache,
|
|
512
|
+
noCache: boolean,
|
|
513
|
+
): Promise<ServerAppResult> {
|
|
514
|
+
void rootDir; // resolution is source-based; rootDir kept for signature parity
|
|
515
|
+
const contexts = packages.filter((p) => isContextPackage(p.packageJson));
|
|
516
|
+
mkdirSync(serverDir, { recursive: true });
|
|
517
|
+
|
|
518
|
+
// Every workspace package name → its SOURCE entry. Spans ALL packages, not
|
|
519
|
+
// just context ones — a context package imports non-context workspace libs
|
|
520
|
+
// (e.g. content-core) that must inline from source too.
|
|
521
|
+
const srcByName = new Map(packages.map((p) => [p.name, p.entrypoint]));
|
|
522
|
+
|
|
523
|
+
// External = framework peers + every non-workspace npm dep any package
|
|
524
|
+
// declares. NOT bundled; resolved at runtime from /app/node_modules (the
|
|
525
|
+
// deploy image installs exactly this set — collectFrameworkDeps). Only the
|
|
526
|
+
// `@ndt/*` workspace source is inlined.
|
|
527
|
+
const externalSet = new Set<string>(FRAMEWORK_PEERS);
|
|
528
|
+
for (const p of packages) {
|
|
529
|
+
for (const name of Object.keys(p.packageJson.peerDependencies ?? {})) {
|
|
530
|
+
externalSet.add(name);
|
|
531
|
+
}
|
|
532
|
+
for (const [name, spec] of Object.entries(
|
|
533
|
+
(p.packageJson.dependencies ?? {}) as Record<string, string>,
|
|
534
|
+
)) {
|
|
535
|
+
if (!spec.startsWith("workspace:")) externalSet.add(name);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
const external = [...externalSet];
|
|
539
|
+
|
|
540
|
+
const unitId = "server-app";
|
|
541
|
+
const inputHash = sha256OfJson({
|
|
542
|
+
members: packages
|
|
543
|
+
.map((p) => ({ name: p.name, src: pkgSourceHash(p) }))
|
|
544
|
+
.sort((a, b) => a.name.localeCompare(b.name)),
|
|
545
|
+
contexts: contexts.map((p) => p.name).sort(),
|
|
546
|
+
external: [...external].sort(),
|
|
547
|
+
defines: SERVER_DEFINES,
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
const entryFileAbs = join(serverDir, SERVER_ENTRY_FILE);
|
|
551
|
+
if (!noCache && isCacheHit(cache, unitId, inputHash, [entryFileAbs])) {
|
|
552
|
+
console.log(` ✓ cached: ${unitId}`);
|
|
553
|
+
return { entryFile: SERVER_ENTRY_FILE, cached: true };
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
console.log(` building: ${unitId} (${contexts.length} server modules)`);
|
|
557
|
+
|
|
558
|
+
// Wipe stale .js — old per-package flattened bundles AND previous chunks —
|
|
559
|
+
// so a smaller rebuild never leaves orphaned content-addressed chunks behind.
|
|
560
|
+
for (const f of readdirSync(serverDir)) {
|
|
561
|
+
if (f.endsWith(".js")) rmSync(join(serverDir, f), { force: true });
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Entry side-effect-imports every context module so each registers via the
|
|
565
|
+
// platform registry singleton. Written to a tmp subdir so it isn't shipped.
|
|
566
|
+
const tmpDir = join(serverDir, "_entries");
|
|
567
|
+
mkdirSync(tmpDir, { recursive: true });
|
|
568
|
+
const entrySrc = join(tmpDir, SERVER_ENTRY_FILE.replace(/\.js$/, ".ts"));
|
|
569
|
+
writeFileSync(
|
|
570
|
+
entrySrc,
|
|
571
|
+
contexts.map((p) => `import "${p.name}";`).join("\n") + "\n",
|
|
572
|
+
);
|
|
573
|
+
|
|
574
|
+
let result;
|
|
575
|
+
try {
|
|
576
|
+
result = await Bun.build({
|
|
577
|
+
entrypoints: [entrySrc],
|
|
578
|
+
outdir: serverDir,
|
|
579
|
+
target: "bun",
|
|
580
|
+
format: "esm",
|
|
581
|
+
// splitting:true is the whole point — it makes Bun emit eager top-level
|
|
582
|
+
// init (cycle-safe) instead of lazy `__esm` wrappers. Shared modules land
|
|
583
|
+
// in chunk-<hash>.js referenced by the entry.
|
|
584
|
+
splitting: true,
|
|
585
|
+
naming: "[name].[ext]",
|
|
586
|
+
external,
|
|
587
|
+
plugins: [
|
|
588
|
+
jsxDevShimPlugin(),
|
|
589
|
+
serverExternalsPlugin(),
|
|
590
|
+
workspaceSourcePlugin(srcByName),
|
|
591
|
+
],
|
|
592
|
+
define: SERVER_DEFINES,
|
|
593
|
+
});
|
|
594
|
+
} finally {
|
|
595
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
if (!result.success) {
|
|
599
|
+
for (const log of result.logs) console.error(log);
|
|
600
|
+
throw new Error("Server app build failed");
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
const entryOut = result.outputs.find((o) => o.kind === "entry-point");
|
|
604
|
+
if (!entryOut) {
|
|
605
|
+
throw new Error("Server app build: entry not found in outputs");
|
|
606
|
+
}
|
|
607
|
+
if (basename(entryOut.path) !== SERVER_ENTRY_FILE) {
|
|
608
|
+
throw new Error(
|
|
609
|
+
`Server app build: unexpected entry name ${basename(entryOut.path)} (wanted ${SERVER_ENTRY_FILE})`,
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
const outputHash = sha256OfDir(serverDir);
|
|
614
|
+
updateCache(cache, unitId, inputHash, { outputHash });
|
|
615
|
+
return { entryFile: SERVER_ENTRY_FILE, cached: false };
|
|
616
|
+
}
|
|
617
|
+
|
|
443
618
|
|
|
444
619
|
// ---------------------------------------------------------------------------
|
|
445
620
|
// Browser app build — one Bun.build with multiple entrypoints.
|
|
@@ -87,8 +87,13 @@ ${envNames.map((name) => ` - "https://${cfg.envs[name]!.domain}"`).jo
|
|
|
87
87
|
|
|
88
88
|
# Per-container CPU / memory / network / block-IO + restarts straight from
|
|
89
89
|
# the Docker daemon (socket bind-mounted read-only, see compose).
|
|
90
|
+
# api_version pinned: the receiver defaults to Docker API 1.25, which modern
|
|
91
|
+
# daemons (Engine 25+ require >= 1.40) reject — without this the receiver
|
|
92
|
+
# fails to start and takes the whole collector down. Quoted so YAML doesn't
|
|
93
|
+
# parse 1.40 → 1.4. Must be <= the daemon's max; 1.40 is the safe floor.
|
|
90
94
|
docker_stats:
|
|
91
95
|
endpoint: unix:///var/run/docker.sock
|
|
96
|
+
api_version: "1.40"
|
|
92
97
|
collection_interval: 30s
|
|
93
98
|
metrics:
|
|
94
99
|
container.restarts:
|
|
@@ -372,6 +377,15 @@ export function generateAlloyConfig(): string {
|
|
|
372
377
|
discovery.docker "containers" {
|
|
373
378
|
host = "unix:///var/run/docker.sock"
|
|
374
379
|
refresh_interval = "15s"
|
|
380
|
+
|
|
381
|
+
// Only containers managed by a compose project (our stack). Ad-hoc / rogue
|
|
382
|
+
// containers (manual debug runs, other stacks) are excluded — one bad
|
|
383
|
+
// stream (e.g. log entries older than Loki's reject window) otherwise 400s
|
|
384
|
+
// the whole loki.write batch and drops good app logs with it.
|
|
385
|
+
filter {
|
|
386
|
+
name = "label"
|
|
387
|
+
values = ["com.docker.compose.project"]
|
|
388
|
+
}
|
|
375
389
|
}
|
|
376
390
|
|
|
377
391
|
discovery.relabel "containers" {
|
package/src/platform/shared.ts
CHANGED
|
@@ -4,10 +4,12 @@ import { dirname, join } from "path";
|
|
|
4
4
|
import {
|
|
5
5
|
buildBrowserApp,
|
|
6
6
|
buildContextPackages,
|
|
7
|
+
buildServerApp,
|
|
7
8
|
buildStyles,
|
|
8
9
|
buildTranslations,
|
|
9
10
|
discoverPackages,
|
|
10
11
|
isContextPackage,
|
|
12
|
+
SERVER_ENTRY_FILE,
|
|
11
13
|
type BrowserAppResult,
|
|
12
14
|
type BuildManifest,
|
|
13
15
|
type ModuleDescriptor,
|
|
@@ -161,20 +163,30 @@ export async function buildAll(
|
|
|
161
163
|
// else chunk grouping (per-package) silently mis-assigns the extra module.
|
|
162
164
|
assertOneModulePerPackage(ws.packages);
|
|
163
165
|
|
|
164
|
-
// Phase 1 — context
|
|
165
|
-
// subprocess imports workspace packages by name, which resolve through
|
|
166
|
-
// node_modules to packages' `main` field (typically `dist/server/main/`).
|
|
166
|
+
// Phase 1 — per-package BROWSER context bundles + type declarations.
|
|
167
167
|
await buildContextPackages(ws.rootDir, ws.packages, cache, noCache);
|
|
168
168
|
|
|
169
|
-
// Phase 1b —
|
|
170
|
-
//
|
|
171
|
-
//
|
|
172
|
-
|
|
169
|
+
// Phase 1b — combined server bundle at `.arc/platform/server/_server.js`.
|
|
170
|
+
// ONE Bun.build for all context modules (deduped from source, cycle-safe)
|
|
171
|
+
// replaces the old per-package server bundles that nested each other's dist
|
|
172
|
+
// into multi-hundred-MB files. The deploy image COPYs this dir wholesale;
|
|
173
|
+
// loadServerContext + the access extractor import the entry, chunks ride along.
|
|
174
|
+
const serverDir = join(ws.arcDir, "server");
|
|
175
|
+
const { entryFile: serverEntry } = await buildServerApp(
|
|
176
|
+
ws.rootDir,
|
|
177
|
+
serverDir,
|
|
178
|
+
ws.packages,
|
|
179
|
+
cache,
|
|
180
|
+
noCache,
|
|
181
|
+
);
|
|
173
182
|
|
|
174
183
|
// Phase 2 — extract access metadata (token name + hasCheck per module) in
|
|
175
|
-
// an isolated subprocess
|
|
176
|
-
// which token group each module belongs to.
|
|
177
|
-
const accessMap = await extractAccessMap(
|
|
184
|
+
// an isolated subprocess that imports the combined server bundle. MUST run
|
|
185
|
+
// before chunk planning so we know which token group each module belongs to.
|
|
186
|
+
const accessMap = await extractAccessMap(
|
|
187
|
+
ws.rootDir,
|
|
188
|
+
join(serverDir, serverEntry),
|
|
189
|
+
);
|
|
178
190
|
|
|
179
191
|
// Persist access map for the runtime host (server.ts reads at startup to
|
|
180
192
|
// wire up moduleAccessMap for filterManifestForTokens / signed URLs).
|
|
@@ -242,34 +254,6 @@ function assembleManifest(
|
|
|
242
254
|
};
|
|
243
255
|
}
|
|
244
256
|
|
|
245
|
-
// ---------------------------------------------------------------------------
|
|
246
|
-
// Context server bundles — flatten to `<arcDir>/server/<safeName>.js`
|
|
247
|
-
// ---------------------------------------------------------------------------
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* Copy each context package's compiled server bundle from
|
|
251
|
-
* `packages/<pkg>/dist/server/main/index.js` to a flat location at
|
|
252
|
-
* `<arcDir>/server/<safeName>.js`. The flat layout makes the deploy image
|
|
253
|
-
* self-contained — `COPY .arc/platform/` pulls everything server-side, no
|
|
254
|
-
* need to drag the entire `packages/` tree into the image.
|
|
255
|
-
*/
|
|
256
|
-
function copyContextServerBundles(ws: WorkspaceInfo): void {
|
|
257
|
-
const outDir = join(ws.arcDir, "server");
|
|
258
|
-
mkdirSync(outDir, { recursive: true });
|
|
259
|
-
|
|
260
|
-
for (const pkg of ws.packages) {
|
|
261
|
-
if (!isContextPackage(pkg.packageJson)) continue;
|
|
262
|
-
const src = join(pkg.path, "dist", "server", "main", "index.js");
|
|
263
|
-
if (!existsSync(src)) {
|
|
264
|
-
err(`Server bundle missing for ${pkg.name}: ${src}`);
|
|
265
|
-
continue;
|
|
266
|
-
}
|
|
267
|
-
const safeName = pkg.path.split("/").pop()!;
|
|
268
|
-
const dst = join(outDir, `${safeName}.js`);
|
|
269
|
-
copyFileSync(src, dst);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
257
|
// ---------------------------------------------------------------------------
|
|
274
258
|
// Browser assets — @arcote.tech/* deps deklarują w `arc.browserAssets` jakie
|
|
275
259
|
// pliki muszą być dostępne w przeglądarce (np. SQLite WASM worker + .wasm).
|
|
@@ -419,44 +403,21 @@ export async function loadServerContext(
|
|
|
419
403
|
|
|
420
404
|
await import(platformEntry);
|
|
421
405
|
|
|
422
|
-
//
|
|
406
|
+
// The combined server bundle lives at `<arcDir>/server/_server.js` (entry)
|
|
407
|
+
// next to its `chunk-<hash>.js` siblings. Importing the entry pulls the
|
|
408
|
+
// chunks transitively and registers every module via the platform singleton.
|
|
423
409
|
// The deploy image only has this directory — there's no workspace `packages/`
|
|
424
|
-
// tree
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
? readdirSync(serverDir).filter((f) => f.endsWith(".js"))
|
|
429
|
-
: [];
|
|
430
|
-
|
|
431
|
-
if (bundles.length > 0) {
|
|
432
|
-
for (const file of bundles) {
|
|
433
|
-
const bundlePath = join(serverDir, file);
|
|
434
|
-
try {
|
|
435
|
-
await import(bundlePath);
|
|
436
|
-
} catch (e) {
|
|
437
|
-
err(`Failed to load server bundle ${file}: ${e}`);
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
} else if (ws.packages.length > 0) {
|
|
441
|
-
// Fallback for the "no .arc/platform/server/ yet" case (e.g. somebody
|
|
442
|
-
// wired up loadServerContext before running the build). This path goes
|
|
443
|
-
// through workspace packages directly — only meaningful in dev.
|
|
444
|
-
const ctxPackages = ws.packages.filter((p) => isContextPackage(p.packageJson));
|
|
445
|
-
for (const ctx of ctxPackages) {
|
|
446
|
-
const serverDist = join(ctx.path, "dist", "server", "main", "index.js");
|
|
447
|
-
if (!existsSync(serverDist)) {
|
|
448
|
-
err(`Context server dist not found: ${serverDist}`);
|
|
449
|
-
continue;
|
|
450
|
-
}
|
|
451
|
-
try {
|
|
452
|
-
await import(serverDist);
|
|
453
|
-
} catch (e) {
|
|
454
|
-
err(`Failed to load server context from ${ctx.name}: ${e}`);
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
} else {
|
|
410
|
+
// tree; dev and prod go through the exact same path.
|
|
411
|
+
const serverEntry = join(ws.arcDir, "server", SERVER_ENTRY_FILE);
|
|
412
|
+
if (!existsSync(serverEntry)) {
|
|
413
|
+
// No build yet (or a static-only workspace) — nothing to register.
|
|
458
414
|
return { context: null, moduleAccess: new Map() };
|
|
459
415
|
}
|
|
416
|
+
try {
|
|
417
|
+
await import(serverEntry);
|
|
418
|
+
} catch (e) {
|
|
419
|
+
err(`Failed to load server bundle ${SERVER_ENTRY_FILE}: ${e}`);
|
|
420
|
+
}
|
|
460
421
|
|
|
461
422
|
const { getContext, getAllModuleAccess } = await import(platformEntry);
|
|
462
423
|
return {
|
package/src/platform/startup.ts
CHANGED
|
@@ -62,8 +62,8 @@ export async function startPlatform(
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
// 2. Server context — same code path in both modes; loadServerContext
|
|
65
|
-
//
|
|
66
|
-
//
|
|
65
|
+
// imports the combined server bundle at .arc/platform/server/_server.js
|
|
66
|
+
// (produced by the buildServerApp step in buildAll).
|
|
67
67
|
log("Loading server context...");
|
|
68
68
|
const { context, moduleAccess } = await loadServerContext(ws);
|
|
69
69
|
if (context) {
|