@arcote.tech/arc-cli 0.8.1 → 0.8.3

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 CHANGED
@@ -31692,6 +31692,52 @@ function workspaceSourcePlugin(srcByName) {
31692
31692
  }
31693
31693
  };
31694
31694
  }
31695
+ var SERVER_ONLY_NODE_BUILTINS = new Set([
31696
+ "crypto",
31697
+ "stream",
31698
+ "zlib",
31699
+ "fs",
31700
+ "fs/promises",
31701
+ "net",
31702
+ "tls",
31703
+ "http",
31704
+ "https",
31705
+ "http2",
31706
+ "dns",
31707
+ "dgram",
31708
+ "child_process",
31709
+ "worker_threads",
31710
+ "cluster",
31711
+ "os",
31712
+ "v8",
31713
+ "vm",
31714
+ "readline",
31715
+ "repl",
31716
+ "tty",
31717
+ "perf_hooks",
31718
+ "inspector",
31719
+ "async_hooks",
31720
+ "diagnostics_channel",
31721
+ "module",
31722
+ "constants"
31723
+ ]);
31724
+ function nodeServerBuiltinStubPlugin() {
31725
+ return {
31726
+ name: "node-server-builtin-stub",
31727
+ setup(build2) {
31728
+ build2.onResolve({ filter: /^(node:)?[a-z_/]+$/ }, (args) => {
31729
+ const bare = args.path.replace(/^node:/, "");
31730
+ if (!SERVER_ONLY_NODE_BUILTINS.has(bare))
31731
+ return;
31732
+ return { path: args.path, namespace: "node-server-stub" };
31733
+ });
31734
+ build2.onLoad({ filter: /.*/, namespace: "node-server-stub" }, (args) => ({
31735
+ contents: `const e=()=>{throw new Error("Node builtin '${args.path}' jest server-only \u2014 niedost\u0119pny w przegl\u0105darce");};` + `module.exports=new Proxy({},{get:(_,p)=>p==="__esModule"?false:e});`,
31736
+ loader: "js"
31737
+ }));
31738
+ }
31739
+ };
31740
+ }
31695
31741
  function jsxDevShimPlugin() {
31696
31742
  return {
31697
31743
  name: "jsx-dev-runtime-shim",
@@ -31813,7 +31859,10 @@ async function buildContextClient(pkg, rootDir, client, cache, noCache) {
31813
31859
  format: "esm",
31814
31860
  naming: "index.[ext]",
31815
31861
  external: externals,
31816
- plugins: [jsxDevShimPlugin()],
31862
+ plugins: [
31863
+ jsxDevShimPlugin(),
31864
+ ...client.target === "browser" ? [nodeServerBuiltinStubPlugin()] : []
31865
+ ],
31817
31866
  define: client.defines
31818
31867
  });
31819
31868
  if (!result.success) {
@@ -31926,7 +31975,6 @@ async function buildServerApp(rootDir, serverDir, packages, cache, noCache) {
31926
31975
  try {
31927
31976
  result = await Bun.build({
31928
31977
  entrypoints: [entrySrc],
31929
- outdir: serverDir,
31930
31978
  target: "bun",
31931
31979
  format: "esm",
31932
31980
  splitting: true,
@@ -31955,6 +32003,9 @@ async function buildServerApp(rootDir, serverDir, packages, cache, noCache) {
31955
32003
  if (basename3(entryOut.path) !== SERVER_ENTRY_FILE) {
31956
32004
  throw new Error(`Server app build: unexpected entry name ${basename3(entryOut.path)} (wanted ${SERVER_ENTRY_FILE})`);
31957
32005
  }
32006
+ for (const out of result.outputs) {
32007
+ writeFileSync7(join9(serverDir, basename3(out.path)), new Uint8Array(await out.arrayBuffer()));
32008
+ }
31958
32009
  const externals = [...recorded].filter(([name]) => !FRAMEWORK_PEER_SET.has(name)).map(([name, version]) => ({ name, version })).sort((a, b) => a.name.localeCompare(b.name));
31959
32010
  writeFileSync7(externalsFileAbs, JSON.stringify(externals, null, 2) + `
31960
32011
  `);
@@ -31972,9 +32023,141 @@ function readServerExternals(path4) {
31972
32023
  return [];
31973
32024
  }
31974
32025
  }
32026
+ function buildStatsEnabled() {
32027
+ return process.env.ARC_BUILD_STATS === "1";
32028
+ }
32029
+ function classifySource(src) {
32030
+ const nm = src.lastIndexOf("node_modules/");
32031
+ if (nm >= 0) {
32032
+ const rest = src.slice(nm + "node_modules/".length);
32033
+ const parts = rest.split("/");
32034
+ const pkg = parts[0].startsWith("@") ? `${parts[0]}/${parts[1]}` : parts[0];
32035
+ const external = !pkg.startsWith("@ndt/") && !pkg.startsWith("@arcote.tech/");
32036
+ return { pkg, external };
32037
+ }
32038
+ const m = src.match(/(?:^|\/)packages\/([^/]+)\//);
32039
+ if (m)
32040
+ return { pkg: `packages/${m[1]}`, external: false };
32041
+ return { pkg: src.split("/").slice(0, 2).join("/") || src, external: false };
32042
+ }
32043
+ function chunkPkgWeights(map) {
32044
+ const acc = new Map;
32045
+ const sources = map.sources ?? [];
32046
+ const contents = map.sourcesContent ?? [];
32047
+ for (let i = 0;i < sources.length; i++) {
32048
+ const bytes = contents[i]?.length ?? 0;
32049
+ const { pkg, external } = classifySource(sources[i]);
32050
+ const cur = acc.get(pkg) ?? { pkg, bytes: 0, external };
32051
+ cur.bytes += bytes;
32052
+ acc.set(pkg, cur);
32053
+ }
32054
+ return [...acc.values()].sort((a, b) => b.bytes - a.bytes);
32055
+ }
32056
+ var kb = (n) => `${Math.round(n / 1024)} KB`;
32057
+ function stripTrailingSourcemapComment(bytes) {
32058
+ let text = Buffer.from(bytes).toString("utf8");
32059
+ text = text.replace(/\n*\/\/# (?:debugId|sourceMappingURL)=[^\n]*\s*$/, "");
32060
+ if (!text.endsWith(`
32061
+ `))
32062
+ text += `
32063
+ `;
32064
+ return new Uint8Array(Buffer.from(text, "utf8"));
32065
+ }
32066
+ function reportBrowserBuildStats(opts) {
32067
+ const { label, entries, jsByName, mapsByJs } = opts;
32068
+ const staticEdge = /(?:^|[;}\s])import\s*["'](\.\/[^"']+\.js)["']|from\s*["'](\.\/[^"']+\.js)["']/g;
32069
+ const staticImports = (name) => {
32070
+ const bytes = jsByName.get(name);
32071
+ if (!bytes)
32072
+ return [];
32073
+ const s = Buffer.from(bytes).toString("utf8");
32074
+ const out = [];
32075
+ let m;
32076
+ staticEdge.lastIndex = 0;
32077
+ while (m = staticEdge.exec(s))
32078
+ out.push(basename3(m[1] ?? m[2]));
32079
+ return out;
32080
+ };
32081
+ const closure = (start) => {
32082
+ const seen = new Set;
32083
+ const stack = [start];
32084
+ while (stack.length) {
32085
+ const f = stack.pop();
32086
+ if (seen.has(f) || !jsByName.has(f))
32087
+ continue;
32088
+ seen.add(f);
32089
+ stack.push(...staticImports(f));
32090
+ }
32091
+ return seen;
32092
+ };
32093
+ const gz = (bytes) => Bun.gzipSync(bytes, { level: 6 }).length;
32094
+ console.log(`
32095
+ \u2501\u2501\u2501\u2501\u2501 Build stats: ${label} \u2501\u2501\u2501\u2501\u2501`);
32096
+ const globalExt = new Map;
32097
+ for (const entry of entries) {
32098
+ const files = closure(entry.file);
32099
+ let raw = 0;
32100
+ let gzip = 0;
32101
+ const pkgAcc = new Map;
32102
+ for (const f of files) {
32103
+ const bytes = jsByName.get(f);
32104
+ raw += bytes.length;
32105
+ gzip += gz(bytes);
32106
+ const map = mapsByJs.get(f);
32107
+ if (!map)
32108
+ continue;
32109
+ for (const w of chunkPkgWeights(map)) {
32110
+ const cur = pkgAcc.get(w.pkg) ?? { pkg: w.pkg, bytes: 0, external: w.external };
32111
+ cur.bytes += w.bytes;
32112
+ pkgAcc.set(w.pkg, cur);
32113
+ if (w.external) {
32114
+ const g = globalExt.get(w.pkg) ?? { bytes: 0, chunks: new Set, entries: new Set };
32115
+ g.bytes = Math.max(g.bytes, w.bytes);
32116
+ g.chunks.add(f);
32117
+ g.entries.add(entry.name);
32118
+ globalExt.set(w.pkg, g);
32119
+ }
32120
+ }
32121
+ }
32122
+ const top = [...pkgAcc.values()].sort((a, b) => b.bytes - a.bytes).slice(0, 10);
32123
+ console.log(`
32124
+ \u25B8 ${entry.name}: pierwsze wczytanie ${kb(raw)} raw / ${kb(gzip)} gzip (${files.size} plik\xF3w)`);
32125
+ for (const w of top) {
32126
+ const tag = w.external ? "ext" : "wewn";
32127
+ console.log(` ${kb(w.bytes).padStart(8)} [${tag}] ${w.pkg}`);
32128
+ }
32129
+ }
32130
+ const heaviest = [...globalExt.entries()].sort((a, b) => b[1].bytes - a[1].bytes).slice(0, 15);
32131
+ if (heaviest.length) {
32132
+ console.log(`
32133
+ \u25B8 Najci\u0119\u017Csze biblioteki ZEWN\u0118TRZNE (\u017Ar\xF3d\u0142a) i gdzie l\u0105duj\u0105:`);
32134
+ for (const [pkg, g] of heaviest) {
32135
+ console.log(` ${kb(g.bytes).padStart(8)} ${pkg} \u2192 ${[...g.entries].join(", ")}`);
32136
+ }
32137
+ }
32138
+ console.log(`
32139
+ \u2501\u2501\u2501\u2501\u2501 koniec stats: ${label} \u2501\u2501\u2501\u2501\u2501
32140
+ `);
32141
+ }
32142
+ function looksMinified(files) {
32143
+ for (const f of files) {
32144
+ if (!f.name.endsWith(".js") || f.bytes.length < 16 * 1024)
32145
+ continue;
32146
+ const sample = f.bytes.subarray(0, 64 * 1024);
32147
+ let newlines = 0;
32148
+ for (const b of sample)
32149
+ if (b === 10)
32150
+ newlines++;
32151
+ const avgLineLen = sample.length / Math.max(1, newlines);
32152
+ if (avgLineLen < 200)
32153
+ return false;
32154
+ }
32155
+ return true;
32156
+ }
31975
32157
  async function buildBrowserApp(rootDir, outDir, plan, cache, noCache, i18nCollector, opts) {
31976
32158
  const federated = opts?.federated ?? false;
31977
32159
  const exposeRuntime = opts?.exposeRuntime ?? false;
32160
+ const minify = opts?.minify ?? true;
31978
32161
  mkdirSync7(outDir, { recursive: true });
31979
32162
  const publicMembers = plan.groups.get("public") ?? [];
31980
32163
  const protectedGroups = plan.chunks.filter((c) => c !== "public").map((c) => ({ name: c, members: plan.groups.get(c) ?? [] })).filter((g) => g.members.length > 0);
@@ -31997,10 +32180,11 @@ async function buildBrowserApp(rootDir, outDir, plan, cache, noCache, i18nCollec
31997
32180
  define: { ONLY_SERVER: "false", ONLY_BROWSER: "true", ONLY_CLIENT: "true" },
31998
32181
  federated,
31999
32182
  exposeRuntime,
32183
+ minify,
32000
32184
  externals: federated ? [...SHELL_EXTERNALS] : [],
32001
32185
  builderRev: 3
32002
32186
  });
32003
- if (!noCache && isCacheHit(cache, unitId, inputHash)) {
32187
+ if (!noCache && !buildStatsEnabled() && isCacheHit(cache, unitId, inputHash)) {
32004
32188
  const cached = cache.units[unitId]?.outputHashes;
32005
32189
  if (cached?._manifest) {
32006
32190
  try {
@@ -32024,24 +32208,27 @@ async function buildBrowserApp(rootDir, outDir, plan, cache, noCache, i18nCollec
32024
32208
  rmSync3(join9(outDir, f), { force: true });
32025
32209
  }
32026
32210
  }
32027
- const tmpDir = join9(outDir, "_entries");
32028
- mkdirSync7(tmpDir, { recursive: true });
32211
+ const tmpDir = join9(rootDir, ".arc", "tmp", `entries-${unitId}`);
32029
32212
  const importLines = (pkgs) => pkgs.map((m) => `import "${m.pkg.name}";`).join(`
32030
32213
  `);
32031
32214
  const initialEntry = join9(tmpDir, "initial.ts");
32032
- writeFileSync7(initialEntry, (exposeRuntime ? runtimeExposePrelude() + `
32033
- ` : "") + `${importLines(publicMembers)}
32034
- export { startApp } from "@arcote.tech/platform";
32035
- `);
32036
32215
  const entryPaths = [initialEntry];
32037
32216
  const groupModuleMap = new Map;
32038
32217
  for (const g of protectedGroups) {
32039
- const entry = join9(tmpDir, `${g.name}.ts`);
32040
- writeFileSync7(entry, `${importLines(g.members)}
32041
- `);
32042
- entryPaths.push(entry);
32218
+ entryPaths.push(join9(tmpDir, `${g.name}.ts`));
32043
32219
  groupModuleMap.set(g.name, g.members.map((m) => m.moduleName));
32044
32220
  }
32221
+ const writeEntryFiles = () => {
32222
+ mkdirSync7(tmpDir, { recursive: true });
32223
+ writeFileSync7(initialEntry, (exposeRuntime ? runtimeExposePrelude() + `
32224
+ ` : "") + `${importLines(publicMembers)}
32225
+ export { startApp } from "@arcote.tech/platform";
32226
+ `);
32227
+ for (const g of protectedGroups) {
32228
+ writeFileSync7(join9(tmpDir, `${g.name}.ts`), `${importLines(g.members)}
32229
+ `);
32230
+ }
32231
+ };
32045
32232
  const allMemberPkgs = new Map;
32046
32233
  for (const m of publicMembers)
32047
32234
  allMemberPkgs.set(m.pkg.name, m.pkg);
@@ -32062,11 +32249,13 @@ export { startApp } from "@arcote.tech/platform";
32062
32249
  `);
32063
32250
  patchedPkgJsons.push({ path: pkgJsonPath, original });
32064
32251
  }
32252
+ const statsMode = buildStatsEnabled();
32253
+ const mapsByJs = new Map;
32065
32254
  let result;
32066
- try {
32067
- result = await Bun.build({
32255
+ const runBuild = () => {
32256
+ writeEntryFiles();
32257
+ return Bun.build({
32068
32258
  entrypoints: entryPaths,
32069
- outdir: outDir,
32070
32259
  splitting: true,
32071
32260
  format: "esm",
32072
32261
  target: "browser",
@@ -32074,9 +32263,12 @@ export { startApp } from "@arcote.tech/platform";
32074
32263
  plugins: [
32075
32264
  ...federated ? [] : [singleReactPlugin(rootDir)],
32076
32265
  jsxDevShimPlugin(),
32266
+ nodeServerBuiltinStubPlugin(),
32077
32267
  i18nExtractPlugin(i18nCollector, rootDir)
32078
32268
  ],
32079
32269
  naming: "[name].[ext]",
32270
+ minify,
32271
+ sourcemap: statsMode ? "external" : "none",
32080
32272
  define: {
32081
32273
  ONLY_SERVER: "false",
32082
32274
  ONLY_BROWSER: "true",
@@ -32084,12 +32276,46 @@ export { startApp } from "@arcote.tech/platform";
32084
32276
  "process.env.NODE_ENV": '"production"'
32085
32277
  }
32086
32278
  });
32279
+ };
32280
+ let outFiles = null;
32281
+ try {
32282
+ for (let attempt = 1;; attempt++) {
32283
+ result = await runBuild();
32284
+ if (result.success) {
32285
+ const files = [];
32286
+ mapsByJs.clear();
32287
+ for (const out of result.outputs) {
32288
+ const name = basename3(out.path);
32289
+ if (name.endsWith(".map")) {
32290
+ try {
32291
+ mapsByJs.set(name.replace(/\.map$/, ""), JSON.parse(await out.text()));
32292
+ } catch {}
32293
+ continue;
32294
+ }
32295
+ let bytes = new Uint8Array(await out.arrayBuffer());
32296
+ if (statsMode && name.endsWith(".js")) {
32297
+ bytes = stripTrailingSourcemapComment(bytes);
32298
+ }
32299
+ files.push({ kind: out.kind, name, bytes });
32300
+ }
32301
+ if (!minify || looksMinified(files)) {
32302
+ outFiles = files;
32303
+ break;
32304
+ }
32305
+ }
32306
+ if (attempt >= 3) {
32307
+ if (!result.success)
32308
+ break;
32309
+ throw new Error("Browser app build: output NIEZMINIFIKOWANY mimo minify:true (3 pr\xF3by). Przerwane, \u017Ceby nie wypu\u015Bci\u0107 ci\u0119\u017Ckich bundli.");
32310
+ }
32311
+ console.warn(` ! browser build pass ${attempt} ${result.success ? "niezminifikowany mimo minify:true (wy\u015Bcig minify Bun.build)" : "nieudany (wy\u015Bcig Bun.build \u2014 np. znikni\u0119te pliki entry)"} \u2014 powtarzam`);
32312
+ }
32087
32313
  } finally {
32088
32314
  for (const p of patchedPkgJsons)
32089
32315
  writeFileSync7(p.path, p.original);
32090
32316
  }
32091
32317
  rmSync3(tmpDir, { recursive: true, force: true });
32092
- if (!result.success) {
32318
+ if (!result.success || !outFiles) {
32093
32319
  for (const log2 of result.logs)
32094
32320
  console.error(log2);
32095
32321
  throw new Error("Browser app build failed");
@@ -32098,17 +32324,12 @@ export { startApp } from "@arcote.tech/platform";
32098
32324
  let initialHash = "";
32099
32325
  const groups = {};
32100
32326
  const sharedChunks = [];
32101
- for (const out of result.outputs) {
32102
- const name = basename3(out.path);
32327
+ for (const out of outFiles) {
32103
32328
  if (out.kind === "entry-point") {
32104
- const bytes = readFileSync8(out.path);
32105
- const hash = sha256Hex(bytes).slice(0, 16);
32106
- const stem = name.replace(/\.js$/, "");
32329
+ const hash = sha256Hex(out.bytes).slice(0, 16);
32330
+ const stem = out.name.replace(/\.js$/, "");
32107
32331
  const finalName = `${stem}.${hash}.js`;
32108
- const finalPath = join9(outDir, finalName);
32109
- rmSync3(finalPath, { force: true });
32110
- writeFileSync7(finalPath, bytes);
32111
- rmSync3(out.path, { force: true });
32332
+ writeFileSync7(join9(outDir, finalName), out.bytes);
32112
32333
  if (stem === "initial") {
32113
32334
  initialFile = finalName;
32114
32335
  initialHash = hash;
@@ -32120,18 +32341,56 @@ export { startApp } from "@arcote.tech/platform";
32120
32341
  };
32121
32342
  }
32122
32343
  } else if (out.kind === "chunk") {
32123
- sharedChunks.push(name);
32344
+ writeFileSync7(join9(outDir, out.name), out.bytes);
32345
+ sharedChunks.push(out.name);
32124
32346
  }
32125
32347
  }
32126
32348
  if (!initialFile) {
32127
32349
  throw new Error("Browser app build: initial entry not found in outputs");
32128
32350
  }
32351
+ const written = new Map;
32352
+ for (const out of outFiles) {
32353
+ const name = out.kind === "entry-point" ? `${out.name.replace(/\.js$/, "")}.${sha256Hex(out.bytes).slice(0, 16)}.js` : out.name;
32354
+ if (out.kind === "entry-point" || out.kind === "chunk") {
32355
+ written.set(name, sha256Hex(out.bytes));
32356
+ }
32357
+ }
32358
+ for (const f of readdirSync4(outDir)) {
32359
+ if (!f.endsWith(".js"))
32360
+ continue;
32361
+ const expected = written.get(f);
32362
+ const actual = sha256Hex(readFileSync8(join9(outDir, f)));
32363
+ if (!expected || expected !== actual) {
32364
+ throw new Error(`Browser app build: plik "${f}" w ${outDir} nie zgadza si\u0119 z artefaktami builda ` + `(${expected ? "tre\u015B\u0107 rozjechana z zapisem" : "obcy plik \u2014 co\u015B innego pisze do outDir"}). ` + `Przerwane, \u017Ceby nie wypu\u015Bci\u0107 niesp\xF3jnego builda.`);
32365
+ }
32366
+ }
32129
32367
  const manifest = {
32130
32368
  initial: { file: initialFile, hash: initialHash },
32131
32369
  groups,
32132
32370
  sharedChunks,
32133
32371
  cached: false
32134
32372
  };
32373
+ if (statsMode) {
32374
+ const jsByName = new Map;
32375
+ const mapsByFinal = new Map;
32376
+ const finalNameOf = (out) => out.kind === "entry-point" ? `${out.name.replace(/\.js$/, "")}.${sha256Hex(out.bytes).slice(0, 16)}.js` : out.name;
32377
+ for (const out of outFiles) {
32378
+ const fn = finalNameOf(out);
32379
+ jsByName.set(fn, out.bytes);
32380
+ const map = mapsByJs.get(out.name);
32381
+ if (map)
32382
+ mapsByFinal.set(fn, map);
32383
+ }
32384
+ reportBrowserBuildStats({
32385
+ label: federated ? "browser-fed" : "browser",
32386
+ entries: [
32387
+ { name: "initial", file: initialFile },
32388
+ ...Object.entries(groups).map(([stem, g]) => ({ name: stem, file: g.file }))
32389
+ ],
32390
+ jsByName,
32391
+ mapsByJs: mapsByFinal
32392
+ });
32393
+ }
32135
32394
  updateCache(cache, unitId, inputHash, {
32136
32395
  outputHashes: { _manifest: JSON.stringify(manifest) }
32137
32396
  });
@@ -32433,6 +32692,11 @@ function collectModuleNames(dir, acc) {
32433
32692
  }
32434
32693
  }
32435
32694
  }
32695
+ function packageDeclaresModule(pkg) {
32696
+ const names = new Set;
32697
+ collectModuleNames(join11(pkg.path, "src"), names);
32698
+ return names.size > 0;
32699
+ }
32436
32700
  function assertOneModulePerPackage(packages) {
32437
32701
  const offenders = [];
32438
32702
  for (const pkg of packages) {
@@ -32623,6 +32887,7 @@ function resolveWorkspace() {
32623
32887
  async function buildAll(ws, opts = {}) {
32624
32888
  const cache = loadBuildCache(ws.arcDir);
32625
32889
  const noCache = opts.noCache ?? false;
32890
+ const minify = opts.minify ?? true;
32626
32891
  const themePath = ws.rootPkg.arc?.theme;
32627
32892
  const federation = getFederationConfig(ws);
32628
32893
  log2(`Building (concurrency parallel${noCache ? ", no-cache" : ""})...`);
@@ -32637,8 +32902,9 @@ async function buildAll(ws, opts = {}) {
32637
32902
  const isHost = Boolean(federation?.host);
32638
32903
  const moduleNameOf2 = (name) => name.split("/").pop() ?? name;
32639
32904
  const exposureOf = (pkgName) => accessMap.exposure[moduleNameOf2(pkgName)]?.exposure ?? "local";
32640
- const localPackages = isHost ? ws.packages : ws.packages.filter((p) => exposureOf(p.name) !== "external");
32641
- const fedPackages = ws.packages.filter((p) => ["external", "both"].includes(exposureOf(p.name)));
32905
+ const modulePackages = ws.packages.filter(packageDeclaresModule);
32906
+ const localPackages = isHost ? modulePackages : modulePackages.filter((p) => exposureOf(p.name) !== "external");
32907
+ const fedPackages = modulePackages.filter((p) => ["external", "both"].includes(exposureOf(p.name)));
32642
32908
  const hasFederatedModules = fedPackages.length > 0;
32643
32909
  const needsPeerShell = isHost || hasFederatedModules;
32644
32910
  log2();
@@ -32649,16 +32915,17 @@ async function buildAll(ws, opts = {}) {
32649
32915
  ok(`Federated modules: ${fedPackages.map((p) => moduleNameOf2(p.name)).join(", ")}`);
32650
32916
  }
32651
32917
  const i18nCollector = new Map;
32652
- const [browserResult, , , , shellResult, fedResult] = await Promise.all([
32653
- buildBrowserApp(ws.rootDir, ws.browserDir, plan, cache, noCache, i18nCollector, {
32654
- federated: false,
32655
- exposeRuntime: isHost
32656
- }),
32918
+ const browserSequence = (async () => {
32919
+ const bundled = await buildBrowserApp(ws.rootDir, ws.browserDir, plan, cache, noCache, i18nCollector, { federated: false, exposeRuntime: isHost, minify });
32920
+ const fed = fedPlan ? await buildBrowserApp(ws.rootDir, join13(ws.arcDir, "browser-fed"), fedPlan, cache, noCache, i18nCollector, { federated: true, unitId: "browser-app-fed", minify }) : undefined;
32921
+ return { bundled, fed };
32922
+ })();
32923
+ const [{ bundled: browserResult, fed: fedResult }, , , , shellResult] = await Promise.all([
32924
+ browserSequence,
32657
32925
  buildStyles(ws.rootDir, ws.arcDir, ws.packages, themePath, cache, noCache),
32658
32926
  copyBrowserAssets(ws, cache, noCache),
32659
32927
  buildTranslations(ws.rootDir, ws.arcDir, cache, noCache),
32660
- needsPeerShell ? buildPeerShell(ws.rootDir, join13(ws.arcDir, "shell"), cache, noCache) : Promise.resolve(undefined),
32661
- fedPlan ? buildBrowserApp(ws.rootDir, join13(ws.arcDir, "browser-fed"), fedPlan, cache, noCache, i18nCollector, { federated: true, unitId: "browser-app-fed" }) : Promise.resolve(undefined)
32928
+ needsPeerShell ? buildPeerShell(ws.rootDir, join13(ws.arcDir, "shell"), cache, noCache) : Promise.resolve(undefined)
32662
32929
  ]);
32663
32930
  const { finalizeTranslations: finalizeTranslations3 } = await Promise.resolve().then(() => (init_i18n(), exports_i18n));
32664
32931
  await finalizeTranslations3(ws.rootDir, ws.arcDir, i18nCollector);
@@ -32806,20 +33073,25 @@ async function loadServerContext(ws) {
32806
33073
  if (!existsSync11(serverEntry)) {
32807
33074
  return { context: null, moduleAccess: new Map };
32808
33075
  }
33076
+ let serverError;
32809
33077
  try {
32810
33078
  await import(serverEntry);
32811
33079
  } catch (e) {
32812
33080
  err(`Failed to load server bundle ${SERVER_ENTRY_FILE}: ${e}`);
33081
+ serverError = e;
32813
33082
  }
32814
33083
  const { getContext, getAllModuleAccess } = await import(platformEntry);
32815
33084
  return {
32816
33085
  context: getContext() ?? null,
32817
- moduleAccess: getAllModuleAccess()
33086
+ moduleAccess: getAllModuleAccess(),
33087
+ serverError
32818
33088
  };
32819
33089
  }
32820
33090
 
32821
33091
  // src/commands/platform-build.ts
32822
33092
  async function platformBuild(opts = {}) {
33093
+ if (opts.stats)
33094
+ process.env.ARC_BUILD_STATS = "1";
32823
33095
  const ws = resolveWorkspace();
32824
33096
  const manifest = await buildAll(ws, { noCache: opts.noCache });
32825
33097
  const groupCount = Object.keys(manifest.groups).length;
@@ -33247,6 +33519,7 @@ function generateCaddyfile(cfg) {
33247
33519
  for (const [name, env2] of Object.entries(cfg.envs)) {
33248
33520
  lines.push(`${env2.domain} {${tlsDirective}`);
33249
33521
  lines.push(...logDirective);
33522
+ lines.push(" encode zstd gzip");
33250
33523
  if (observability) {
33251
33524
  lines.push(" handle_path /otel/* {");
33252
33525
  lines.push(" reverse_proxy otel-collector:4318");
@@ -38266,6 +38539,14 @@ async function createArcServer(config) {
38266
38539
  if (contentLength > maxRequestBodySize) {
38267
38540
  return Response.json({ error: "Payload too large" }, { status: 413, headers: corsHeaders2 });
38268
38541
  }
38542
+ if (url.pathname === "/ws" && req.headers.get("Upgrade") === "websocket") {
38543
+ if (server2.upgrade(req, { data: { clientId: "" } }))
38544
+ return;
38545
+ return new Response("WebSocket upgrade failed", {
38546
+ status: 500,
38547
+ headers: corsHeaders2
38548
+ });
38549
+ }
38269
38550
  const authHeader = req.headers.get("Authorization");
38270
38551
  let rawToken = authHeader?.replace("Bearer ", "") || null;
38271
38552
  if (!rawToken && config.allowTokenInQuery) {
@@ -38283,14 +38564,6 @@ async function createArcServer(config) {
38283
38564
  if (rawToken && !tokenPayload) {
38284
38565
  return Response.json({ error: "Unauthorized" }, { status: 401, headers: corsHeaders2 });
38285
38566
  }
38286
- if (url.pathname === "/ws" && req.headers.get("Upgrade") === "websocket") {
38287
- if (server2.upgrade(req, { data: { clientId: "" } }))
38288
- return;
38289
- return new Response("WebSocket upgrade failed", {
38290
- status: 500,
38291
- headers: corsHeaders2
38292
- });
38293
- }
38294
38567
  const reqCtx = { rawToken, tokenPayload, corsHeaders: corsHeaders2 };
38295
38568
  for (const handler of httpHandlers) {
38296
38569
  const response = await handler(req, url, reqCtx);
@@ -38404,6 +38677,13 @@ init_i18n();
38404
38677
  import { timingSafeEqual } from "crypto";
38405
38678
  import { existsSync as existsSync19, mkdirSync as mkdirSync15 } from "fs";
38406
38679
  import { join as join22 } from "path";
38680
+ async function initContextHandler(context2, dbPath) {
38681
+ const factory = await resolveDbAdapterFactory(dbPath);
38682
+ const dbAdapter = factory(context2);
38683
+ const handler = new ContextHandler(context2, dbAdapter);
38684
+ await handler.init();
38685
+ return handler;
38686
+ }
38407
38687
  async function resolveDbAdapterFactory(dbPath) {
38408
38688
  const databaseUrl = process.env.DATABASE_URL;
38409
38689
  if (databaseUrl && databaseUrl.length > 0) {
@@ -38684,13 +38964,6 @@ function handleDiscovery(req, ctx, ws, manifest, federation, mapUrl) {
38684
38964
  if (!federation) {
38685
38965
  return new Response("Not Found", { status: 404, headers: ctx.corsHeaders });
38686
38966
  }
38687
- const presented = req.headers.get("X-Arc-Pairing-Token");
38688
- if (!presented || !safeEqual(presented, federation.registrationToken)) {
38689
- return new Response("Unauthorized", {
38690
- status: 401,
38691
- headers: ctx.corsHeaders
38692
- });
38693
- }
38694
38967
  const fed = manifest.federation;
38695
38968
  const platformUrl = process.env.ARC_PUBLIC_URL || new URL(req.url).origin;
38696
38969
  return Response.json({
@@ -39025,7 +39298,7 @@ async function startPlatform(opts) {
39025
39298
  const dbPath = opts.dbPath ?? join23(ws.rootDir, ".arc", "data", devMode ? "dev.db" : "prod.db");
39026
39299
  let manifest;
39027
39300
  if (devMode) {
39028
- manifest = await buildAll(ws);
39301
+ manifest = await buildAll(ws, { minify: false });
39029
39302
  } else {
39030
39303
  const manifestPath = join23(ws.arcDir, "manifest.json");
39031
39304
  if (!existsSync20(manifestPath)) {
@@ -39041,7 +39314,11 @@ async function startPlatform(opts) {
39041
39314
  process.env.ARC_APP_SECRET = resolveAppSecret(ws.rootDir);
39042
39315
  }
39043
39316
  log2("Loading server context...");
39044
- const { context: context2, moduleAccess } = await loadServerContext(ws);
39317
+ const { context: context2, moduleAccess, serverError } = await loadServerContext(ws);
39318
+ if (serverError) {
39319
+ err("Serwer NIE wstanie: modu\u0142 serwerowy nie za\u0142adowa\u0142 si\u0119 (b\u0142\u0105d wy\u017Cej). " + "Cz\u0119\u015Bciowy kontekst = po\u0142owiczna aplikacja (query rzucaj\u0105 " + "'Element not found'). Napraw przyczyn\u0119 (np. brakuj\u0105ce zmienne " + "\u015Brodowiskowe) i wystartuj ponownie.");
39320
+ process.exit(1);
39321
+ }
39045
39322
  if (context2) {
39046
39323
  ok("Context loaded");
39047
39324
  } else {
@@ -39136,18 +39413,12 @@ async function startMapTool(ws, port, authToken, platformServer, platform3) {
39136
39413
  function assertProdFederationSecrets(devMode, hasFederation) {
39137
39414
  if (devMode || !hasFederation)
39138
39415
  return;
39139
- const missing = [];
39140
- if (!process.env.ARC_APP_SECRET)
39141
- missing.push("ARC_APP_SECRET");
39142
- if (!process.env.ARC_PAIRING_TOKEN && !process.env.ARC_MAP_TOKEN) {
39143
- missing.push("ARC_PAIRING_TOKEN");
39144
- }
39145
- if (missing.length === 0)
39416
+ if (process.env.ARC_PAIRING_TOKEN || process.env.ARC_MAP_TOKEN)
39146
39417
  return;
39147
- err(`Federacja w produkcji wymaga zmiennych \u015Brodowiskowych: ${missing.join(", ")}.
39148
- ` + ` Bez nich sekret i token s\u0105 generowane w kontenerze i GIN\u0104 przy pierwszym
39149
- ` + ` recreate \u2014 hosty musia\u0142yby przeinstalowa\u0107 aplikacj\u0119, bez \u017Cadnego b\u0142\u0119du.
39150
- ` + ` Ustaw je w deployu (deploy.arc.json \u2192 envVars + deploy.arc.<env>.env).`);
39418
+ err(`Federacja w produkcji wymaga zmiennej \u015Brodowiskowej ARC_PAIRING_TOKEN.
39419
+ ` + ` Bez niej token parowania jest generowany w kontenerze i GINIE przy
39420
+ ` + ` pierwszym recreate \u2014 parowania host\xF3w staj\u0105 si\u0119 martwe, bez b\u0142\u0119du.
39421
+ ` + ` Ustaw j\u0105 w deployu (deploy.arc.json \u2192 envVars + deploy.arc.<env>.env).`);
39151
39422
  process.exit(1);
39152
39423
  }
39153
39424
  function resolveAppSecret(rootDir) {
@@ -39213,7 +39484,7 @@ function attachDevWatcher(ws, platform3, onReload) {
39213
39484
  isRebuilding = true;
39214
39485
  log2("Rebuilding...");
39215
39486
  try {
39216
- const next = await buildAll(ws);
39487
+ const next = await buildAll(ws, { minify: false });
39217
39488
  platform3.setManifest(next);
39218
39489
  platform3.notifyReload(next);
39219
39490
  onReload?.();
@@ -39258,6 +39529,36 @@ async function platformDev(opts = {}) {
39258
39529
  });
39259
39530
  }
39260
39531
 
39532
+ // src/commands/platform-pairing-token.ts
39533
+ import { randomBytes as randomBytes3 } from "crypto";
39534
+ import { join as join24 } from "path";
39535
+ async function platformPairingToken() {
39536
+ const ws = resolveWorkspace();
39537
+ const { context: context2 } = await loadServerContext(ws);
39538
+ if (!context2) {
39539
+ err("Brak zbudowanego kontekstu. Uruchom najpierw `arc platform build`.");
39540
+ process.exit(1);
39541
+ }
39542
+ const isProd = false;
39543
+ const dbPath = join24(ws.rootDir, ".arc", "data", isProd ? "prod.db" : "dev.db");
39544
+ const handler = await initContextHandler(context2, dbPath);
39545
+ const code = randomBytes3(24).toString("base64url");
39546
+ const res = await handler.executeCommand("pairingCode.issue", { code }, null, { internal: true });
39547
+ if (!res?.ok) {
39548
+ err(`Nie uda\u0142o si\u0119 zapisa\u0107 kodu parowania${res?.error ? `: ${res.error}` : ""}. ` + "Czy aplikacja wystawia agregat `pairingCode` (federacja)?");
39549
+ process.exit(1);
39550
+ }
39551
+ process.stdout.write(`
39552
+ Kod parowania (jednorazowy):
39553
+
39554
+ ${code}
39555
+
39556
+ ` + ` Podaj go w ho\u015Bcie przy instalacji aplikacji. Wygasa po pierwszym u\u017Cyciu.
39557
+
39558
+ `);
39559
+ process.exit(0);
39560
+ }
39561
+
39261
39562
  // src/commands/platform-start.ts
39262
39563
  async function platformStart() {
39263
39564
  const ws = resolveWorkspace();
@@ -39275,8 +39576,9 @@ platform3.command("dev").description("Start platform in dev mode (Bun server + V
39275
39576
  map: opts.map,
39276
39577
  headless: opts.headless
39277
39578
  }));
39278
- platform3.command("build").description("Build platform for production").option("--no-cache", "Force full rebuild").action((opts) => platformBuild({ noCache: opts.cache === false }));
39579
+ platform3.command("build").description("Build platform for production").option("--no-cache", "Force full rebuild").option("--stats", "Wypisz rozbicie chunk\xF3w na biblioteki (per-wej\u015Bcie raw/gzip + najci\u0119\u017Csze liby, ext/wewn). Wymusza pe\u0142ny build.").action((opts) => platformBuild({ noCache: opts.cache === false, stats: opts.stats }));
39279
39580
  platform3.command("start").description("Start platform in production mode (requires prior build)").action(platformStart);
39581
+ platform3.command("pairing-token").description("Generate a one-time federation pairing code (writes to DB, prints it)").action(platformPairingToken);
39280
39582
  platform3.command("deploy [env]").description("Deploy platform to a remote server (reads deploy.arc.json, surveys if missing)").option("--skip-build", "Skip local build step").option("--rebuild", "Force rebuild before deploy").option("--build-only", "Build the Docker image locally, then exit (no remote push)").option("--image-tag <hash>", "Roll back / pin to an existing image tag instead of building a new one").option("--force-bootstrap", "Re-run Ansible host bootstrap even if the server is already configured").action((env2, opts) => platformDeploy(env2, opts));
39281
39583
  program2.parse(process.argv);
39282
39584
  if (process.argv.length === 2) {