@arcote.tech/arc-cli 0.5.5 → 0.5.7

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
@@ -13620,6 +13620,9 @@ class AuthAdapter {
13620
13620
  localStorage.removeItem(TOKEN_PREFIX + scope);
13621
13621
  }
13622
13622
  }
13623
+ setDecoded(decoded, scope = "default") {
13624
+ this.scopes.set(scope, { raw: "", decoded });
13625
+ }
13623
13626
  loadPersisted() {
13624
13627
  if (!hasLocalStorage())
13625
13628
  return;
@@ -13816,7 +13819,8 @@ class EventWire {
13816
13819
  localId: e2.localId,
13817
13820
  type: e2.type,
13818
13821
  payload: e2.payload,
13819
- createdAt: e2.createdAt
13822
+ createdAt: e2.createdAt,
13823
+ authContext: e2.authContext
13820
13824
  }))
13821
13825
  }));
13822
13826
  }
@@ -13968,11 +13972,13 @@ class LocalEventPublisher2 {
13968
13972
  }
13969
13973
  async publish(event) {
13970
13974
  const allChanges = [];
13975
+ const eventAuthContext = event.authContext ?? null;
13971
13976
  const storedEvent = {
13972
13977
  _id: event.id,
13973
13978
  type: event.type,
13974
13979
  payload: JSON.stringify(event.payload),
13975
- createdAt: event.createdAt.toISOString()
13980
+ createdAt: event.createdAt.toISOString(),
13981
+ authContext: eventAuthContext ? JSON.stringify(eventAuthContext) : null
13976
13982
  };
13977
13983
  allChanges.push({
13978
13984
  store: EVENT_TABLES.events,
@@ -14645,11 +14651,14 @@ class AggregateBase {
14645
14651
  if (!adapters.eventPublisher) {
14646
14652
  throw new Error(`Cannot emit event "${arcEvent.name}": no eventPublisher adapter available`);
14647
14653
  }
14654
+ const decoded = adapters.authAdapter?.getDecoded() ?? null;
14655
+ const authContext = decoded ? { tokenName: decoded.tokenName, params: decoded.params } : null;
14648
14656
  const eventInstance = {
14649
14657
  type: arcEvent.name,
14650
14658
  payload,
14651
14659
  id: `${Date.now()}_${Math.random().toString(36).slice(2)}`,
14652
- createdAt: new Date
14660
+ createdAt: new Date,
14661
+ authContext
14653
14662
  };
14654
14663
  await adapters.eventPublisher.publish(eventInstance);
14655
14664
  }
@@ -15347,7 +15356,8 @@ class StreamingEventPublisher {
15347
15356
  localId: event3.id,
15348
15357
  type: event3.type,
15349
15358
  payload: event3.payload,
15350
- createdAt: event3.createdAt.toISOString()
15359
+ createdAt: event3.createdAt.toISOString(),
15360
+ authContext: event3.authContext ?? null
15351
15361
  }
15352
15362
  ]);
15353
15363
  }
@@ -17221,11 +17231,14 @@ var init_dist = __esm(() => {
17221
17231
  if (!adapters.eventPublisher) {
17222
17232
  throw new Error(`Event "${this.data.name}" cannot be emitted: no eventPublisher adapter available`);
17223
17233
  }
17234
+ const decoded = adapters.authAdapter?.getDecoded() ?? null;
17235
+ const authContext = decoded ? { tokenName: decoded.tokenName, params: decoded.params } : null;
17224
17236
  const event = {
17225
17237
  type: this.data.name,
17226
17238
  payload,
17227
17239
  id: this.eventId.generate(),
17228
- createdAt: new Date
17240
+ createdAt: new Date,
17241
+ authContext
17229
17242
  };
17230
17243
  await adapters.eventPublisher.publish(event);
17231
17244
  }
@@ -17511,31 +17524,15 @@ var init_dist = __esm(() => {
17511
17524
  if (adapters.authAdapter?.isAuthenticated()) {
17512
17525
  return adapters;
17513
17526
  }
17514
- const allElements = [
17515
- ...this.data.queryElements,
17516
- ...this.data.mutationElements
17517
- ];
17518
- let tokenName = null;
17519
- for (const element of allElements) {
17520
- const protections = element.__aggregateProtections ?? element.data?.protections;
17521
- if (protections?.length > 0) {
17522
- tokenName = protections[0].token.name;
17523
- break;
17524
- }
17525
- }
17526
- if (!tokenName || !event2.payload) {
17527
+ const authContext = event2.authContext;
17528
+ if (!authContext) {
17527
17529
  return adapters;
17528
17530
  }
17529
17531
  const scopedAuth = new AuthAdapter;
17530
- scopedAuth.scopes = new Map([
17531
- ["default", {
17532
- raw: "",
17533
- decoded: {
17534
- tokenName,
17535
- params: event2.payload
17536
- }
17537
- }]
17538
- ]);
17532
+ scopedAuth.setDecoded({
17533
+ tokenName: authContext.tokenName,
17534
+ params: authContext.params
17535
+ });
17539
17536
  return { ...adapters, authAdapter: scopedAuth };
17540
17537
  }
17541
17538
  destroy() {
@@ -26348,19 +26345,20 @@ ${colors3.yellow}Type declaration errors:${colors3.reset}`);
26348
26345
  }
26349
26346
 
26350
26347
  // src/platform/shared.ts
26351
- import { copyFileSync, existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync6, readdirSync as readdirSync4, writeFileSync as writeFileSync6 } from "fs";
26352
- import { dirname as dirname6, join as join7 } from "path";
26348
+ import { copyFileSync, existsSync as existsSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync8, readdirSync as readdirSync5, rmSync as rmSync2, writeFileSync as writeFileSync7 } from "fs";
26349
+ import { dirname as dirname6, join as join9 } from "path";
26353
26350
 
26354
26351
  // src/builder/module-builder.ts
26355
26352
  import { execSync } from "child_process";
26356
26353
  import {
26357
- existsSync as existsSync5,
26358
- mkdirSync as mkdirSync5,
26359
- readFileSync as readFileSync5,
26360
- readdirSync as readdirSync3,
26361
- writeFileSync as writeFileSync5
26354
+ existsSync as existsSync7,
26355
+ mkdirSync as mkdirSync6,
26356
+ readFileSync as readFileSync7,
26357
+ readdirSync as readdirSync4,
26358
+ rmSync,
26359
+ writeFileSync as writeFileSync6
26362
26360
  } from "fs";
26363
- import { dirname as dirname5, join as join6, relative as relative2 } from "path";
26361
+ import { dirname as dirname5, join as join8, relative as relative3 } from "path";
26364
26362
 
26365
26363
  // src/i18n/index.ts
26366
26364
  import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
@@ -26633,43 +26631,154 @@ async function finalizeTranslations(rootDir, outDir, collector) {
26633
26631
  }
26634
26632
  }
26635
26633
 
26634
+ // src/builder/build-cache.ts
26635
+ import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "fs";
26636
+ import { join as join6 } from "path";
26637
+ var CACHE_VERSION = 1;
26638
+ var CACHE_FILE = ".build-cache.json";
26639
+ function emptyCache() {
26640
+ return { version: CACHE_VERSION, units: {} };
26641
+ }
26642
+ function loadBuildCache(arcDir) {
26643
+ const path4 = join6(arcDir, CACHE_FILE);
26644
+ if (!existsSync5(path4))
26645
+ return emptyCache();
26646
+ try {
26647
+ const raw = JSON.parse(readFileSync5(path4, "utf-8"));
26648
+ if (raw?.version !== CACHE_VERSION || typeof raw.units !== "object") {
26649
+ return emptyCache();
26650
+ }
26651
+ return raw;
26652
+ } catch {
26653
+ return emptyCache();
26654
+ }
26655
+ }
26656
+ function saveBuildCache(arcDir, cache) {
26657
+ mkdirSync5(arcDir, { recursive: true });
26658
+ writeFileSync5(join6(arcDir, CACHE_FILE), JSON.stringify(cache, null, 2));
26659
+ }
26660
+ function isCacheHit(cache, unitId, inputHash, requiredOutputs = []) {
26661
+ const entry = cache.units[unitId];
26662
+ if (!entry || entry.inputHash !== inputHash)
26663
+ return false;
26664
+ for (const out of requiredOutputs) {
26665
+ if (!existsSync5(out))
26666
+ return false;
26667
+ }
26668
+ return true;
26669
+ }
26670
+ function updateCache(cache, unitId, inputHash, output = {}) {
26671
+ cache.units[unitId] = {
26672
+ inputHash,
26673
+ ...output.outputHash !== undefined && { outputHash: output.outputHash },
26674
+ ...output.outputHashes !== undefined && { outputHashes: output.outputHashes }
26675
+ };
26676
+ }
26677
+
26678
+ // src/builder/hash.ts
26679
+ import { existsSync as existsSync6, readFileSync as readFileSync6, readdirSync as readdirSync3, statSync } from "fs";
26680
+ import { join as join7, relative as relative2, sep as sep2 } from "path";
26681
+ function sha256Hex(bytes) {
26682
+ const hasher = new Bun.CryptoHasher("sha256");
26683
+ hasher.update(bytes);
26684
+ return hasher.digest("hex");
26685
+ }
26686
+ function sha256OfFiles(paths) {
26687
+ const hasher = new Bun.CryptoHasher("sha256");
26688
+ const sorted = [...paths].sort();
26689
+ for (const p of sorted) {
26690
+ if (!existsSync6(p))
26691
+ continue;
26692
+ hasher.update(readFileSync6(p));
26693
+ hasher.update("\x00");
26694
+ }
26695
+ return hasher.digest("hex");
26696
+ }
26697
+ function sha256OfDir(dir, filter2) {
26698
+ if (!existsSync6(dir))
26699
+ return sha256Hex("");
26700
+ const hasher = new Bun.CryptoHasher("sha256");
26701
+ const entries = [];
26702
+ function walk(absDir) {
26703
+ for (const entry of readdirSync3(absDir, { withFileTypes: true })) {
26704
+ const abs = join7(absDir, entry.name);
26705
+ const rel = relative2(dir, abs).split(sep2).join("/");
26706
+ if (filter2 && !filter2(rel))
26707
+ continue;
26708
+ if (entry.isDirectory())
26709
+ walk(abs);
26710
+ else if (entry.isFile())
26711
+ entries.push({ rel, abs });
26712
+ }
26713
+ }
26714
+ walk(dir);
26715
+ entries.sort((a, b) => a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0);
26716
+ for (const { rel, abs } of entries) {
26717
+ hasher.update(rel);
26718
+ hasher.update("\x00");
26719
+ hasher.update(readFileSync6(abs));
26720
+ hasher.update("\x00");
26721
+ }
26722
+ return hasher.digest("hex");
26723
+ }
26724
+ function sha256OfJson(value) {
26725
+ return sha256Hex(stableStringify(value));
26726
+ }
26727
+ function stableStringify(value) {
26728
+ if (value === null || typeof value !== "object")
26729
+ return JSON.stringify(value);
26730
+ if (Array.isArray(value)) {
26731
+ return "[" + value.map(stableStringify).join(",") + "]";
26732
+ }
26733
+ const obj = value;
26734
+ const keys = Object.keys(obj).sort();
26735
+ return "{" + keys.map((k) => JSON.stringify(k) + ":" + stableStringify(obj[k])).join(",") + "}";
26736
+ }
26737
+ function readInstalledVersion(rootDir, pkgName) {
26738
+ const pkgJson = join7(rootDir, "node_modules", pkgName, "package.json");
26739
+ if (!existsSync6(pkgJson))
26740
+ return null;
26741
+ try {
26742
+ return JSON.parse(readFileSync6(pkgJson, "utf-8")).version ?? null;
26743
+ } catch {
26744
+ return null;
26745
+ }
26746
+ }
26747
+ function mtimeOf(path4) {
26748
+ if (!existsSync6(path4))
26749
+ return 0;
26750
+ try {
26751
+ return statSync(path4).mtimeMs;
26752
+ } catch {
26753
+ return 0;
26754
+ }
26755
+ }
26756
+
26757
+ // src/builder/parallel.ts
26758
+ import { cpus } from "os";
26759
+ var DEFAULT_CONCURRENCY = Math.max(1, cpus().length - 1);
26760
+ async function pAll(tasks, concurrency = DEFAULT_CONCURRENCY) {
26761
+ const results = new Array(tasks.length);
26762
+ let nextIndex = 0;
26763
+ const limit = Math.max(1, Math.min(concurrency, tasks.length));
26764
+ async function worker() {
26765
+ while (true) {
26766
+ const i = nextIndex++;
26767
+ if (i >= tasks.length)
26768
+ return;
26769
+ results[i] = await tasks[i]();
26770
+ }
26771
+ }
26772
+ const workers = Array.from({ length: limit }, () => worker());
26773
+ await Promise.all(workers);
26774
+ return results;
26775
+ }
26776
+
26636
26777
  // src/builder/module-builder.ts
26637
26778
  var CONTEXT_CLIENTS = [
26638
26779
  { name: "server", target: "bun", defines: { ONLY_SERVER: "true", ONLY_BROWSER: "false", ONLY_CLIENT: "false" } },
26639
26780
  { name: "browser", target: "browser", defines: { ONLY_SERVER: "false", ONLY_BROWSER: "true", ONLY_CLIENT: "true" } }
26640
26781
  ];
26641
- async function buildContextPackage(pkg) {
26642
- const entrypoint = pkg.entrypoint;
26643
- const outDir = join6(pkg.path, "dist");
26644
- const peerDeps = Object.keys(pkg.packageJson.peerDependencies || {});
26645
- const deps = Object.keys(pkg.packageJson.dependencies || {});
26646
- const externals = [...peerDeps, ...deps];
26647
- const allDeclErrors = [];
26648
- for (const client of CONTEXT_CLIENTS) {
26649
- const result = await Bun.build({
26650
- entrypoints: [entrypoint],
26651
- outdir: join6(outDir, client.name, "main"),
26652
- target: client.target,
26653
- format: "esm",
26654
- naming: "index.[ext]",
26655
- external: externals,
26656
- define: client.defines
26657
- });
26658
- if (!result.success) {
26659
- console.error(`Context ${client.name} build failed:`);
26660
- for (const log2 of result.logs)
26661
- console.error(log2);
26662
- throw new Error(`${client.name} build failed for ${pkg.name}`);
26663
- }
26664
- const globalsContent = Object.entries(client.defines).map(([k, v]) => `declare const ${k}: ${v};`).join(`
26665
- `);
26666
- const declResult = await buildTypeDeclarations([entrypoint], join6(outDir, client.name), dirname5(entrypoint), globalsContent);
26667
- if (!declResult.success && declResult.errors.length > 0) {
26668
- allDeclErrors.push(...declResult.errors.map((e) => `[${pkg.name}/${client.name}] ${e}`));
26669
- }
26670
- }
26671
- return { declarationErrors: allDeclErrors };
26672
- }
26673
26782
  var SHELL_EXTERNALS = [
26674
26783
  "react",
26675
26784
  "react-dom",
@@ -26683,57 +26792,41 @@ var SHELL_EXTERNALS = [
26683
26792
  "@arcote.tech/arc-workspace",
26684
26793
  "@arcote.tech/platform"
26685
26794
  ];
26686
- function sha256Hex(bytes) {
26687
- const hasher = new Bun.CryptoHasher("sha256");
26688
- hasher.update(bytes);
26689
- return hasher.digest("hex");
26690
- }
26691
- function sha256OfFiles(paths) {
26692
- const hasher = new Bun.CryptoHasher("sha256");
26693
- const sorted = [...paths].sort();
26694
- for (const p of sorted) {
26695
- if (!existsSync5(p))
26696
- continue;
26697
- hasher.update(readFileSync5(p));
26698
- hasher.update("\x00");
26699
- }
26700
- return hasher.digest("hex");
26701
- }
26702
26795
  function discoverPackages(rootDir) {
26703
- const rootPkg = JSON.parse(readFileSync5(join6(rootDir, "package.json"), "utf-8"));
26796
+ const rootPkg = JSON.parse(readFileSync7(join8(rootDir, "package.json"), "utf-8"));
26704
26797
  const workspaceGlobs = rootPkg.workspaces ?? [];
26705
26798
  const results = [];
26706
26799
  for (const glob2 of workspaceGlobs) {
26707
26800
  const base2 = glob2.replace("/*", "");
26708
- const baseDir = join6(rootDir, base2);
26709
- if (!existsSync5(baseDir))
26801
+ const baseDir = join8(rootDir, base2);
26802
+ if (!existsSync7(baseDir))
26710
26803
  continue;
26711
26804
  let entries;
26712
26805
  try {
26713
- entries = readdirSync3(baseDir);
26806
+ entries = readdirSync4(baseDir);
26714
26807
  } catch {
26715
26808
  continue;
26716
26809
  }
26717
26810
  for (const entry of entries) {
26718
- const pkgPath = join6(baseDir, entry, "package.json");
26719
- if (!existsSync5(pkgPath))
26811
+ const pkgPath = join8(baseDir, entry, "package.json");
26812
+ if (!existsSync7(pkgPath))
26720
26813
  continue;
26721
- const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
26814
+ const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
26722
26815
  if (pkg.name?.startsWith("@arcote.tech/"))
26723
26816
  continue;
26724
- const pkgDir = join6(baseDir, entry);
26817
+ const pkgDir = join8(baseDir, entry);
26725
26818
  const candidates = [
26726
- join6(pkgDir, "src", "index.ts"),
26727
- join6(pkgDir, "src", "index.tsx"),
26728
- join6(pkgDir, "index.ts"),
26729
- join6(pkgDir, "index.tsx")
26819
+ join8(pkgDir, "src", "index.ts"),
26820
+ join8(pkgDir, "src", "index.tsx"),
26821
+ join8(pkgDir, "index.ts"),
26822
+ join8(pkgDir, "index.tsx")
26730
26823
  ];
26731
- const entrypoint = candidates.find((c) => existsSync5(c)) ?? null;
26824
+ const entrypoint = candidates.find((c) => existsSync7(c)) ?? null;
26732
26825
  if (!entrypoint)
26733
26826
  continue;
26734
26827
  results.push({
26735
26828
  name: pkg.name,
26736
- path: join6(baseDir, entry),
26829
+ path: join8(baseDir, entry),
26737
26830
  entrypoint,
26738
26831
  packageJson: pkg
26739
26832
  });
@@ -26752,95 +26845,198 @@ function isContextPackage(pkg) {
26752
26845
  }
26753
26846
  return false;
26754
26847
  }
26755
- async function buildPackages(rootDir, outDir, packages) {
26756
- mkdirSync5(outDir, { recursive: true });
26757
- const contexts = packages.filter((p) => isContextPackage(p.packageJson));
26758
- const allDeclErrors = [];
26759
- for (const ctx of contexts) {
26760
- console.log(` Building context: ${ctx.name}`);
26761
- const { declarationErrors } = await buildContextPackage(ctx);
26762
- allDeclErrors.push(...declarationErrors);
26763
- }
26764
- if (allDeclErrors.length > 0) {
26765
- console.warn(`
26766
- \x1B[33mType declaration errors:\x1B[0m`);
26767
- for (const err of allDeclErrors) {
26768
- console.warn(` ${err}`);
26769
- }
26770
- console.warn("");
26771
- }
26772
- const tmpDir = join6(outDir, "_entries");
26773
- mkdirSync5(tmpDir, { recursive: true });
26774
- const entrypoints = [];
26775
- const fileToName = new Map;
26776
- for (const pkg of packages) {
26777
- const safeName = pkg.path.split("/").pop();
26778
- const moduleName = pkg.name.includes("/") ? pkg.name.split("/").pop() : pkg.name;
26779
- fileToName.set(safeName, moduleName);
26780
- const wrapperFile = join6(tmpDir, `${safeName}.ts`);
26781
- writeFileSync5(wrapperFile, `export * from "${pkg.name}";
26782
- `);
26783
- entrypoints.push(wrapperFile);
26848
+ var sourceFilter = (rel) => {
26849
+ if (rel.startsWith("dist/") || rel.startsWith("dist"))
26850
+ return false;
26851
+ if (rel.includes("/node_modules/") || rel.startsWith("node_modules"))
26852
+ return false;
26853
+ if (rel.startsWith(".arc/") || rel.startsWith(".arc"))
26854
+ return false;
26855
+ return true;
26856
+ };
26857
+ function pkgSourceHash(pkg) {
26858
+ return sha256OfDir(join8(pkg.path, "src"), sourceFilter);
26859
+ }
26860
+ function depVersionsHash(rootDir, pkg) {
26861
+ const peerDeps = Object.keys(pkg.packageJson.peerDependencies ?? {});
26862
+ const deps = Object.keys(pkg.packageJson.dependencies ?? {});
26863
+ const versions = {};
26864
+ for (const dep of [...peerDeps, ...deps].sort()) {
26865
+ versions[dep] = readInstalledVersion(rootDir, dep);
26866
+ }
26867
+ return sha256OfJson(versions);
26868
+ }
26869
+ async function buildContextClient(pkg, rootDir, client, cache, noCache) {
26870
+ const unitId = `context-pkg:${pkg.name}:${client.name}`;
26871
+ const outDir = join8(pkg.path, "dist", client.name);
26872
+ const inputHash = sha256OfJson({
26873
+ src: pkgSourceHash(pkg),
26874
+ pkg: pkg.packageJson,
26875
+ deps: depVersionsHash(rootDir, pkg),
26876
+ client: client.name,
26877
+ target: client.target,
26878
+ defines: client.defines
26879
+ });
26880
+ if (!noCache && isCacheHit(cache, unitId, inputHash, [join8(outDir, "main", "index.js")])) {
26881
+ console.log(` \u2713 cached: ${pkg.name} (${client.name})`);
26882
+ return { pkgName: pkg.name, client: client.name, declarationErrors: [], cached: true };
26784
26883
  }
26785
- console.log(` Bundling ${entrypoints.length} package(s)...`);
26786
- const i18nCollector = new Map;
26787
- const arcExternalPlugin = {
26788
- name: "arc-external",
26789
- setup(build2) {
26790
- build2.onResolve({ filter: /^@arcote\.tech\// }, (args) => {
26791
- return { path: args.path, external: true };
26792
- });
26793
- }
26794
- };
26884
+ console.log(` building: ${pkg.name} (${client.name})`);
26885
+ const peerDeps = Object.keys(pkg.packageJson.peerDependencies ?? {});
26886
+ const deps = Object.keys(pkg.packageJson.dependencies ?? {});
26887
+ const externals = [...peerDeps, ...deps];
26795
26888
  const result = await Bun.build({
26796
- entrypoints,
26797
- outdir: outDir,
26798
- splitting: true,
26889
+ entrypoints: [pkg.entrypoint],
26890
+ outdir: join8(outDir, "main"),
26891
+ target: client.target,
26799
26892
  format: "esm",
26800
- target: "browser",
26801
- external: SHELL_EXTERNALS,
26802
- plugins: [arcExternalPlugin, i18nExtractPlugin(i18nCollector, rootDir)],
26803
- naming: "[name].[ext]",
26804
- define: {
26805
- ONLY_SERVER: "false",
26806
- ONLY_BROWSER: "true",
26807
- ONLY_CLIENT: "true"
26808
- }
26893
+ naming: "index.[ext]",
26894
+ external: externals,
26895
+ define: client.defines
26809
26896
  });
26810
26897
  if (!result.success) {
26811
- console.error("Build failed:");
26898
+ console.error(`Context ${client.name} build failed:`);
26812
26899
  for (const log2 of result.logs)
26813
26900
  console.error(log2);
26814
- throw new Error("Module build failed");
26815
- }
26816
- await finalizeTranslations(rootDir, join6(outDir, ".."), i18nCollector);
26817
- const { rmSync } = await import("fs");
26818
- rmSync(tmpDir, { recursive: true, force: true });
26819
- const moduleEntries = result.outputs.filter((o) => o.kind === "entry-point").map((o) => {
26820
- const file = o.path.split("/").pop();
26821
- const safeName = file.replace(/\.js$/, "");
26822
- const bytes = readFileSync5(o.path);
26823
- return {
26824
- file,
26825
- name: fileToName.get(safeName) ?? safeName,
26826
- hash: sha256Hex(bytes)
26827
- };
26901
+ throw new Error(`${client.name} build failed for ${pkg.name}`);
26902
+ }
26903
+ const globalsContent = Object.entries(client.defines).map(([k, v]) => `declare const ${k}: ${v};`).join(`
26904
+ `);
26905
+ const declResult = await buildTypeDeclarations([pkg.entrypoint], outDir, dirname5(pkg.entrypoint), globalsContent);
26906
+ const declarationErrors = !declResult.success && declResult.errors.length > 0 ? declResult.errors.map((e) => `[${pkg.name}/${client.name}] ${e}`) : [];
26907
+ const outputHash = sha256OfDir(outDir);
26908
+ updateCache(cache, unitId, inputHash, { outputHash });
26909
+ return { pkgName: pkg.name, client: client.name, declarationErrors, cached: false };
26910
+ }
26911
+ async function buildContextPackages(rootDir, packages, cache, noCache) {
26912
+ const contexts = packages.filter((p) => isContextPackage(p.packageJson));
26913
+ if (contexts.length === 0)
26914
+ return { declarationErrors: [] };
26915
+ const tasks = contexts.flatMap((pkg) => CONTEXT_CLIENTS.map((client) => () => buildContextClient(pkg, rootDir, client, cache, noCache)));
26916
+ const results = await pAll(tasks);
26917
+ const declarationErrors = results.flatMap((r) => r.declarationErrors);
26918
+ if (declarationErrors.length > 0) {
26919
+ console.warn(`
26920
+ \x1B[33mType declaration errors:\x1B[0m`);
26921
+ for (const err of declarationErrors)
26922
+ console.warn(` ${err}`);
26923
+ console.warn("");
26924
+ }
26925
+ return { declarationErrors };
26926
+ }
26927
+ async function buildModulesBundle(rootDir, outDir, packages, cache, noCache) {
26928
+ mkdirSync6(outDir, { recursive: true });
26929
+ const unitId = "modules-bundle";
26930
+ const pkgHashes = packages.map((p) => ({
26931
+ name: p.name,
26932
+ safeName: p.path.split("/").pop(),
26933
+ srcHash: pkgSourceHash(p)
26934
+ }));
26935
+ const inputHash = sha256OfJson({
26936
+ pkgHashes,
26937
+ externals: SHELL_EXTERNALS,
26938
+ define: { ONLY_SERVER: "false", ONLY_BROWSER: "true", ONLY_CLIENT: "true" }
26828
26939
  });
26829
- const manifest = {
26830
- modules: moduleEntries,
26831
- shellHash: "",
26832
- stylesHash: "",
26833
- buildTime: new Date().toISOString()
26834
- };
26835
- writeFileSync5(join6(outDir, "manifest.json"), JSON.stringify(manifest, null, 2));
26836
- return manifest;
26940
+ if (!noCache && isCacheHit(cache, unitId, inputHash)) {
26941
+ const existing = cache.units[unitId]?.outputHashes ?? {};
26942
+ const modules = [];
26943
+ for (const { safeName, name } of pkgHashes) {
26944
+ const file = `${safeName}.js`;
26945
+ const filePath = join8(outDir, file);
26946
+ if (!existsSync7(filePath)) {
26947
+ console.log(` rebuilding modules-bundle: output ${file} missing`);
26948
+ return await actuallyBuild();
26949
+ }
26950
+ modules.push({ file, name, hash: existing[safeName] ?? sha256Hex(readFileSync7(filePath)) });
26951
+ }
26952
+ console.log(` \u2713 cached: modules-bundle (${modules.length} module(s))`);
26953
+ return { modules, cached: true };
26954
+ }
26955
+ return await actuallyBuild();
26956
+ async function actuallyBuild() {
26957
+ console.log(` building: modules-bundle (${packages.length} package(s))`);
26958
+ const tmpDir = join8(outDir, "_entries");
26959
+ mkdirSync6(tmpDir, { recursive: true });
26960
+ const entrypoints = [];
26961
+ const fileToName = new Map;
26962
+ for (const pkg of packages) {
26963
+ const safeName = pkg.path.split("/").pop();
26964
+ const moduleName = pkg.name.includes("/") ? pkg.name.split("/").pop() : pkg.name;
26965
+ fileToName.set(safeName, moduleName);
26966
+ const wrapperFile = join8(tmpDir, `${safeName}.ts`);
26967
+ writeFileSync6(wrapperFile, `export * from "${pkg.name}";
26968
+ `);
26969
+ entrypoints.push(wrapperFile);
26970
+ }
26971
+ const i18nCollector = new Map;
26972
+ const arcExternalPlugin = {
26973
+ name: "arc-external",
26974
+ setup(build2) {
26975
+ build2.onResolve({ filter: /^@arcote\.tech\// }, (args) => {
26976
+ return { path: args.path, external: true };
26977
+ });
26978
+ }
26979
+ };
26980
+ const result = await Bun.build({
26981
+ entrypoints,
26982
+ outdir: outDir,
26983
+ splitting: true,
26984
+ format: "esm",
26985
+ target: "browser",
26986
+ external: SHELL_EXTERNALS,
26987
+ plugins: [arcExternalPlugin, i18nExtractPlugin(i18nCollector, rootDir)],
26988
+ naming: "[name].[ext]",
26989
+ define: {
26990
+ ONLY_SERVER: "false",
26991
+ ONLY_BROWSER: "true",
26992
+ ONLY_CLIENT: "true"
26993
+ }
26994
+ });
26995
+ if (!result.success) {
26996
+ console.error("Modules bundle build failed:");
26997
+ for (const log2 of result.logs)
26998
+ console.error(log2);
26999
+ throw new Error("Module build failed");
27000
+ }
27001
+ await finalizeTranslations(rootDir, join8(outDir, ".."), i18nCollector);
27002
+ rmSync(tmpDir, { recursive: true, force: true });
27003
+ const outputHashes = {};
27004
+ const modules = result.outputs.filter((o) => o.kind === "entry-point").map((o) => {
27005
+ const file = o.path.split("/").pop();
27006
+ const safeName = file.replace(/\.js$/, "");
27007
+ const bytes = readFileSync7(o.path);
27008
+ const hash = sha256Hex(bytes);
27009
+ outputHashes[safeName] = hash;
27010
+ return {
27011
+ file,
27012
+ name: fileToName.get(safeName) ?? safeName,
27013
+ hash
27014
+ };
27015
+ });
27016
+ updateCache(cache, unitId, inputHash, { outputHashes });
27017
+ return { modules, cached: false };
27018
+ }
27019
+ }
27020
+ async function buildTranslations(rootDir, arcDir, cache, noCache) {
27021
+ const localesDir = join8(rootDir, "locales");
27022
+ if (!existsSync7(localesDir))
27023
+ return;
27024
+ const unitId = "translations";
27025
+ const poFiles = readdirSync4(localesDir).filter((f) => f.endsWith(".po")).map((f) => join8(localesDir, f));
27026
+ if (poFiles.length === 0)
27027
+ return;
27028
+ const inputHash = sha256OfFiles(poFiles);
27029
+ if (!noCache && isCacheHit(cache, unitId, inputHash, [join8(arcDir, "locales")])) {
27030
+ console.log(` \u2713 cached: translations`);
27031
+ return;
27032
+ }
27033
+ console.log(` building: translations (${poFiles.length} catalog(s))`);
27034
+ compileAllCatalogs(localesDir, join8(arcDir, "locales"));
27035
+ const jsonFiles = readdirSync4(join8(arcDir, "locales")).filter((f) => f.endsWith(".json")).map((f) => join8(arcDir, "locales", f));
27036
+ const outputHash = sha256OfFiles(jsonFiles);
27037
+ updateCache(cache, unitId, inputHash, { outputHash });
26837
27038
  }
26838
- async function buildStyles(rootDir, outDir) {
26839
- mkdirSync5(outDir, { recursive: true });
26840
- const inputCss = join6(outDir, "_input.css");
26841
- const outputCss = join6(outDir, "styles.css");
26842
- const rootRel = relative2(outDir, rootDir).replace(/\\/g, "/");
26843
- writeFileSync5(inputCss, `@import "tailwindcss";
27039
+ var TAILWIND_INPUT_TEMPLATE = (rootRel) => `@import "tailwindcss";
26844
27040
  @import "tw-animate-css";
26845
27041
 
26846
27042
  @source "${rootRel}/packages/*/*.{ts,tsx}";
@@ -26898,12 +27094,61 @@ async function buildStyles(rootDir, outDir) {
26898
27094
  min-height: 100vh;
26899
27095
  }
26900
27096
  }
26901
- `);
26902
- console.log(" Building CSS...");
27097
+ `;
27098
+ async function buildStyles(rootDir, arcDir, packages, themePath, cache, noCache) {
27099
+ mkdirSync6(arcDir, { recursive: true });
27100
+ const inputCss = join8(arcDir, "_input.css");
27101
+ const outputCss = join8(arcDir, "styles.css");
27102
+ const themeOutput = join8(arcDir, "theme.css");
27103
+ const rootRel = relative3(arcDir, rootDir).replace(/\\/g, "/");
27104
+ const inputCssContent = TAILWIND_INPUT_TEMPLATE(rootRel);
27105
+ const tsxFilter = (rel) => {
27106
+ if (!sourceFilter(rel))
27107
+ return false;
27108
+ if (/\.[^/]+$/.test(rel) && !/\.(ts|tsx)$/.test(rel))
27109
+ return false;
27110
+ return true;
27111
+ };
27112
+ const wsHashes = {};
27113
+ for (const p of packages) {
27114
+ wsHashes[p.name] = sha256OfDir(join8(p.path, "src"), tsxFilter);
27115
+ }
27116
+ const platformSrc = join8(rootDir, "node_modules", "@arcote.tech", "platform", "src");
27117
+ const arcDsSrc = join8(rootDir, "node_modules", "@arcote.tech", "arc-ds", "src");
27118
+ const frameworkHashes = {
27119
+ platform: sha256OfDir(platformSrc, tsxFilter),
27120
+ arcDs: sha256OfDir(arcDsSrc, tsxFilter)
27121
+ };
27122
+ const themeContent = themePath && existsSync7(join8(rootDir, themePath)) ? readFileSync7(join8(rootDir, themePath)) : null;
27123
+ const themeHash = themeContent ? sha256Hex(themeContent) : null;
27124
+ const unitId = "styles";
27125
+ const inputHash = sha256OfJson({
27126
+ workspaces: wsHashes,
27127
+ framework: frameworkHashes,
27128
+ inputCss: inputCssContent,
27129
+ themeHash
27130
+ });
27131
+ const requiredOutputs = [outputCss];
27132
+ if (themePath)
27133
+ requiredOutputs.push(themeOutput);
27134
+ if (!noCache && isCacheHit(cache, unitId, inputHash, requiredOutputs)) {
27135
+ console.log(` \u2713 cached: styles`);
27136
+ return;
27137
+ }
27138
+ console.log(` building: styles`);
27139
+ writeFileSync6(inputCss, inputCssContent);
26903
27140
  execSync(`bunx @tailwindcss/cli -i ${inputCss} -o ${outputCss} --minify`, {
26904
27141
  cwd: rootDir,
26905
27142
  stdio: "inherit"
26906
27143
  });
27144
+ if (themePath && themeContent) {
27145
+ writeFileSync6(themeOutput, themeContent);
27146
+ }
27147
+ const outFiles = [outputCss];
27148
+ if (themePath && existsSync7(themeOutput))
27149
+ outFiles.push(themeOutput);
27150
+ const outputHash = sha256OfFiles(outFiles);
27151
+ updateCache(cache, unitId, inputHash, { outputHash });
26907
27152
  }
26908
27153
 
26909
27154
  // src/platform/shared.ts
@@ -26923,9 +27168,9 @@ function resolveWorkspace() {
26923
27168
  process.exit(1);
26924
27169
  }
26925
27170
  const rootDir = dirname6(packageJsonPath);
26926
- const rootPkg = JSON.parse(readFileSync6(packageJsonPath, "utf-8"));
27171
+ const rootPkg = JSON.parse(readFileSync8(packageJsonPath, "utf-8"));
26927
27172
  const appName = rootPkg.name ?? "Arc App";
26928
- const arcDir = join7(rootDir, ".arc", "platform");
27173
+ const arcDir = join9(rootDir, ".arc", "platform");
26929
27174
  log2("Scanning workspaces...");
26930
27175
  const packages = discoverPackages(rootDir);
26931
27176
  ok(`Found ${packages.length} package(s): ${packages.map((p) => p.name).join(", ")}`);
@@ -26935,10 +27180,10 @@ function resolveWorkspace() {
26935
27180
  }
26936
27181
  let manifest;
26937
27182
  for (const name of ["manifest.json", "manifest.webmanifest"]) {
26938
- const manifestPath = join7(rootDir, name);
26939
- if (existsSync6(manifestPath)) {
27183
+ const manifestPath = join9(rootDir, name);
27184
+ if (existsSync8(manifestPath)) {
26940
27185
  try {
26941
- const data = JSON.parse(readFileSync6(manifestPath, "utf-8"));
27186
+ const data = JSON.parse(readFileSync8(manifestPath, "utf-8"));
26942
27187
  const icons = data.icons;
26943
27188
  manifest = {
26944
27189
  path: manifestPath,
@@ -26957,61 +27202,135 @@ function resolveWorkspace() {
26957
27202
  rootPkg,
26958
27203
  appName,
26959
27204
  arcDir,
26960
- modulesDir: join7(arcDir, "modules"),
26961
- shellDir: join7(arcDir, "shell"),
26962
- publicDir: join7(rootDir, "public"),
27205
+ modulesDir: join9(arcDir, "modules"),
27206
+ shellDir: join9(arcDir, "shell"),
27207
+ assetsDir: join9(arcDir, "assets"),
27208
+ publicDir: join9(rootDir, "public"),
26963
27209
  packages,
26964
27210
  manifest
26965
27211
  };
26966
27212
  }
26967
- async function buildAll(ws) {
26968
- log2("Building packages...");
26969
- const manifest = await buildPackages(ws.rootDir, ws.modulesDir, ws.packages);
26970
- ok(`Built ${manifest.modules.length} module(s)`);
26971
- log2("Building styles...");
26972
- await buildStyles(ws.rootDir, ws.arcDir);
26973
- ok("Styles built");
27213
+ async function buildAll(ws, opts = {}) {
27214
+ const cache = loadBuildCache(ws.arcDir);
27215
+ const noCache = opts.noCache ?? false;
26974
27216
  const themePath = ws.rootPkg.arc?.theme;
26975
- if (themePath) {
26976
- const src = join7(ws.rootDir, themePath);
26977
- if (existsSync6(src)) {
26978
- copyFileSync(src, join7(ws.arcDir, "theme.css"));
26979
- ok("Theme copied");
26980
- }
26981
- }
26982
- log2("Building shell...");
26983
- await buildShell(ws.shellDir, ws.packages);
26984
- ok("Shell built");
26985
- const finalManifest = finalizeManifest(ws, manifest);
26986
- writeFileSync6(join7(ws.modulesDir, "manifest.json"), JSON.stringify(finalManifest, null, 2));
27217
+ log2(`Building (concurrency parallel${noCache ? ", no-cache" : ""})...`);
27218
+ const [, modulesResult] = await Promise.all([
27219
+ buildContextPackages(ws.rootDir, ws.packages, cache, noCache),
27220
+ buildModulesBundle(ws.rootDir, ws.modulesDir, ws.packages, cache, noCache),
27221
+ buildShell(ws, cache, noCache),
27222
+ buildStyles(ws.rootDir, ws.arcDir, ws.packages, themePath, cache, noCache),
27223
+ copyBrowserAssets(ws, cache, noCache),
27224
+ buildTranslations(ws.rootDir, ws.arcDir, cache, noCache)
27225
+ ]);
27226
+ saveBuildCache(ws.arcDir, cache);
27227
+ const finalManifest = assembleManifest(ws, modulesResult.modules, cache);
27228
+ writeFileSync7(join9(ws.modulesDir, "manifest.json"), JSON.stringify(finalManifest, null, 2));
26987
27229
  return finalManifest;
26988
27230
  }
26989
- function finalizeManifest(ws, manifest) {
26990
- const shellFiles = listFilesRec(ws.shellDir);
26991
- const stylesFiles = [
26992
- join7(ws.arcDir, "styles.css"),
26993
- join7(ws.arcDir, "theme.css")
26994
- ].filter((p) => existsSync6(p));
27231
+ function assembleManifest(ws, modules, cache) {
27232
+ const shellEntries = {};
27233
+ for (const [unitId, entry] of Object.entries(cache.units)) {
27234
+ if (unitId.startsWith("shell:") && entry.outputHash) {
27235
+ shellEntries[unitId] = entry.outputHash;
27236
+ }
27237
+ }
27238
+ const shellHash = sha256OfJson(shellEntries);
27239
+ const stylesHash = cache.units["styles"]?.outputHash ?? "";
26995
27240
  return {
26996
- modules: manifest.modules,
26997
- shellHash: sha256OfFiles(shellFiles),
26998
- stylesHash: sha256OfFiles(stylesFiles),
26999
- buildTime: manifest.buildTime
27241
+ modules,
27242
+ shellHash,
27243
+ stylesHash,
27244
+ buildTime: new Date().toISOString()
27000
27245
  };
27001
27246
  }
27002
- function listFilesRec(dir) {
27003
- if (!existsSync6(dir))
27247
+ function resolveAssetSource(from, pkgDir, rootDir) {
27248
+ if (from.startsWith("./") || from.startsWith("../")) {
27249
+ const resolved = join9(pkgDir, from);
27250
+ return existsSync8(resolved) ? resolved : null;
27251
+ }
27252
+ const candidates = [
27253
+ join9(rootDir, "node_modules", from),
27254
+ join9(pkgDir, "node_modules", from)
27255
+ ];
27256
+ for (const c of candidates) {
27257
+ if (existsSync8(c))
27258
+ return c;
27259
+ }
27260
+ const bunCacheDir = join9(rootDir, "node_modules", ".bun");
27261
+ if (existsSync8(bunCacheDir)) {
27262
+ for (const entry of readdirSync5(bunCacheDir, { withFileTypes: true })) {
27263
+ if (!entry.isDirectory())
27264
+ continue;
27265
+ const candidate = join9(bunCacheDir, entry.name, "node_modules", from);
27266
+ if (existsSync8(candidate))
27267
+ return candidate;
27268
+ }
27269
+ }
27270
+ return null;
27271
+ }
27272
+ function readBrowserAssets(pkgDir) {
27273
+ const pkgJsonPath = join9(pkgDir, "package.json");
27274
+ if (!existsSync8(pkgJsonPath))
27275
+ return [];
27276
+ try {
27277
+ const pkg = JSON.parse(readFileSync8(pkgJsonPath, "utf-8"));
27278
+ const assets = pkg.arc?.browserAssets;
27279
+ if (!Array.isArray(assets))
27280
+ return [];
27281
+ return assets.filter((a) => typeof a?.from === "string" && typeof a?.to === "string");
27282
+ } catch {
27283
+ return [];
27284
+ }
27285
+ }
27286
+ function discoverBrowserAssets(ws) {
27287
+ const arcDir = join9(ws.rootDir, "node_modules", "@arcote.tech");
27288
+ if (!existsSync8(arcDir))
27004
27289
  return [];
27005
27290
  const out = [];
27006
- for (const entry of readdirSync4(dir, { withFileTypes: true })) {
27007
- const p = join7(dir, entry.name);
27008
- if (entry.isDirectory())
27009
- out.push(...listFilesRec(p));
27010
- else if (entry.isFile())
27011
- out.push(p);
27291
+ for (const entry of readdirSync5(arcDir, { withFileTypes: true })) {
27292
+ if (!entry.isDirectory() && !entry.isSymbolicLink())
27293
+ continue;
27294
+ const pkgDir = join9(arcDir, entry.name);
27295
+ const assets = readBrowserAssets(pkgDir);
27296
+ for (const asset of assets) {
27297
+ const src = resolveAssetSource(asset.from, pkgDir, ws.rootDir);
27298
+ if (!src) {
27299
+ err(`browserAsset not found: ${asset.from} (from @arcote.tech/${entry.name})`);
27300
+ continue;
27301
+ }
27302
+ out.push({ arcPkg: entry.name, from: asset.from, to: asset.to, src });
27303
+ }
27012
27304
  }
27013
27305
  return out;
27014
27306
  }
27307
+ async function copyBrowserAssets(ws, cache, noCache) {
27308
+ mkdirSync7(ws.assetsDir, { recursive: true });
27309
+ const assets = discoverBrowserAssets(ws);
27310
+ if (assets.length === 0)
27311
+ return;
27312
+ const unitId = "browser-assets";
27313
+ const inputHash = sha256OfJson(assets.map((a) => ({
27314
+ arcPkg: a.arcPkg,
27315
+ from: a.from,
27316
+ to: a.to,
27317
+ mtime: mtimeOf(a.src)
27318
+ })));
27319
+ const requiredOutputs = assets.map((a) => join9(ws.assetsDir, a.to));
27320
+ if (!noCache && isCacheHit(cache, unitId, inputHash, requiredOutputs)) {
27321
+ console.log(` \u2713 cached: browser-assets (${assets.length})`);
27322
+ return;
27323
+ }
27324
+ console.log(` building: browser-assets (${assets.length})`);
27325
+ const outputHashes = {};
27326
+ for (const asset of assets) {
27327
+ const dest = join9(ws.assetsDir, asset.to);
27328
+ mkdirSync7(dirname6(dest), { recursive: true });
27329
+ copyFileSync(asset.src, dest);
27330
+ outputHashes[asset.to] = sha256Hex(readFileSync8(dest));
27331
+ }
27332
+ updateCache(cache, unitId, inputHash, { outputHashes });
27333
+ }
27015
27334
  function collectArcPeerDeps(packages) {
27016
27335
  const seen = new Set;
27017
27336
  for (const pkg of ["@arcote.tech/arc", "@arcote.tech/arc-ds", "@arcote.tech/arc-react", "@arcote.tech/platform"]) {
@@ -27029,14 +27348,10 @@ function collectArcPeerDeps(packages) {
27029
27348
  return [short, pkg];
27030
27349
  });
27031
27350
  }
27032
- async function buildShell(outDir, packages) {
27033
- mkdirSync6(outDir, { recursive: true });
27034
- const tmpDir = join7(outDir, "_tmp");
27035
- mkdirSync6(tmpDir, { recursive: true });
27036
- const reactEntries = [
27037
- [
27038
- "react",
27039
- `import React from "react";
27351
+ var REACT_ENTRIES = [
27352
+ [
27353
+ "react",
27354
+ `import React from "react";
27040
27355
  export default React;
27041
27356
  export const {
27042
27357
  Children, Component, Fragment, Profiler, PureComponent, StrictMode, Suspense,
@@ -27046,84 +27361,133 @@ export const {
27046
27361
  useLayoutEffect, useMemo, useReducer, useRef, useState, useSyncExternalStore,
27047
27362
  useTransition, version, useActionState, useOptimistic,
27048
27363
  } = React;`
27049
- ],
27050
- ["jsx-runtime", `export { jsx, jsxs, Fragment } from "react/jsx-runtime";`],
27051
- [
27052
- "jsx-dev-runtime",
27053
- `export { jsxDEV, Fragment } from "react/jsx-dev-runtime";`
27054
- ],
27055
- [
27056
- "react-dom",
27057
- `import ReactDOM from "react-dom";
27364
+ ],
27365
+ ["jsx-runtime", `export { jsx, jsxs, Fragment } from "react/jsx-runtime";`],
27366
+ [
27367
+ "jsx-dev-runtime",
27368
+ `export { jsxDEV, Fragment } from "react/jsx-dev-runtime";`
27369
+ ],
27370
+ [
27371
+ "react-dom",
27372
+ `import ReactDOM from "react-dom";
27058
27373
  export default ReactDOM;
27059
27374
  export const { createPortal, flushSync } = ReactDOM;`
27060
- ],
27061
- [
27062
- "react-dom-client",
27063
- `export { createRoot, hydrateRoot } from "react-dom/client";`
27064
- ]
27065
- ];
27375
+ ],
27376
+ [
27377
+ "react-dom-client",
27378
+ `export { createRoot, hydrateRoot } from "react-dom/client";`
27379
+ ]
27380
+ ];
27381
+ var REACT_OUTPUT_FILES = REACT_ENTRIES.map(([n]) => `${n}.js`);
27382
+ var SHELL_BASE_EXTERNAL = [
27383
+ "react",
27384
+ "react-dom",
27385
+ "react/jsx-runtime",
27386
+ "react/jsx-dev-runtime",
27387
+ "react-dom/client"
27388
+ ];
27389
+ var sourceFilter2 = (rel) => {
27390
+ if (rel.startsWith("dist/") || rel.startsWith("dist"))
27391
+ return false;
27392
+ if (rel.includes("/node_modules/") || rel.startsWith("node_modules"))
27393
+ return false;
27394
+ if (rel.startsWith(".arc/") || rel.startsWith(".arc"))
27395
+ return false;
27396
+ return true;
27397
+ };
27398
+ function arcPkgSrcHash(rootDir, pkg) {
27399
+ const srcDir = join9(rootDir, "node_modules", pkg, "src");
27400
+ if (existsSync8(srcDir))
27401
+ return sha256OfDir(srcDir, sourceFilter2);
27402
+ return sha256OfDir(join9(rootDir, "node_modules", pkg), sourceFilter2);
27403
+ }
27404
+ async function buildShellReact(shellDir, tmpDir, rootDir, cache, noCache) {
27405
+ const unitId = "shell:react";
27406
+ const inputHash = sha256OfJson({
27407
+ react: readInstalledVersion(rootDir, "react"),
27408
+ "react-dom": readInstalledVersion(rootDir, "react-dom"),
27409
+ entries: REACT_ENTRIES.map(([k, v]) => [k, v])
27410
+ });
27411
+ const requiredOutputs = REACT_OUTPUT_FILES.map((f) => join9(shellDir, f));
27412
+ if (!noCache && isCacheHit(cache, unitId, inputHash, requiredOutputs)) {
27413
+ console.log(` \u2713 cached: shell:react`);
27414
+ return;
27415
+ }
27416
+ console.log(` building: shell:react`);
27066
27417
  const reactEps = [];
27067
- for (const [name, code] of reactEntries) {
27068
- const f = join7(tmpDir, `${name}.ts`);
27069
- Bun.write(f, code);
27418
+ for (const [name, code] of REACT_ENTRIES) {
27419
+ const f = join9(tmpDir, `${name}.ts`);
27420
+ await Bun.write(f, code);
27070
27421
  reactEps.push(f);
27071
27422
  }
27072
- const r1 = await Bun.build({
27423
+ const r = await Bun.build({
27073
27424
  entrypoints: reactEps,
27074
- outdir: outDir,
27425
+ outdir: shellDir,
27075
27426
  splitting: true,
27076
27427
  format: "esm",
27077
27428
  target: "browser",
27078
27429
  naming: "[name].[ext]"
27079
27430
  });
27080
- if (!r1.success) {
27081
- for (const l of r1.logs)
27431
+ if (!r.success) {
27432
+ for (const l of r.logs)
27082
27433
  console.error(l);
27083
27434
  throw new Error("Shell React build failed");
27084
27435
  }
27085
- const arcEntries = packages ? collectArcPeerDeps(packages) : [
27086
- ["arc", "@arcote.tech/arc"],
27087
- ["arc-ds", "@arcote.tech/arc-ds"],
27088
- ["arc-react", "@arcote.tech/arc-react"],
27089
- ["platform", "@arcote.tech/platform"]
27090
- ];
27091
- const baseExternal = [
27092
- "react",
27093
- "react-dom",
27094
- "react/jsx-runtime",
27095
- "react/jsx-dev-runtime",
27096
- "react-dom/client"
27097
- ];
27098
- const allArcPkgs = arcEntries.map(([, pkg]) => pkg);
27099
- for (const [name, pkg] of arcEntries) {
27100
- const f = join7(tmpDir, `${name}.ts`);
27101
- Bun.write(f, `export * from "${pkg}";
27436
+ const outputHash = sha256OfFiles(requiredOutputs);
27437
+ updateCache(cache, unitId, inputHash, { outputHash });
27438
+ }
27439
+ async function buildShellArcEntry(shortName, pkg, allArcPkgs, shellDir, tmpDir, rootDir, cache, noCache) {
27440
+ const unitId = `shell:arc:${shortName}`;
27441
+ const otherExternals = allArcPkgs.filter((p) => p !== pkg);
27442
+ const inputHash = sha256OfJson({
27443
+ pkg,
27444
+ version: readInstalledVersion(rootDir, pkg),
27445
+ src: arcPkgSrcHash(rootDir, pkg),
27446
+ base: SHELL_BASE_EXTERNAL,
27447
+ others: [...otherExternals].sort()
27448
+ });
27449
+ const outputFile = join9(shellDir, `${shortName}.js`);
27450
+ if (!noCache && isCacheHit(cache, unitId, inputHash, [outputFile])) {
27451
+ console.log(` \u2713 cached: ${unitId}`);
27452
+ return;
27453
+ }
27454
+ console.log(` building: ${unitId}`);
27455
+ const f = join9(tmpDir, `${shortName}.ts`);
27456
+ await Bun.write(f, `export * from "${pkg}";
27102
27457
  `);
27103
- const r2 = await Bun.build({
27104
- entrypoints: [f],
27105
- outdir: outDir,
27106
- format: "esm",
27107
- target: "browser",
27108
- naming: "[name].[ext]",
27109
- external: [
27110
- ...baseExternal,
27111
- ...allArcPkgs.filter((p) => p !== pkg)
27112
- ],
27113
- define: {
27114
- ONLY_SERVER: "false",
27115
- ONLY_BROWSER: "true",
27116
- ONLY_CLIENT: "true"
27117
- }
27118
- });
27119
- if (!r2.success) {
27120
- for (const l of r2.logs)
27121
- console.error(l);
27122
- throw new Error(`Shell build failed for ${pkg}`);
27458
+ const r = await Bun.build({
27459
+ entrypoints: [f],
27460
+ outdir: shellDir,
27461
+ format: "esm",
27462
+ target: "browser",
27463
+ naming: "[name].[ext]",
27464
+ external: [...SHELL_BASE_EXTERNAL, ...otherExternals],
27465
+ define: {
27466
+ ONLY_SERVER: "false",
27467
+ ONLY_BROWSER: "true",
27468
+ ONLY_CLIENT: "true"
27123
27469
  }
27470
+ });
27471
+ if (!r.success) {
27472
+ for (const l of r.logs)
27473
+ console.error(l);
27474
+ throw new Error(`Shell build failed for ${pkg}`);
27124
27475
  }
27125
- const { rmSync } = await import("fs");
27126
- rmSync(tmpDir, { recursive: true, force: true });
27476
+ const outputHash = sha256OfFiles([outputFile]);
27477
+ updateCache(cache, unitId, inputHash, { outputHash });
27478
+ }
27479
+ async function buildShell(ws, cache, noCache) {
27480
+ mkdirSync7(ws.shellDir, { recursive: true });
27481
+ const tmpDir = join9(ws.shellDir, "_tmp");
27482
+ mkdirSync7(tmpDir, { recursive: true });
27483
+ const arcEntries = collectArcPeerDeps(ws.packages);
27484
+ const allArcPkgs = arcEntries.map(([, pkg]) => pkg);
27485
+ const tasks = [
27486
+ () => buildShellReact(ws.shellDir, tmpDir, ws.rootDir, cache, noCache),
27487
+ ...arcEntries.map(([short, pkg]) => () => buildShellArcEntry(short, pkg, allArcPkgs, ws.shellDir, tmpDir, ws.rootDir, cache, noCache))
27488
+ ];
27489
+ await pAll(tasks);
27490
+ rmSync2(tmpDir, { recursive: true, force: true });
27127
27491
  }
27128
27492
  async function loadServerContext(packages) {
27129
27493
  const ctxPackages = packages.filter((p) => isContextPackage(p.packageJson));
@@ -27132,13 +27496,13 @@ async function loadServerContext(packages) {
27132
27496
  globalThis.ONLY_SERVER = true;
27133
27497
  globalThis.ONLY_BROWSER = false;
27134
27498
  globalThis.ONLY_CLIENT = false;
27135
- const platformDir = join7(process.cwd(), "node_modules", "@arcote.tech", "platform");
27136
- const platformPkg = JSON.parse(readFileSync6(join7(platformDir, "package.json"), "utf-8"));
27137
- const platformEntry = join7(platformDir, platformPkg.main ?? "src/index.ts");
27499
+ const platformDir = join9(process.cwd(), "node_modules", "@arcote.tech", "platform");
27500
+ const platformPkg = JSON.parse(readFileSync8(join9(platformDir, "package.json"), "utf-8"));
27501
+ const platformEntry = join9(platformDir, platformPkg.main ?? "src/index.ts");
27138
27502
  await import(platformEntry);
27139
27503
  for (const ctx of ctxPackages) {
27140
- const serverDist = join7(ctx.path, "dist", "server", "main", "index.js");
27141
- if (!existsSync6(serverDist)) {
27504
+ const serverDist = join9(ctx.path, "dist", "server", "main", "index.js");
27505
+ if (!existsSync8(serverDist)) {
27142
27506
  err(`Context server dist not found: ${serverDist}`);
27143
27507
  continue;
27144
27508
  }
@@ -27162,26 +27526,26 @@ async function loadServerContext(packages) {
27162
27526
  }
27163
27527
 
27164
27528
  // src/commands/platform-build.ts
27165
- async function platformBuild() {
27529
+ async function platformBuild(opts = {}) {
27166
27530
  const ws = resolveWorkspace();
27167
- const manifest = await buildAll(ws);
27531
+ const manifest = await buildAll(ws, { noCache: opts.noCache });
27168
27532
  ok(`Platform built \u2014 ${manifest.modules.length} module(s)`);
27169
27533
  }
27170
27534
 
27171
27535
  // src/commands/platform-deploy.ts
27172
- import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
27173
- import { join as join13 } from "path";
27536
+ import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
27537
+ import { join as join15 } from "path";
27174
27538
 
27175
27539
  // src/deploy/bootstrap.ts
27176
- import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync10 } from "fs";
27540
+ import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync11 } from "fs";
27177
27541
  import { tmpdir as tmpdir3 } from "os";
27178
- import { join as join11 } from "path";
27542
+ import { join as join13 } from "path";
27179
27543
 
27180
27544
  // src/deploy/ansible.ts
27181
27545
  var {spawn: spawn2 } = globalThis.Bun;
27182
- import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync7 } from "fs";
27546
+ import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync8 } from "fs";
27183
27547
  import { tmpdir } from "os";
27184
- import { join as join8 } from "path";
27548
+ import { join as join10 } from "path";
27185
27549
 
27186
27550
  // src/deploy/assets.ts
27187
27551
  var TERRAFORM_MAIN_TF = `terraform {
@@ -27438,18 +27802,18 @@ var ASSETS = {
27438
27802
  }
27439
27803
  };
27440
27804
  async function materializeAssets(targetDir, files) {
27441
- const { mkdirSync: mkdirSync7, writeFileSync: writeFileSync7 } = await import("fs");
27442
- const { join: join8 } = await import("path");
27443
- mkdirSync7(targetDir, { recursive: true });
27805
+ const { mkdirSync: mkdirSync8, writeFileSync: writeFileSync8 } = await import("fs");
27806
+ const { join: join10 } = await import("path");
27807
+ mkdirSync8(targetDir, { recursive: true });
27444
27808
  for (const [name, content] of Object.entries(files)) {
27445
- writeFileSync7(join8(targetDir, name), content);
27809
+ writeFileSync8(join10(targetDir, name), content);
27446
27810
  }
27447
27811
  }
27448
27812
 
27449
27813
  // src/deploy/ansible.ts
27450
27814
  async function runAnsible(inputs) {
27451
- const workDir = join8(tmpdir(), "arc-deploy", `ansible-${Date.now()}`);
27452
- mkdirSync7(workDir, { recursive: true });
27815
+ const workDir = join10(tmpdir(), "arc-deploy", `ansible-${Date.now()}`);
27816
+ mkdirSync8(workDir, { recursive: true });
27453
27817
  await materializeAssets(workDir, ASSETS.ansible);
27454
27818
  const user = inputs.asRoot ? "root" : inputs.target.user;
27455
27819
  const port = inputs.ansible?.sshPort ?? inputs.target.port;
@@ -27463,7 +27827,7 @@ async function runAnsible(inputs) {
27463
27827
  ""
27464
27828
  ].join(`
27465
27829
  `);
27466
- writeFileSync7(join8(workDir, "inventory.ini"), inventory);
27830
+ writeFileSync8(join10(workDir, "inventory.ini"), inventory);
27467
27831
  const extraVars = [
27468
27832
  `username=${inputs.target.user}`,
27469
27833
  `ssh_port=${port}`
@@ -27588,15 +27952,15 @@ function generateCompose({ cfg }) {
27588
27952
 
27589
27953
  // src/deploy/terraform.ts
27590
27954
  var {spawn: spawn3 } = globalThis.Bun;
27591
- import { existsSync as existsSync7, mkdirSync as mkdirSync8, writeFileSync as writeFileSync8 } from "fs";
27955
+ import { existsSync as existsSync9, mkdirSync as mkdirSync9, writeFileSync as writeFileSync9 } from "fs";
27592
27956
  import { tmpdir as tmpdir2 } from "os";
27593
- import { join as join9 } from "path";
27957
+ import { join as join11 } from "path";
27594
27958
  async function runTerraform(inputs) {
27595
- const workDir = join9(tmpdir2(), "arc-deploy", `tf-${Date.now()}`);
27596
- mkdirSync8(workDir, { recursive: true });
27959
+ const workDir = join11(tmpdir2(), "arc-deploy", `tf-${Date.now()}`);
27960
+ mkdirSync9(workDir, { recursive: true });
27597
27961
  await materializeAssets(workDir, ASSETS.terraform);
27598
27962
  const sshPubKey = inputs.tf.sshPublicKey ?? expandHome("~/.ssh/id_ed25519.pub");
27599
- if (!existsSync7(expandHome(sshPubKey))) {
27963
+ if (!existsSync9(expandHome(sshPubKey))) {
27600
27964
  throw new Error(`SSH public key not found at ${sshPubKey}. Set provision.terraform.sshPublicKey in deploy.arc.json.`);
27601
27965
  }
27602
27966
  const tfvars = [
@@ -27609,7 +27973,7 @@ async function runTerraform(inputs) {
27609
27973
  ].join(`
27610
27974
  `) + `
27611
27975
  `;
27612
- writeFileSync8(join9(workDir, "terraform.tfvars"), tfvars);
27976
+ writeFileSync9(join11(workDir, "terraform.tfvars"), tfvars);
27613
27977
  await runTf(workDir, ["init", "-input=false", "-no-color"]);
27614
27978
  await runTf(workDir, [
27615
27979
  "apply",
@@ -27661,21 +28025,21 @@ function expandHome(p) {
27661
28025
  }
27662
28026
 
27663
28027
  // src/deploy/config.ts
27664
- import { existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync9 } from "fs";
27665
- import { join as join10 } from "path";
28028
+ import { existsSync as existsSync10, readFileSync as readFileSync9, writeFileSync as writeFileSync10 } from "fs";
28029
+ import { join as join12 } from "path";
27666
28030
  var DEPLOY_CONFIG_FILE = "deploy.arc.json";
27667
28031
  function deployConfigPath(rootDir) {
27668
- return join10(rootDir, DEPLOY_CONFIG_FILE);
28032
+ return join12(rootDir, DEPLOY_CONFIG_FILE);
27669
28033
  }
27670
28034
  function deployConfigExists(rootDir) {
27671
- return existsSync8(deployConfigPath(rootDir));
28035
+ return existsSync10(deployConfigPath(rootDir));
27672
28036
  }
27673
28037
  function loadDeployConfig(rootDir) {
27674
28038
  const path4 = deployConfigPath(rootDir);
27675
- if (!existsSync8(path4)) {
28039
+ if (!existsSync10(path4)) {
27676
28040
  throw new Error(`Missing ${DEPLOY_CONFIG_FILE} at ${path4}`);
27677
28041
  }
27678
- const raw = readFileSync7(path4, "utf-8");
28042
+ const raw = readFileSync9(path4, "utf-8");
27679
28043
  let parsed;
27680
28044
  try {
27681
28045
  parsed = JSON.parse(raw);
@@ -27686,7 +28050,7 @@ function loadDeployConfig(rootDir) {
27686
28050
  return validateDeployConfig(expanded);
27687
28051
  }
27688
28052
  function saveDeployConfig(rootDir, cfg) {
27689
- writeFileSync9(deployConfigPath(rootDir), JSON.stringify(cfg, null, 2) + `
28053
+ writeFileSync10(deployConfigPath(rootDir), JSON.stringify(cfg, null, 2) + `
27690
28054
  `);
27691
28055
  }
27692
28056
  var VAR_REGEX = /\$\{([A-Z0-9_]+)\}|\$([A-Z0-9_]+)/g;
@@ -28086,22 +28450,22 @@ async function bootstrap(inputs) {
28086
28450
  }
28087
28451
  async function upStack(inputs) {
28088
28452
  const { cfg } = inputs;
28089
- const workDir = join11(tmpdir3(), "arc-deploy", `stack-${Date.now()}`);
28090
- mkdirSync9(workDir, { recursive: true });
28091
- writeFileSync10(join11(workDir, "Caddyfile"), generateCaddyfile(cfg));
28092
- writeFileSync10(join11(workDir, "docker-compose.yml"), generateCompose({ cfg }));
28453
+ const workDir = join13(tmpdir3(), "arc-deploy", `stack-${Date.now()}`);
28454
+ mkdirSync10(workDir, { recursive: true });
28455
+ writeFileSync11(join13(workDir, "Caddyfile"), generateCaddyfile(cfg));
28456
+ writeFileSync11(join13(workDir, "docker-compose.yml"), generateCompose({ cfg }));
28093
28457
  await assertExec(cfg.target, `sudo mkdir -p ${cfg.target.remoteDir} && sudo chown ${cfg.target.user}:${cfg.target.user} ${cfg.target.remoteDir}`);
28094
28458
  for (const name of Object.keys(cfg.envs)) {
28095
28459
  await assertExec(cfg.target, `mkdir -p ${cfg.target.remoteDir}/${name}`);
28096
28460
  }
28097
- await scpUpload(cfg.target, join11(workDir, "Caddyfile"), `${cfg.target.remoteDir}/Caddyfile`);
28098
- await scpUpload(cfg.target, join11(workDir, "docker-compose.yml"), `${cfg.target.remoteDir}/docker-compose.yml`);
28461
+ await scpUpload(cfg.target, join13(workDir, "Caddyfile"), `${cfg.target.remoteDir}/Caddyfile`);
28462
+ await scpUpload(cfg.target, join13(workDir, "docker-compose.yml"), `${cfg.target.remoteDir}/docker-compose.yml`);
28099
28463
  await assertExec(cfg.target, `cd ${cfg.target.remoteDir} && docker compose pull --ignore-pull-failures && docker compose up -d`);
28100
28464
  }
28101
28465
 
28102
28466
  // src/deploy/remote-sync.ts
28103
- import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
28104
- import { join as join12, relative as relative3 } from "path";
28467
+ import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
28468
+ import { join as join14, relative as relative4 } from "path";
28105
28469
  function diffManifests(local, remote) {
28106
28470
  const remoteByName = new Map(remote.modules.map((m) => [m.name, m]));
28107
28471
  const changedModules = local.modules.filter((m) => remoteByName.get(m.name)?.hash !== m.hash);
@@ -28118,11 +28482,11 @@ async function syncEnv(inputs) {
28118
28482
  throw new Error(`Unknown env: ${env2}`);
28119
28483
  const remotePath = `${cfg.target.remoteDir}/${env2}`;
28120
28484
  await rsyncDir(cfg.target, projectDir, remotePath);
28121
- const localManifestPath = join12(ws.modulesDir, "manifest.json");
28122
- if (!existsSync9(localManifestPath)) {
28485
+ const localManifestPath = join14(ws.modulesDir, "manifest.json");
28486
+ if (!existsSync11(localManifestPath)) {
28123
28487
  throw new Error(`Local build missing at ${localManifestPath}. Run arc platform build first.`);
28124
28488
  }
28125
- const localManifest = JSON.parse(readFileSync8(localManifestPath, "utf-8"));
28489
+ const localManifest = JSON.parse(readFileSync10(localManifestPath, "utf-8"));
28126
28490
  const localPort = 15500 + hashEnvToOffset(env2);
28127
28491
  const tunnel = await openTunnel(cfg.target, localPort, "127.0.0.1", 2019);
28128
28492
  try {
@@ -28137,8 +28501,8 @@ async function syncEnv(inputs) {
28137
28501
  const shellFiles = collectFiles(ws.shellDir);
28138
28502
  const form = new FormData;
28139
28503
  for (const absPath of shellFiles) {
28140
- const rel = relative3(ws.shellDir, absPath);
28141
- form.append(rel, new Blob([readFileSync8(absPath)]), rel);
28504
+ const rel = relative4(ws.shellDir, absPath);
28505
+ form.append(rel, new Blob([readFileSync10(absPath)]), rel);
28142
28506
  }
28143
28507
  const res2 = await fetch(`${base2}/api/deploy/shell`, {
28144
28508
  method: "POST",
@@ -28150,9 +28514,9 @@ async function syncEnv(inputs) {
28150
28514
  if (diff.stylesChanged) {
28151
28515
  const form = new FormData;
28152
28516
  for (const name of ["styles.css", "theme.css"]) {
28153
- const p = join12(ws.arcDir, name);
28154
- if (existsSync9(p)) {
28155
- form.append(name, new Blob([readFileSync8(p)]), name);
28517
+ const p = join14(ws.arcDir, name);
28518
+ if (existsSync11(p)) {
28519
+ form.append(name, new Blob([readFileSync10(p)]), name);
28156
28520
  }
28157
28521
  }
28158
28522
  const res2 = await fetch(`${base2}/api/deploy/shell`, {
@@ -28165,8 +28529,8 @@ async function syncEnv(inputs) {
28165
28529
  if (diff.changedModules.length > 0) {
28166
28530
  const form = new FormData;
28167
28531
  for (const mod of diff.changedModules) {
28168
- const p = join12(ws.modulesDir, mod.file);
28169
- form.append(mod.file, new Blob([readFileSync8(p)]), mod.file);
28532
+ const p = join14(ws.modulesDir, mod.file);
28533
+ form.append(mod.file, new Blob([readFileSync10(p)]), mod.file);
28170
28534
  }
28171
28535
  const res2 = await fetch(`${base2}/api/deploy/modules`, {
28172
28536
  method: "POST",
@@ -28193,12 +28557,12 @@ async function syncEnv(inputs) {
28193
28557
  }
28194
28558
  }
28195
28559
  function collectFiles(dir) {
28196
- if (!existsSync9(dir))
28560
+ if (!existsSync11(dir))
28197
28561
  return [];
28198
- const { readdirSync: readdirSync5 } = __require("fs");
28562
+ const { readdirSync: readdirSync6 } = __require("fs");
28199
28563
  const out = [];
28200
- for (const entry of readdirSync5(dir, { withFileTypes: true })) {
28201
- const p = join12(dir, entry.name);
28564
+ for (const entry of readdirSync6(dir, { withFileTypes: true })) {
28565
+ const p = join14(dir, entry.name);
28202
28566
  if (entry.isDirectory())
28203
28567
  out.push(...collectFiles(p));
28204
28568
  else if (entry.isFile())
@@ -28910,13 +29274,13 @@ async function platformDeploy(envArg, options = {}) {
28910
29274
  err(`Unknown env "${envArg}". Known: ${Object.keys(cfg.envs).join(", ")}`);
28911
29275
  process.exit(1);
28912
29276
  })() : Object.keys(cfg.envs);
28913
- const manifestPath = join13(ws.modulesDir, "manifest.json");
28914
- const needBuild = options.rebuild || !existsSync10(manifestPath);
29277
+ const manifestPath = join15(ws.modulesDir, "manifest.json");
29278
+ const needBuild = options.rebuild || !existsSync12(manifestPath);
28915
29279
  if (needBuild && !options.skipBuild) {
28916
29280
  log2("Building platform...");
28917
- await buildAll(ws);
29281
+ await buildAll(ws, { noCache: options.rebuild });
28918
29282
  ok("Build complete");
28919
- } else if (!existsSync10(manifestPath)) {
29283
+ } else if (!existsSync12(manifestPath)) {
28920
29284
  err("No build found and --skip-build was set.");
28921
29285
  process.exit(1);
28922
29286
  }
@@ -28959,24 +29323,24 @@ async function platformDeploy(envArg, options = {}) {
28959
29323
  }
28960
29324
  function readCliVersion() {
28961
29325
  try {
28962
- const pkgPath = join13(import.meta.dir, "..", "..", "package.json");
28963
- const pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
29326
+ const pkgPath = join15(import.meta.dir, "..", "..", "package.json");
29327
+ const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
28964
29328
  return pkg.version ?? "unknown";
28965
29329
  } catch {
28966
29330
  return "unknown";
28967
29331
  }
28968
29332
  }
28969
29333
  async function hashDeployConfig(rootDir) {
28970
- const p2 = join13(rootDir, "deploy.arc.json");
28971
- const content = readFileSync9(p2);
29334
+ const p2 = join15(rootDir, "deploy.arc.json");
29335
+ const content = readFileSync11(p2);
28972
29336
  const hasher = new Bun.CryptoHasher("sha256");
28973
29337
  hasher.update(content);
28974
29338
  return hasher.digest("hex").slice(0, 16);
28975
29339
  }
28976
29340
 
28977
29341
  // src/commands/platform-dev.ts
28978
- import { existsSync as existsSync13, watch } from "fs";
28979
- import { join as join16 } from "path";
29342
+ import { existsSync as existsSync15, watch } from "fs";
29343
+ import { join as join18 } from "path";
28980
29344
 
28981
29345
  // ../host/src/create-server.ts
28982
29346
  var import_jsonwebtoken = __toESM(require_jsonwebtoken(), 1);
@@ -30295,7 +30659,10 @@ async function createArcServer(config) {
30295
30659
  "Access-Control-Allow-Origin": origin,
30296
30660
  "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
30297
30661
  "Access-Control-Allow-Headers": "Content-Type, Authorization, X-Arc-Scope, X-Arc-Tokens",
30298
- "Access-Control-Allow-Credentials": "true"
30662
+ "Access-Control-Allow-Credentials": "true",
30663
+ "Cross-Origin-Opener-Policy": "same-origin",
30664
+ "Cross-Origin-Embedder-Policy": "require-corp",
30665
+ "Cross-Origin-Resource-Policy": "cross-origin"
30299
30666
  };
30300
30667
  }
30301
30668
  const corsHeaders = buildCorsHeaders();
@@ -30423,12 +30790,12 @@ async function createArcServer(config) {
30423
30790
  };
30424
30791
  }
30425
30792
  // src/platform/server.ts
30426
- import { existsSync as existsSync12, mkdirSync as mkdirSync11 } from "fs";
30427
- import { join as join15 } from "path";
30793
+ import { existsSync as existsSync14, mkdirSync as mkdirSync12 } from "fs";
30794
+ import { join as join17 } from "path";
30428
30795
 
30429
30796
  // src/platform/deploy-api.ts
30430
- import { existsSync as existsSync11, mkdirSync as mkdirSync10, readFileSync as readFileSync10, writeFileSync as writeFileSync11 } from "fs";
30431
- import { dirname as dirname7, join as join14, normalize as normalize2, relative as relative4, resolve } from "path";
30797
+ import { existsSync as existsSync13, mkdirSync as mkdirSync11, readFileSync as readFileSync12, writeFileSync as writeFileSync12 } from "fs";
30798
+ import { dirname as dirname7, join as join16, normalize as normalize2, relative as relative5, resolve } from "path";
30432
30799
  function createDeployApiHandler(opts) {
30433
30800
  return async (req, url, ctx) => {
30434
30801
  const p3 = url.pathname;
@@ -30445,7 +30812,7 @@ function createDeployApiHandler(opts) {
30445
30812
  if (!validateManifest(body)) {
30446
30813
  return Response.json({ error: "Invalid manifest body" }, { status: 400, headers: ctx.corsHeaders });
30447
30814
  }
30448
- writeFileSync11(join14(opts.ws.modulesDir, "manifest.json"), JSON.stringify(body, null, 2));
30815
+ writeFileSync12(join16(opts.ws.modulesDir, "manifest.json"), JSON.stringify(body, null, 2));
30449
30816
  opts.setManifest(body);
30450
30817
  opts.notifyReload(body);
30451
30818
  return Response.json({ ok: true, moduleCount: body.modules.length }, { headers: ctx.corsHeaders });
@@ -30471,8 +30838,8 @@ function createDeployApiHandler(opts) {
30471
30838
  const writtenShell = await writeUploadedFileList(shellFiles, opts.ws.shellDir);
30472
30839
  const writtenRoot = [];
30473
30840
  for (const { name, file } of rootFiles) {
30474
- const target = join14(opts.ws.arcDir, name);
30475
- writeFileSync11(target, Buffer.from(await file.arrayBuffer()));
30841
+ const target = join16(opts.ws.arcDir, name);
30842
+ writeFileSync12(target, Buffer.from(await file.arrayBuffer()));
30476
30843
  writtenRoot.push(name);
30477
30844
  }
30478
30845
  return Response.json({ ok: true, written: [...writtenShell, ...writtenRoot] }, { headers: ctx.corsHeaders });
@@ -30497,16 +30864,16 @@ function isFile(v3) {
30497
30864
  async function writeUploadedFileList(files, targetDir) {
30498
30865
  const written = [];
30499
30866
  const safeRoot = resolve(targetDir);
30500
- mkdirSync10(safeRoot, { recursive: true });
30867
+ mkdirSync11(safeRoot, { recursive: true });
30501
30868
  for (const file of files) {
30502
30869
  const rel = normalize2(file.name);
30503
30870
  const full = resolve(safeRoot, rel);
30504
30871
  if (!full.startsWith(safeRoot + "/") && full !== safeRoot) {
30505
30872
  throw new Error(`Path traversal rejected: ${file.name}`);
30506
30873
  }
30507
- mkdirSync10(dirname7(full), { recursive: true });
30508
- writeFileSync11(full, Buffer.from(await file.arrayBuffer()));
30509
- written.push(relative4(safeRoot, full) || rel);
30874
+ mkdirSync11(dirname7(full), { recursive: true });
30875
+ writeFileSync12(full, Buffer.from(await file.arrayBuffer()));
30876
+ written.push(relative5(safeRoot, full) || rel);
30510
30877
  }
30511
30878
  return written;
30512
30879
  }
@@ -30573,14 +30940,15 @@ var MIME = {
30573
30940
  ".ico": "image/x-icon",
30574
30941
  ".woff2": "font/woff2",
30575
30942
  ".woff": "font/woff",
30576
- ".ttf": "font/ttf"
30943
+ ".ttf": "font/ttf",
30944
+ ".wasm": "application/wasm"
30577
30945
  };
30578
30946
  function getMime(path4) {
30579
30947
  const ext2 = path4.substring(path4.lastIndexOf("."));
30580
30948
  return MIME[ext2] ?? "application/octet-stream";
30581
30949
  }
30582
30950
  function serveFile(filePath, headers = {}) {
30583
- if (!existsSync12(filePath))
30951
+ if (!existsSync14(filePath))
30584
30952
  return new Response("Not Found", { status: 404 });
30585
30953
  return new Response(Bun.file(filePath), {
30586
30954
  headers: { "Content-Type": getMime(filePath), ...headers }
@@ -30669,7 +31037,7 @@ function staticFilesHandler(ws, devMode, moduleAccessMap) {
30669
31037
  return (_req, url, ctx) => {
30670
31038
  const path4 = url.pathname;
30671
31039
  if (path4.startsWith("/shell/"))
30672
- return serveFile(join15(ws.shellDir, path4.slice(7)), ctx.corsHeaders);
31040
+ return serveFile(join17(ws.shellDir, path4.slice(7)), ctx.corsHeaders);
30673
31041
  if (path4.startsWith("/modules/")) {
30674
31042
  const fileWithParams = path4.slice(9);
30675
31043
  const filename = fileWithParams.split("?")[0];
@@ -30681,23 +31049,25 @@ function staticFilesHandler(ws, devMode, moduleAccessMap) {
30681
31049
  return new Response("Forbidden", { status: 403, headers: ctx.corsHeaders });
30682
31050
  }
30683
31051
  }
30684
- return serveFile(join15(ws.modulesDir, filename), {
31052
+ return serveFile(join17(ws.modulesDir, filename), {
30685
31053
  ...ctx.corsHeaders,
30686
31054
  "Cache-Control": devMode ? "no-cache" : "max-age=31536000,immutable"
30687
31055
  });
30688
31056
  }
30689
31057
  if (path4.startsWith("/locales/"))
30690
- return serveFile(join15(ws.arcDir, path4.slice(1)), ctx.corsHeaders);
31058
+ return serveFile(join17(ws.arcDir, path4.slice(1)), ctx.corsHeaders);
31059
+ if (path4.startsWith("/assets/"))
31060
+ return serveFile(join17(ws.assetsDir, path4.slice(8)), ctx.corsHeaders);
30691
31061
  if (path4 === "/styles.css")
30692
- return serveFile(join15(ws.arcDir, "styles.css"), ctx.corsHeaders);
31062
+ return serveFile(join17(ws.arcDir, "styles.css"), ctx.corsHeaders);
30693
31063
  if (path4 === "/theme.css")
30694
- return serveFile(join15(ws.arcDir, "theme.css"), ctx.corsHeaders);
31064
+ return serveFile(join17(ws.arcDir, "theme.css"), ctx.corsHeaders);
30695
31065
  if ((path4 === "/manifest.json" || path4 === "/manifest.webmanifest") && ws.manifest) {
30696
31066
  return serveFile(ws.manifest.path, ctx.corsHeaders);
30697
31067
  }
30698
31068
  if (path4.lastIndexOf(".") > path4.lastIndexOf("/")) {
30699
- const publicFile = join15(ws.publicDir, path4.slice(1));
30700
- if (existsSync12(publicFile))
31069
+ const publicFile = join17(ws.publicDir, path4.slice(1));
31070
+ if (existsSync14(publicFile))
30701
31071
  return serveFile(publicFile, ctx.corsHeaders);
30702
31072
  }
30703
31073
  return null;
@@ -30794,7 +31164,10 @@ async function startPlatformServer(opts) {
30794
31164
  const cors = {
30795
31165
  "Access-Control-Allow-Origin": "*",
30796
31166
  "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
30797
- "Access-Control-Allow-Headers": "Content-Type, Authorization, X-Arc-Tokens"
31167
+ "Access-Control-Allow-Headers": "Content-Type, Authorization, X-Arc-Tokens",
31168
+ "Cross-Origin-Opener-Policy": "same-origin",
31169
+ "Cross-Origin-Embedder-Policy": "require-corp",
31170
+ "Cross-Origin-Resource-Policy": "cross-origin"
30798
31171
  };
30799
31172
  const server = Bun.serve({
30800
31173
  port,
@@ -30832,10 +31205,10 @@ async function startPlatformServer(opts) {
30832
31205
  };
30833
31206
  }
30834
31207
  const { createBunSQLiteAdapterFactory: createBunSQLiteAdapterFactory2 } = await Promise.resolve().then(() => (init_dist(), exports_dist2));
30835
- const dbPath = opts.dbPath || join15(ws.arcDir, "data", "arc.db");
31208
+ const dbPath = opts.dbPath || join17(ws.arcDir, "data", "arc.db");
30836
31209
  const dbDir = dbPath.substring(0, dbPath.lastIndexOf("/"));
30837
31210
  if (dbDir)
30838
- mkdirSync11(dbDir, { recursive: true });
31211
+ mkdirSync12(dbDir, { recursive: true });
30839
31212
  const arcServer = await createArcServer({
30840
31213
  context,
30841
31214
  dbAdapterFactory: createBunSQLiteAdapterFactory2(dbPath),
@@ -30860,10 +31233,10 @@ async function startPlatformServer(opts) {
30860
31233
  }
30861
31234
 
30862
31235
  // src/commands/platform-dev.ts
30863
- async function platformDev() {
31236
+ async function platformDev(opts = {}) {
30864
31237
  const ws = resolveWorkspace();
30865
31238
  const port = 5005;
30866
- let manifest = await buildAll(ws);
31239
+ let manifest = await buildAll(ws, { noCache: opts.noCache });
30867
31240
  log2("Loading server context...");
30868
31241
  const { context, moduleAccess } = await loadServerContext(ws.packages);
30869
31242
  if (context) {
@@ -30878,7 +31251,7 @@ async function platformDev() {
30878
31251
  manifest,
30879
31252
  context,
30880
31253
  moduleAccess,
30881
- dbPath: join16(ws.rootDir, ".arc", "data", "dev.db"),
31254
+ dbPath: join18(ws.rootDir, ".arc", "data", "dev.db"),
30882
31255
  devMode: true,
30883
31256
  arcEntries
30884
31257
  });
@@ -30888,53 +31261,44 @@ async function platformDev() {
30888
31261
  log2("Watching for changes...");
30889
31262
  let rebuildTimer = null;
30890
31263
  let isRebuilding = false;
31264
+ const triggerRebuild = () => {
31265
+ if (rebuildTimer)
31266
+ clearTimeout(rebuildTimer);
31267
+ rebuildTimer = setTimeout(async () => {
31268
+ if (isRebuilding)
31269
+ return;
31270
+ isRebuilding = true;
31271
+ log2("Rebuilding...");
31272
+ try {
31273
+ manifest = await buildAll(ws);
31274
+ platform3.setManifest(manifest);
31275
+ platform3.notifyReload(manifest);
31276
+ ok(`Rebuilt \u2014 ${manifest.modules.length} module(s)`);
31277
+ } catch (e2) {
31278
+ console.error(`Rebuild failed: ${e2}`);
31279
+ } finally {
31280
+ isRebuilding = false;
31281
+ }
31282
+ }, 300);
31283
+ };
30891
31284
  for (const pkg of ws.packages) {
30892
- const srcDir = join16(pkg.path, "src");
30893
- if (!existsSync13(srcDir))
31285
+ const srcDir = join18(pkg.path, "src");
31286
+ if (!existsSync15(srcDir))
30894
31287
  continue;
30895
31288
  watch(srcDir, { recursive: true }, (_event, filename) => {
30896
31289
  if (!filename || filename.includes(".arc") || filename.endsWith(".d.ts") || filename.includes("node_modules") || filename.includes("dist"))
30897
31290
  return;
30898
31291
  if (!filename.endsWith(".ts") && !filename.endsWith(".tsx"))
30899
31292
  return;
30900
- if (rebuildTimer)
30901
- clearTimeout(rebuildTimer);
30902
- rebuildTimer = setTimeout(async () => {
30903
- if (isRebuilding)
30904
- return;
30905
- isRebuilding = true;
30906
- log2("Rebuilding...");
30907
- try {
30908
- manifest = await buildPackages(ws.rootDir, ws.modulesDir, ws.packages);
30909
- await buildStyles(ws.rootDir, ws.arcDir);
30910
- platform3.setManifest(manifest);
30911
- platform3.notifyReload(manifest);
30912
- ok(`Rebuilt ${manifest.modules.length} module(s)`);
30913
- } catch (e2) {
30914
- console.error(`Rebuild failed: ${e2}`);
30915
- } finally {
30916
- isRebuilding = false;
30917
- }
30918
- }, 300);
31293
+ triggerRebuild();
30919
31294
  });
30920
31295
  }
30921
- const localesDir = join16(ws.rootDir, "locales");
30922
- if (existsSync13(localesDir)) {
30923
- let poTimer = null;
31296
+ const localesDir = join18(ws.rootDir, "locales");
31297
+ if (existsSync15(localesDir)) {
30924
31298
  watch(localesDir, { recursive: false }, (_event, filename) => {
30925
31299
  if (!filename?.endsWith(".po"))
30926
31300
  return;
30927
- if (poTimer)
30928
- clearTimeout(poTimer);
30929
- poTimer = setTimeout(async () => {
30930
- try {
30931
- compileAllCatalogs(localesDir, join16(ws.arcDir, "locales"));
30932
- ok("Translations recompiled");
30933
- platform3.notifyReload(manifest);
30934
- } catch (e2) {
30935
- console.error(`Translation compile failed: ${e2}`);
30936
- }
30937
- }, 200);
31301
+ triggerRebuild();
30938
31302
  });
30939
31303
  }
30940
31304
  const cleanup = () => {
@@ -30946,17 +31310,17 @@ async function platformDev() {
30946
31310
  }
30947
31311
 
30948
31312
  // src/commands/platform-start.ts
30949
- import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
30950
- import { join as join17 } from "path";
31313
+ import { existsSync as existsSync16, readFileSync as readFileSync13 } from "fs";
31314
+ import { join as join19 } from "path";
30951
31315
  async function platformStart() {
30952
31316
  const ws = resolveWorkspace();
30953
31317
  const port = parseInt(process.env.PORT || "5005", 10);
30954
- const manifestPath = join17(ws.modulesDir, "manifest.json");
30955
- if (!existsSync14(manifestPath)) {
31318
+ const manifestPath = join19(ws.modulesDir, "manifest.json");
31319
+ if (!existsSync16(manifestPath)) {
30956
31320
  err("No build found. Run `arc platform build` first.");
30957
31321
  process.exit(1);
30958
31322
  }
30959
- const manifest = JSON.parse(readFileSync11(manifestPath, "utf-8"));
31323
+ const manifest = JSON.parse(readFileSync13(manifestPath, "utf-8"));
30960
31324
  log2("Loading server context...");
30961
31325
  const { context, moduleAccess } = await loadServerContext(ws.packages);
30962
31326
  if (context) {
@@ -30974,7 +31338,7 @@ async function platformStart() {
30974
31338
  manifest,
30975
31339
  context,
30976
31340
  moduleAccess,
30977
- dbPath: join17(ws.rootDir, ".arc", "data", "prod.db"),
31341
+ dbPath: join19(ws.rootDir, ".arc", "data", "prod.db"),
30978
31342
  devMode: false,
30979
31343
  deployApi,
30980
31344
  arcEntries
@@ -30996,8 +31360,8 @@ program2.name("arc").description("CLI tool for Arc framework").version("0.0.3");
30996
31360
  program2.command("dev").description("Run development mode for Arc framework").action(dev);
30997
31361
  program2.command("build").description("Build all clients and declarations").action(build);
30998
31362
  var platform3 = program2.command("platform").description("Platform commands \u2014 run full stack (server + UI)");
30999
- platform3.command("dev").description("Start platform in dev mode (Bun server + Vite HMR)").action(platformDev);
31000
- platform3.command("build").description("Build platform for production").action(platformBuild);
31363
+ platform3.command("dev").description("Start platform in dev mode (Bun server + Vite HMR)").option("--no-cache", "Force full rebuild on startup").action((opts) => platformDev({ noCache: opts.cache === false }));
31364
+ platform3.command("build").description("Build platform for production").option("--no-cache", "Force full rebuild").action((opts) => platformBuild({ noCache: opts.cache === false }));
31001
31365
  platform3.command("start").description("Start platform in production mode (requires prior build)").action(platformStart);
31002
31366
  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").action((env2, opts) => platformDeploy(env2, opts));
31003
31367
  program2.parse(process.argv);