@arcote.tech/arc-cli 0.7.0 → 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.
@@ -1,13 +1,14 @@
1
1
  import { findUpSync } from "find-up";
2
- import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "fs";
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
- buildModulesByChunks,
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,
@@ -24,16 +25,13 @@ import { planChunks } from "../builder/chunk-planner";
24
25
  import { collectFrameworkDeps } from "../builder/dependency-collector";
25
26
  import {
26
27
  mtimeOf,
27
- readInstalledVersion,
28
28
  sha256Hex,
29
- sha256OfDir,
30
29
  sha256OfFiles,
31
30
  sha256OfJson,
32
31
  } from "../builder/hash";
33
- import { pAll } from "../builder/parallel";
34
32
 
35
33
  // Re-export for convenience
36
- export { buildContextPackages, buildModulesByChunks, buildStyles, isContextPackage };
34
+ export { buildContextPackages, buildStyles, isContextPackage };
37
35
  export type { BuildManifest, ModuleDescriptor, WorkspacePackage };
38
36
 
39
37
  // ---------------------------------------------------------------------------
@@ -68,8 +66,8 @@ export interface WorkspaceInfo {
68
66
  rootPkg: Record<string, any>;
69
67
  appName: string;
70
68
  arcDir: string;
71
- modulesDir: string;
72
- shellDir: string;
69
+ /** Single Bun.build output dir for browser app: `<arcDir>/browser/`. */
70
+ browserDir: string;
73
71
  /** Static assets generated z arc.browserAssets workspace deps. Serwowane pod /assets/*. */
74
72
  assetsDir: string;
75
73
  publicDir: string;
@@ -128,8 +126,7 @@ export function resolveWorkspace(): WorkspaceInfo {
128
126
  rootPkg,
129
127
  appName,
130
128
  arcDir,
131
- modulesDir: join(arcDir, "modules"),
132
- shellDir: join(arcDir, "shell"),
129
+ browserDir: join(arcDir, "browser"),
133
130
  assetsDir: join(arcDir, "assets"),
134
131
  publicDir: join(rootDir, "public"),
135
132
  packages,
@@ -191,16 +188,22 @@ export async function buildAll(
191
188
  .join(", ")}`,
192
189
  );
193
190
 
194
- // Phase 4 — independent parallel build units. Module chunks are the only
195
- // unit whose output we need for the manifest; others update the cache.
196
- const [modulesResult] = await Promise.all([
197
- buildModulesByChunks(ws.rootDir, ws.modulesDir, plan, cache, noCache),
198
- buildShell(ws, cache, noCache),
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),
199
198
  buildStyles(ws.rootDir, ws.arcDir, ws.packages, themePath, cache, noCache),
200
199
  copyBrowserAssets(ws, cache, noCache),
201
200
  buildTranslations(ws.rootDir, ws.arcDir, cache, noCache),
202
201
  ]);
203
202
 
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);
206
+
204
207
  // Phase 5 — framework peer manifest at `<arcDir>/package.json`. Used by
205
208
  // the deploy image build (single `bun install` for all peers, one copy
206
209
  // shared across module bundles).
@@ -208,37 +211,28 @@ export async function buildAll(
208
211
 
209
212
  saveBuildCache(ws.arcDir, cache);
210
213
 
211
- const finalManifest = assembleManifest(ws, modulesResult.modules, plan.chunks, cache);
214
+ const finalManifest = assembleManifest(ws, browserResult, cache);
212
215
  writeFileSync(
213
- join(ws.modulesDir, "manifest.json"),
216
+ join(ws.arcDir, "manifest.json"),
214
217
  JSON.stringify(finalManifest, null, 2),
215
218
  );
216
219
  return finalManifest;
217
220
  }
218
221
 
219
222
  /**
220
- * Assemble the platform manifest from cached output hashes no disk reads.
223
+ * Assemble the platform manifest from the browser app result + style hash.
221
224
  */
222
225
  function assembleManifest(
223
226
  ws: WorkspaceInfo,
224
- modules: ModuleDescriptor[],
225
- chunks: readonly string[],
227
+ browser: BrowserAppResult,
226
228
  cache: BuildCache,
227
229
  ): BuildManifest {
228
- // Aggregate shellHash from all shell:* unit output hashes.
229
- const shellEntries: Record<string, string> = {};
230
- for (const [unitId, entry] of Object.entries(cache.units)) {
231
- if (unitId.startsWith("shell:") && entry.outputHash) {
232
- shellEntries[unitId] = entry.outputHash;
233
- }
234
- }
235
- const shellHash = sha256OfJson(shellEntries);
236
230
  const stylesHash = cache.units["styles"]?.outputHash ?? "";
237
231
 
238
232
  return {
239
- modules,
240
- chunks,
241
- shellHash,
233
+ initial: browser.initial,
234
+ groups: browser.groups,
235
+ sharedChunks: browser.sharedChunks,
242
236
  stylesHash,
243
237
  buildTime: new Date().toISOString(),
244
238
  };
@@ -399,212 +393,6 @@ async function copyBrowserAssets(
399
393
  updateCache(cache, unitId, inputHash, { outputHashes });
400
394
  }
401
395
 
402
- // ---------------------------------------------------------------------------
403
- // Shell builder — framework packages for import map
404
- // ---------------------------------------------------------------------------
405
-
406
- /** Collect all @arcote.tech/* peerDependencies from workspace packages. */
407
- export function collectArcPeerDeps(packages: WorkspacePackage[]): [string, string][] {
408
- const seen = new Set<string>();
409
- for (const pkg of ["@arcote.tech/arc", "@arcote.tech/arc-ds", "@arcote.tech/arc-react", "@arcote.tech/platform"]) {
410
- seen.add(pkg);
411
- }
412
- for (const wp of packages) {
413
- const peerDeps = wp.packageJson.peerDependencies ?? {};
414
- for (const dep of Object.keys(peerDeps)) {
415
- if (dep.startsWith("@arcote.tech/")) seen.add(dep);
416
- }
417
- }
418
- return [...seen].map((pkg) => {
419
- const short = pkg === "@arcote.tech/platform"
420
- ? "platform"
421
- : pkg.replace("@arcote.tech/", "");
422
- return [short, pkg];
423
- });
424
- }
425
-
426
- const REACT_ENTRIES: [string, string][] = [
427
- [
428
- "react",
429
- `import React from "react";
430
- export default React;
431
- export const {
432
- Children, Component, Fragment, Profiler, PureComponent, StrictMode, Suspense,
433
- cloneElement, createContext, createElement, createRef, forwardRef, isValidElement,
434
- lazy, memo, startTransition, use, useCallback, useContext, useDebugValue,
435
- useDeferredValue, useEffect, useId, useImperativeHandle, useInsertionEffect,
436
- useLayoutEffect, useMemo, useReducer, useRef, useState, useSyncExternalStore,
437
- useTransition, version, useActionState, useOptimistic,
438
- } = React;`,
439
- ],
440
- ["jsx-runtime", `export { jsx, jsxs, Fragment } from "react/jsx-runtime";`],
441
- [
442
- "jsx-dev-runtime",
443
- `export { jsxDEV, Fragment } from "react/jsx-dev-runtime";`,
444
- ],
445
- [
446
- "react-dom",
447
- `import ReactDOM from "react-dom";
448
- export default ReactDOM;
449
- export const { createPortal, flushSync } = ReactDOM;`,
450
- ],
451
- [
452
- "react-dom-client",
453
- `export { createRoot, hydrateRoot } from "react-dom/client";`,
454
- ],
455
- ];
456
-
457
- const REACT_OUTPUT_FILES = REACT_ENTRIES.map(([n]) => `${n}.js`);
458
-
459
- const SHELL_BASE_EXTERNAL = [
460
- "react",
461
- "react-dom",
462
- "react/jsx-runtime",
463
- "react/jsx-dev-runtime",
464
- "react-dom/client",
465
- ];
466
-
467
- const sourceFilter = (rel: string): boolean => {
468
- if (rel.startsWith("dist/") || rel.startsWith("dist")) return false;
469
- if (rel.includes("/node_modules/") || rel.startsWith("node_modules")) return false;
470
- if (rel.startsWith(".arc/") || rel.startsWith(".arc")) return false;
471
- return true;
472
- };
473
-
474
- function arcPkgSrcHash(rootDir: string, pkg: string): string {
475
- // Prefer src/ tree (workspace links), fallback to whole package dir.
476
- const srcDir = join(rootDir, "node_modules", pkg, "src");
477
- if (existsSync(srcDir)) return sha256OfDir(srcDir, sourceFilter);
478
- return sha256OfDir(join(rootDir, "node_modules", pkg), sourceFilter);
479
- }
480
-
481
- async function buildShellReact(
482
- shellDir: string,
483
- tmpDir: string,
484
- rootDir: string,
485
- cache: BuildCache,
486
- noCache: boolean,
487
- ): Promise<void> {
488
- const unitId = "shell:react";
489
- const inputHash = sha256OfJson({
490
- react: readInstalledVersion(rootDir, "react"),
491
- "react-dom": readInstalledVersion(rootDir, "react-dom"),
492
- entries: REACT_ENTRIES.map(([k, v]) => [k, v]),
493
- });
494
-
495
- const requiredOutputs = REACT_OUTPUT_FILES.map((f) => join(shellDir, f));
496
- if (!noCache && isCacheHit(cache, unitId, inputHash, requiredOutputs)) {
497
- console.log(` ✓ cached: shell:react`);
498
- return;
499
- }
500
-
501
- console.log(` building: shell:react`);
502
-
503
- const reactEps: string[] = [];
504
- for (const [name, code] of REACT_ENTRIES) {
505
- const f = join(tmpDir, `${name}.ts`);
506
- await Bun.write(f, code);
507
- reactEps.push(f);
508
- }
509
-
510
- const r = await Bun.build({
511
- entrypoints: reactEps,
512
- outdir: shellDir,
513
- splitting: true,
514
- format: "esm",
515
- target: "browser",
516
- naming: "[name].[ext]",
517
- });
518
- if (!r.success) {
519
- for (const l of r.logs) console.error(l);
520
- throw new Error("Shell React build failed");
521
- }
522
-
523
- const outputHash = sha256OfFiles(requiredOutputs);
524
- updateCache(cache, unitId, inputHash, { outputHash });
525
- }
526
-
527
- async function buildShellArcEntry(
528
- shortName: string,
529
- pkg: string,
530
- allArcPkgs: string[],
531
- shellDir: string,
532
- tmpDir: string,
533
- rootDir: string,
534
- cache: BuildCache,
535
- noCache: boolean,
536
- ): Promise<void> {
537
- const unitId = `shell:arc:${shortName}`;
538
- const otherExternals = allArcPkgs.filter((p) => p !== pkg);
539
- const inputHash = sha256OfJson({
540
- pkg,
541
- version: readInstalledVersion(rootDir, pkg),
542
- src: arcPkgSrcHash(rootDir, pkg),
543
- base: SHELL_BASE_EXTERNAL,
544
- others: [...otherExternals].sort(),
545
- });
546
-
547
- const outputFile = join(shellDir, `${shortName}.js`);
548
- if (!noCache && isCacheHit(cache, unitId, inputHash, [outputFile])) {
549
- console.log(` ✓ cached: ${unitId}`);
550
- return;
551
- }
552
-
553
- console.log(` building: ${unitId}`);
554
-
555
- const f = join(tmpDir, `${shortName}.ts`);
556
- await Bun.write(f, `export * from "${pkg}";\n`);
557
-
558
- const r = await Bun.build({
559
- entrypoints: [f],
560
- outdir: shellDir,
561
- format: "esm",
562
- target: "browser",
563
- naming: "[name].[ext]",
564
- external: [...SHELL_BASE_EXTERNAL, ...otherExternals],
565
- define: {
566
- ONLY_SERVER: "false",
567
- ONLY_BROWSER: "true",
568
- ONLY_CLIENT: "true",
569
- },
570
- });
571
- if (!r.success) {
572
- for (const l of r.logs) console.error(l);
573
- throw new Error(`Shell build failed for ${pkg}`);
574
- }
575
-
576
- const outputHash = sha256OfFiles([outputFile]);
577
- updateCache(cache, unitId, inputHash, { outputHash });
578
- }
579
-
580
- /**
581
- * Build the framework shell — react layer + each @arcote.tech/* package as a
582
- * separate cacheable unit. Tasks run in parallel via pAll.
583
- */
584
- export async function buildShell(
585
- ws: WorkspaceInfo,
586
- cache: BuildCache,
587
- noCache: boolean,
588
- ): Promise<void> {
589
- mkdirSync(ws.shellDir, { recursive: true });
590
- const tmpDir = join(ws.shellDir, "_tmp");
591
- mkdirSync(tmpDir, { recursive: true });
592
-
593
- const arcEntries = collectArcPeerDeps(ws.packages);
594
- const allArcPkgs = arcEntries.map(([, pkg]) => pkg);
595
-
596
- const tasks: Array<() => Promise<void>> = [
597
- () => buildShellReact(ws.shellDir, tmpDir, ws.rootDir, cache, noCache),
598
- ...arcEntries.map(([short, pkg]) => () =>
599
- buildShellArcEntry(short, pkg, allArcPkgs, ws.shellDir, tmpDir, ws.rootDir, cache, noCache),
600
- ),
601
- ];
602
-
603
- await pAll(tasks);
604
-
605
- rmSync(tmpDir, { recursive: true, force: true });
606
- }
607
-
608
396
  // ---------------------------------------------------------------------------
609
397
  // Server context loading
610
398
  // ---------------------------------------------------------------------------
@@ -618,9 +406,9 @@ export async function loadServerContext(
618
406
  (globalThis as any).ONLY_BROWSER = false;
619
407
  (globalThis as any).ONLY_CLIENT = false;
620
408
 
621
- // Resolve platform from the project's node_modules. Bun picks the `bun`
622
- // export condition from package.json, which points at `index.server.ts`
623
- // (server-safe, no React/JSX at top level).
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).
624
412
  const platformDir = join(process.cwd(), "node_modules", "@arcote.tech", "platform");
625
413
  const platformPkg = JSON.parse(readFileSync(join(platformDir, "package.json"), "utf-8"));
626
414
  const platformEntry = join(platformDir, platformPkg.main ?? "src/index.ts");
@@ -4,7 +4,6 @@ import { startPlatformServer, type PlatformServer } from "./server";
4
4
  import type { BuildManifest } from "./shared";
5
5
  import {
6
6
  buildAll,
7
- collectArcPeerDeps,
8
7
  err,
9
8
  loadServerContext,
10
9
  log,
@@ -54,7 +53,7 @@ export async function startPlatform(
54
53
  if (devMode) {
55
54
  manifest = await buildAll(ws);
56
55
  } else {
57
- const manifestPath = join(ws.modulesDir, "manifest.json");
56
+ const manifestPath = join(ws.arcDir, "manifest.json");
58
57
  if (!existsSync(manifestPath)) {
59
58
  err("No build found. Run `arc platform build` first.");
60
59
  process.exit(1);
@@ -75,7 +74,6 @@ export async function startPlatform(
75
74
 
76
75
  // 3. Start the platform server. Cache headers + SSE behaviour are
77
76
  // controlled inside the server by `devMode`; we just pass the flag.
78
- const arcEntries = collectArcPeerDeps(ws.packages);
79
77
  const platform = await startPlatformServer({
80
78
  ws,
81
79
  port,
@@ -84,7 +82,6 @@ export async function startPlatform(
84
82
  moduleAccess,
85
83
  dbPath,
86
84
  devMode,
87
- arcEntries,
88
85
  });
89
86
 
90
87
  ok(`Server on http://localhost:${port}`);
@@ -121,7 +118,9 @@ function attachDevWatcher(ws: WorkspaceInfo, platform: PlatformServer): void {
121
118
  const next = await buildAll(ws);
122
119
  platform.setManifest(next);
123
120
  platform.notifyReload(next);
124
- ok(`Rebuilt — ${next.modules.length} module(s)`);
121
+ ok(
122
+ `Rebuilt — initial + ${Object.keys(next.groups).length} group(s)`,
123
+ );
125
124
  } catch (e) {
126
125
  console.error(`Rebuild failed: ${e}`);
127
126
  } finally {