@arcote.tech/arc-cli 0.5.8 → 0.6.0

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
@@ -21686,7 +21686,7 @@ var emitWarning = (msg, type, code, fn) => {
21686
21686
  var AC = globalThis.AbortController;
21687
21687
  var AS = globalThis.AbortSignal;
21688
21688
  if (typeof AC === "undefined") {
21689
- AS = class AbortSignal {
21689
+ AS = class AbortSignal2 {
21690
21690
  onabort;
21691
21691
  _onabort = [];
21692
21692
  reason;
@@ -26129,9 +26129,110 @@ ${colors2.yellow}Type declaration errors:${colors2.reset}`);
26129
26129
  }
26130
26130
  }
26131
26131
 
26132
+ // src/commands/build-shell.ts
26133
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readdirSync as readdirSync2, rmSync } from "fs";
26134
+ import { join as join3 } from "path";
26135
+ var REACT_ENTRIES = [
26136
+ ["react", `export * from "react";
26137
+ import * as React from "react";
26138
+ export default React;`],
26139
+ ["react-dom", `export * from "react-dom";
26140
+ import * as ReactDOM from "react-dom";
26141
+ export default ReactDOM;`],
26142
+ ["jsx-runtime", `export * from "react/jsx-runtime";`],
26143
+ ["jsx-dev-runtime", `export * from "react/jsx-dev-runtime";`],
26144
+ ["react-dom-client", `export { createRoot, hydrateRoot } from "react-dom/client";`]
26145
+ ];
26146
+ var SHELL_BASE_EXTERNAL = [
26147
+ "react",
26148
+ "react-dom",
26149
+ "react/jsx-runtime",
26150
+ "react/jsx-dev-runtime",
26151
+ "react-dom/client"
26152
+ ];
26153
+ async function buildShell(opts) {
26154
+ const outDir = opts.out;
26155
+ const fromDir = opts.from;
26156
+ if (!existsSync3(fromDir)) {
26157
+ console.error(`[_build-shell] --from not found: ${fromDir}`);
26158
+ process.exit(1);
26159
+ }
26160
+ mkdirSync3(outDir, { recursive: true });
26161
+ const tmpDir = join3(outDir, "_tmp");
26162
+ mkdirSync3(tmpDir, { recursive: true });
26163
+ try {
26164
+ const arcPkgs = discoverArcPackages(fromDir);
26165
+ if (arcPkgs.length === 0) {
26166
+ console.warn("[_build-shell] no @arcote.tech/* packages discovered");
26167
+ }
26168
+ console.log(`[_build-shell] building shell for react + ${arcPkgs.length} @arcote.tech/* package(s)`);
26169
+ await buildReactShell(outDir, tmpDir, fromDir);
26170
+ for (const pkg of arcPkgs) {
26171
+ await buildArcEntry(pkg, arcPkgs, outDir, tmpDir, fromDir);
26172
+ }
26173
+ console.log(`[_build-shell] done \u2192 ${outDir}`);
26174
+ } finally {
26175
+ rmSync(tmpDir, { recursive: true, force: true });
26176
+ }
26177
+ }
26178
+ function discoverArcPackages(fromDir) {
26179
+ const arcDir = join3(fromDir, "@arcote.tech");
26180
+ if (!existsSync3(arcDir))
26181
+ return [];
26182
+ return readdirSync2(arcDir).filter((name) => existsSync3(join3(arcDir, name, "package.json"))).map((name) => `@arcote.tech/${name}`);
26183
+ }
26184
+ async function buildReactShell(outDir, tmpDir, fromDir) {
26185
+ const eps = [];
26186
+ for (const [name, code] of REACT_ENTRIES) {
26187
+ const f = join3(tmpDir, `${name}.ts`);
26188
+ await Bun.write(f, code);
26189
+ eps.push(f);
26190
+ }
26191
+ const r = await Bun.build({
26192
+ entrypoints: eps,
26193
+ outdir: outDir,
26194
+ splitting: true,
26195
+ format: "esm",
26196
+ target: "browser",
26197
+ naming: "[name].[ext]",
26198
+ root: fromDir
26199
+ });
26200
+ if (!r.success) {
26201
+ for (const l of r.logs)
26202
+ console.error(l);
26203
+ throw new Error("React shell build failed");
26204
+ }
26205
+ }
26206
+ async function buildArcEntry(pkg, allArcPkgs, outDir, tmpDir, fromDir) {
26207
+ const shortName = pkg.replace("@arcote.tech/", "");
26208
+ const otherExternals = allArcPkgs.filter((p) => p !== pkg);
26209
+ const f = join3(tmpDir, `${shortName}.ts`);
26210
+ await Bun.write(f, `export * from "${pkg}";
26211
+ `);
26212
+ const r = await Bun.build({
26213
+ entrypoints: [f],
26214
+ outdir: outDir,
26215
+ format: "esm",
26216
+ target: "browser",
26217
+ naming: "[name].[ext]",
26218
+ root: fromDir,
26219
+ external: [...SHELL_BASE_EXTERNAL, ...otherExternals],
26220
+ define: {
26221
+ ONLY_SERVER: "false",
26222
+ ONLY_BROWSER: "true",
26223
+ ONLY_CLIENT: "true"
26224
+ }
26225
+ });
26226
+ if (!r.success) {
26227
+ for (const l of r.logs)
26228
+ console.error(l);
26229
+ throw new Error(`Shell build failed for ${pkg}`);
26230
+ }
26231
+ }
26232
+
26132
26233
  // src/commands/dev.ts
26133
26234
  var import_chokidar = __toESM(require_chokidar(), 1);
26134
- import { dirname as dirname3, join as join3, relative } from "path";
26235
+ import { dirname as dirname3, join as join4, relative } from "path";
26135
26236
  function getContextForFile(filePath, contexts, configDir) {
26136
26237
  const relativePath = relative(configDir, filePath);
26137
26238
  for (const context of contexts) {
@@ -26248,7 +26349,7 @@ ${colors3.yellow}Type declaration errors:${colors3.reset}`);
26248
26349
  } else {
26249
26350
  log("Initial build complete \u2713", "green");
26250
26351
  }
26251
- const watcher = import_chokidar.default.watch([join3(configDir, "**/*.ts"), join3(configDir, "**/*.tsx")], {
26352
+ const watcher = import_chokidar.default.watch([join4(configDir, "**/*.ts"), join4(configDir, "**/*.tsx")], {
26252
26353
  ignored: [
26253
26354
  "**/node_modules/**",
26254
26355
  `**/${config.outDir}/**`,
@@ -26345,24 +26446,24 @@ ${colors3.yellow}Type declaration errors:${colors3.reset}`);
26345
26446
  }
26346
26447
 
26347
26448
  // src/platform/shared.ts
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";
26449
+ import { copyFileSync as copyFileSync2, existsSync as existsSync10, mkdirSync as mkdirSync10, readFileSync as readFileSync11, readdirSync as readdirSync6, rmSync as rmSync3, writeFileSync as writeFileSync10 } from "fs";
26450
+ import { dirname as dirname6, join as join12 } from "path";
26350
26451
 
26351
26452
  // src/builder/module-builder.ts
26352
26453
  import { execSync } from "child_process";
26353
26454
  import {
26354
- existsSync as existsSync7,
26355
- mkdirSync as mkdirSync6,
26356
- readFileSync as readFileSync7,
26357
- readdirSync as readdirSync4,
26358
- rmSync,
26359
- writeFileSync as writeFileSync6
26455
+ existsSync as existsSync8,
26456
+ mkdirSync as mkdirSync7,
26457
+ readFileSync as readFileSync8,
26458
+ readdirSync as readdirSync5,
26459
+ rmSync as rmSync2,
26460
+ writeFileSync as writeFileSync7
26360
26461
  } from "fs";
26361
- import { basename as basename2, dirname as dirname5, join as join8, relative as relative3 } from "path";
26462
+ import { basename as basename2, dirname as dirname5, join as join9, relative as relative3 } from "path";
26362
26463
 
26363
26464
  // src/i18n/index.ts
26364
- import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
26365
- import { dirname as dirname4, join as join5 } from "path";
26465
+ import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "fs";
26466
+ import { dirname as dirname4, join as join6 } from "path";
26366
26467
 
26367
26468
  // src/i18n/catalog.ts
26368
26469
  function hashMsgid(msgid) {
@@ -26533,10 +26634,10 @@ function quoteString(s) {
26533
26634
  }
26534
26635
 
26535
26636
  // src/i18n/compile.ts
26536
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync3, readdirSync as readdirSync2, writeFileSync as writeFileSync3 } from "fs";
26537
- import { join as join4 } from "path";
26637
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync4, readdirSync as readdirSync3, writeFileSync as writeFileSync4 } from "fs";
26638
+ import { join as join5 } from "path";
26538
26639
  function compileCatalog(poPath) {
26539
- const content = readFileSync3(poPath, "utf-8");
26640
+ const content = readFileSync4(poPath, "utf-8");
26540
26641
  const entries = parsePo(content);
26541
26642
  const result = {};
26542
26643
  for (const entry of entries) {
@@ -26551,13 +26652,13 @@ function compileCatalog(poPath) {
26551
26652
  return sorted;
26552
26653
  }
26553
26654
  function compileAllCatalogs(localesDir, outDir) {
26554
- mkdirSync3(outDir, { recursive: true });
26555
- for (const file of readdirSync2(localesDir)) {
26655
+ mkdirSync4(outDir, { recursive: true });
26656
+ for (const file of readdirSync3(localesDir)) {
26556
26657
  if (!file.endsWith(".po"))
26557
26658
  continue;
26558
26659
  const locale = file.replace(".po", "");
26559
- const compiled = compileCatalog(join4(localesDir, file));
26560
- writeFileSync3(join4(outDir, `${locale}.json`), JSON.stringify(compiled));
26660
+ const compiled = compileCatalog(join5(localesDir, file));
26661
+ writeFileSync4(join5(outDir, `${locale}.json`), JSON.stringify(compiled));
26561
26662
  }
26562
26663
  }
26563
26664
 
@@ -26601,10 +26702,10 @@ function i18nExtractPlugin(collector, rootDir) {
26601
26702
 
26602
26703
  // src/i18n/index.ts
26603
26704
  function readTranslationsConfig(rootDir) {
26604
- const pkgPath = join5(rootDir, "package.json");
26605
- if (!existsSync4(pkgPath))
26705
+ const pkgPath = join6(rootDir, "package.json");
26706
+ if (!existsSync5(pkgPath))
26606
26707
  return null;
26607
- const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
26708
+ const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
26608
26709
  const config = pkg.arc?.translations;
26609
26710
  if (!config?.locales?.length)
26610
26711
  return null;
@@ -26617,34 +26718,34 @@ async function finalizeTranslations(rootDir, outDir, collector) {
26617
26718
  const config = readTranslationsConfig(rootDir);
26618
26719
  if (!config || collector.size === 0)
26619
26720
  return;
26620
- const localesJsonDir = join5(outDir, "locales");
26621
- mkdirSync4(localesJsonDir, { recursive: true });
26721
+ const localesJsonDir = join6(outDir, "locales");
26722
+ mkdirSync5(localesJsonDir, { recursive: true });
26622
26723
  console.log(` Extracted ${collector.size} translatable string(s) for ${config.locales.length} locale(s)`);
26623
26724
  for (const locale of config.locales) {
26624
- const poPath = join5(rootDir, "locales", `${locale}.po`);
26625
- mkdirSync4(dirname4(poPath), { recursive: true });
26626
- const existing = existsSync4(poPath) ? parsePo(readFileSync4(poPath, "utf-8")) : [];
26725
+ const poPath = join6(rootDir, "locales", `${locale}.po`);
26726
+ mkdirSync5(dirname4(poPath), { recursive: true });
26727
+ const existing = existsSync5(poPath) ? parsePo(readFileSync5(poPath, "utf-8")) : [];
26627
26728
  const merged = mergeCatalog(existing, collector);
26628
- writeFileSync4(poPath, writePo(merged));
26729
+ writeFileSync5(poPath, writePo(merged));
26629
26730
  const compiled = compileCatalog(poPath);
26630
- writeFileSync4(join5(localesJsonDir, `${locale}.json`), JSON.stringify(compiled));
26731
+ writeFileSync5(join6(localesJsonDir, `${locale}.json`), JSON.stringify(compiled));
26631
26732
  }
26632
26733
  }
26633
26734
 
26634
26735
  // 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";
26736
+ import { existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "fs";
26737
+ import { join as join7 } from "path";
26637
26738
  var CACHE_VERSION = 1;
26638
26739
  var CACHE_FILE = ".build-cache.json";
26639
26740
  function emptyCache() {
26640
26741
  return { version: CACHE_VERSION, units: {} };
26641
26742
  }
26642
26743
  function loadBuildCache(arcDir) {
26643
- const path4 = join6(arcDir, CACHE_FILE);
26644
- if (!existsSync5(path4))
26744
+ const path4 = join7(arcDir, CACHE_FILE);
26745
+ if (!existsSync6(path4))
26645
26746
  return emptyCache();
26646
26747
  try {
26647
- const raw = JSON.parse(readFileSync5(path4, "utf-8"));
26748
+ const raw = JSON.parse(readFileSync6(path4, "utf-8"));
26648
26749
  if (raw?.version !== CACHE_VERSION || typeof raw.units !== "object") {
26649
26750
  return emptyCache();
26650
26751
  }
@@ -26654,15 +26755,15 @@ function loadBuildCache(arcDir) {
26654
26755
  }
26655
26756
  }
26656
26757
  function saveBuildCache(arcDir, cache) {
26657
- mkdirSync5(arcDir, { recursive: true });
26658
- writeFileSync5(join6(arcDir, CACHE_FILE), JSON.stringify(cache, null, 2));
26758
+ mkdirSync6(arcDir, { recursive: true });
26759
+ writeFileSync6(join7(arcDir, CACHE_FILE), JSON.stringify(cache, null, 2));
26659
26760
  }
26660
26761
  function isCacheHit(cache, unitId, inputHash, requiredOutputs = []) {
26661
26762
  const entry = cache.units[unitId];
26662
26763
  if (!entry || entry.inputHash !== inputHash)
26663
26764
  return false;
26664
26765
  for (const out of requiredOutputs) {
26665
- if (!existsSync5(out))
26766
+ if (!existsSync6(out))
26666
26767
  return false;
26667
26768
  }
26668
26769
  return true;
@@ -26676,8 +26777,8 @@ function updateCache(cache, unitId, inputHash, output = {}) {
26676
26777
  }
26677
26778
 
26678
26779
  // 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";
26780
+ import { existsSync as existsSync7, readFileSync as readFileSync7, readdirSync as readdirSync4, statSync } from "fs";
26781
+ import { join as join8, relative as relative2, sep as sep2 } from "path";
26681
26782
  function sha256Hex(bytes) {
26682
26783
  const hasher = new Bun.CryptoHasher("sha256");
26683
26784
  hasher.update(bytes);
@@ -26687,21 +26788,21 @@ function sha256OfFiles(paths) {
26687
26788
  const hasher = new Bun.CryptoHasher("sha256");
26688
26789
  const sorted = [...paths].sort();
26689
26790
  for (const p of sorted) {
26690
- if (!existsSync6(p))
26791
+ if (!existsSync7(p))
26691
26792
  continue;
26692
- hasher.update(readFileSync6(p));
26793
+ hasher.update(readFileSync7(p));
26693
26794
  hasher.update("\x00");
26694
26795
  }
26695
26796
  return hasher.digest("hex");
26696
26797
  }
26697
26798
  function sha256OfDir(dir, filter2) {
26698
- if (!existsSync6(dir))
26799
+ if (!existsSync7(dir))
26699
26800
  return sha256Hex("");
26700
26801
  const hasher = new Bun.CryptoHasher("sha256");
26701
26802
  const entries = [];
26702
26803
  function walk(absDir) {
26703
- for (const entry of readdirSync3(absDir, { withFileTypes: true })) {
26704
- const abs = join7(absDir, entry.name);
26804
+ for (const entry of readdirSync4(absDir, { withFileTypes: true })) {
26805
+ const abs = join8(absDir, entry.name);
26705
26806
  const rel = relative2(dir, abs).split(sep2).join("/");
26706
26807
  if (filter2 && !filter2(rel))
26707
26808
  continue;
@@ -26716,7 +26817,7 @@ function sha256OfDir(dir, filter2) {
26716
26817
  for (const { rel, abs } of entries) {
26717
26818
  hasher.update(rel);
26718
26819
  hasher.update("\x00");
26719
- hasher.update(readFileSync6(abs));
26820
+ hasher.update(readFileSync7(abs));
26720
26821
  hasher.update("\x00");
26721
26822
  }
26722
26823
  return hasher.digest("hex");
@@ -26735,17 +26836,17 @@ function stableStringify(value) {
26735
26836
  return "{" + keys.map((k) => JSON.stringify(k) + ":" + stableStringify(obj[k])).join(",") + "}";
26736
26837
  }
26737
26838
  function readInstalledVersion(rootDir, pkgName) {
26738
- const pkgJson = join7(rootDir, "node_modules", pkgName, "package.json");
26739
- if (!existsSync6(pkgJson))
26839
+ const pkgJson = join8(rootDir, "node_modules", pkgName, "package.json");
26840
+ if (!existsSync7(pkgJson))
26740
26841
  return null;
26741
26842
  try {
26742
- return JSON.parse(readFileSync6(pkgJson, "utf-8")).version ?? null;
26843
+ return JSON.parse(readFileSync7(pkgJson, "utf-8")).version ?? null;
26743
26844
  } catch {
26744
26845
  return null;
26745
26846
  }
26746
26847
  }
26747
26848
  function mtimeOf(path4) {
26748
- if (!existsSync6(path4))
26849
+ if (!existsSync7(path4))
26749
26850
  return 0;
26750
26851
  try {
26751
26852
  return statSync(path4).mtimeMs;
@@ -26793,40 +26894,40 @@ var SHELL_EXTERNALS = [
26793
26894
  "@arcote.tech/platform"
26794
26895
  ];
26795
26896
  function discoverPackages(rootDir) {
26796
- const rootPkg = JSON.parse(readFileSync7(join8(rootDir, "package.json"), "utf-8"));
26897
+ const rootPkg = JSON.parse(readFileSync8(join9(rootDir, "package.json"), "utf-8"));
26797
26898
  const workspaceGlobs = rootPkg.workspaces ?? [];
26798
26899
  const results = [];
26799
26900
  for (const glob2 of workspaceGlobs) {
26800
26901
  const base2 = glob2.replace("/*", "");
26801
- const baseDir = join8(rootDir, base2);
26802
- if (!existsSync7(baseDir))
26902
+ const baseDir = join9(rootDir, base2);
26903
+ if (!existsSync8(baseDir))
26803
26904
  continue;
26804
26905
  let entries;
26805
26906
  try {
26806
- entries = readdirSync4(baseDir);
26907
+ entries = readdirSync5(baseDir);
26807
26908
  } catch {
26808
26909
  continue;
26809
26910
  }
26810
26911
  for (const entry of entries) {
26811
- const pkgPath = join8(baseDir, entry, "package.json");
26812
- if (!existsSync7(pkgPath))
26912
+ const pkgPath = join9(baseDir, entry, "package.json");
26913
+ if (!existsSync8(pkgPath))
26813
26914
  continue;
26814
- const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
26915
+ const pkg = JSON.parse(readFileSync8(pkgPath, "utf-8"));
26815
26916
  if (pkg.name?.startsWith("@arcote.tech/"))
26816
26917
  continue;
26817
- const pkgDir = join8(baseDir, entry);
26918
+ const pkgDir = join9(baseDir, entry);
26818
26919
  const candidates = [
26819
- join8(pkgDir, "src", "index.ts"),
26820
- join8(pkgDir, "src", "index.tsx"),
26821
- join8(pkgDir, "index.ts"),
26822
- join8(pkgDir, "index.tsx")
26920
+ join9(pkgDir, "src", "index.ts"),
26921
+ join9(pkgDir, "src", "index.tsx"),
26922
+ join9(pkgDir, "index.ts"),
26923
+ join9(pkgDir, "index.tsx")
26823
26924
  ];
26824
- const entrypoint = candidates.find((c) => existsSync7(c)) ?? null;
26925
+ const entrypoint = candidates.find((c) => existsSync8(c)) ?? null;
26825
26926
  if (!entrypoint)
26826
26927
  continue;
26827
26928
  results.push({
26828
26929
  name: pkg.name,
26829
- path: join8(baseDir, entry),
26930
+ path: join9(baseDir, entry),
26830
26931
  entrypoint,
26831
26932
  packageJson: pkg
26832
26933
  });
@@ -26855,7 +26956,7 @@ var sourceFilter = (rel) => {
26855
26956
  return true;
26856
26957
  };
26857
26958
  function pkgSourceHash(pkg) {
26858
- return sha256OfDir(join8(pkg.path, "src"), sourceFilter);
26959
+ return sha256OfDir(join9(pkg.path, "src"), sourceFilter);
26859
26960
  }
26860
26961
  function depVersionsHash(rootDir, pkg) {
26861
26962
  const peerDeps = Object.keys(pkg.packageJson.peerDependencies ?? {});
@@ -26868,7 +26969,7 @@ function depVersionsHash(rootDir, pkg) {
26868
26969
  }
26869
26970
  async function buildContextClient(pkg, rootDir, client, cache, noCache) {
26870
26971
  const unitId = `context-pkg:${pkg.name}:${client.name}`;
26871
- const outDir = join8(pkg.path, "dist", client.name);
26972
+ const outDir = join9(pkg.path, "dist", client.name);
26872
26973
  const inputHash = sha256OfJson({
26873
26974
  src: pkgSourceHash(pkg),
26874
26975
  pkg: pkg.packageJson,
@@ -26877,7 +26978,7 @@ async function buildContextClient(pkg, rootDir, client, cache, noCache) {
26877
26978
  target: client.target,
26878
26979
  defines: client.defines
26879
26980
  });
26880
- if (!noCache && isCacheHit(cache, unitId, inputHash, [join8(outDir, "main", "index.js")])) {
26981
+ if (!noCache && isCacheHit(cache, unitId, inputHash, [join9(outDir, "main", "index.js")])) {
26881
26982
  console.log(` \u2713 cached: ${pkg.name} (${client.name})`);
26882
26983
  return { pkgName: pkg.name, client: client.name, declarationErrors: [], cached: true };
26883
26984
  }
@@ -26887,7 +26988,7 @@ async function buildContextClient(pkg, rootDir, client, cache, noCache) {
26887
26988
  const externals = [...peerDeps, ...deps];
26888
26989
  const result = await Bun.build({
26889
26990
  entrypoints: [pkg.entrypoint],
26890
- outdir: join8(outDir, "main"),
26991
+ outdir: join9(outDir, "main"),
26891
26992
  target: client.target,
26892
26993
  format: "esm",
26893
26994
  naming: "index.[ext]",
@@ -26925,7 +27026,7 @@ async function buildContextPackages(rootDir, packages, cache, noCache) {
26925
27026
  return { declarationErrors };
26926
27027
  }
26927
27028
  async function buildModulesBundle(rootDir, outDir, packages, cache, noCache) {
26928
- mkdirSync6(outDir, { recursive: true });
27029
+ mkdirSync7(outDir, { recursive: true });
26929
27030
  const unitId = "modules-bundle";
26930
27031
  const pkgHashes = packages.map((p) => ({
26931
27032
  name: p.name,
@@ -26942,12 +27043,12 @@ async function buildModulesBundle(rootDir, outDir, packages, cache, noCache) {
26942
27043
  const modules = [];
26943
27044
  for (const { safeName, name } of pkgHashes) {
26944
27045
  const file = `${safeName}.js`;
26945
- const filePath = join8(outDir, file);
26946
- if (!existsSync7(filePath)) {
27046
+ const filePath = join9(outDir, file);
27047
+ if (!existsSync8(filePath)) {
26947
27048
  console.log(` rebuilding modules-bundle: output ${file} missing`);
26948
27049
  return await actuallyBuild();
26949
27050
  }
26950
- modules.push({ file, name, hash: existing[safeName] ?? sha256Hex(readFileSync7(filePath)) });
27051
+ modules.push({ file, name, hash: existing[safeName] ?? sha256Hex(readFileSync8(filePath)) });
26951
27052
  }
26952
27053
  console.log(` \u2713 cached: modules-bundle (${modules.length} module(s))`);
26953
27054
  return { modules, cached: true };
@@ -26955,16 +27056,16 @@ async function buildModulesBundle(rootDir, outDir, packages, cache, noCache) {
26955
27056
  return await actuallyBuild();
26956
27057
  async function actuallyBuild() {
26957
27058
  console.log(` building: modules-bundle (${packages.length} package(s))`);
26958
- const tmpDir = join8(outDir, "_entries");
26959
- mkdirSync6(tmpDir, { recursive: true });
27059
+ const tmpDir = join9(outDir, "_entries");
27060
+ mkdirSync7(tmpDir, { recursive: true });
26960
27061
  const entrypoints = [];
26961
27062
  const fileToName = new Map;
26962
27063
  for (const pkg of packages) {
26963
27064
  const safeName = basename2(pkg.path);
26964
27065
  const moduleName = pkg.name.includes("/") ? pkg.name.split("/").pop() : pkg.name;
26965
27066
  fileToName.set(safeName, moduleName);
26966
- const wrapperFile = join8(tmpDir, `${safeName}.ts`);
26967
- writeFileSync6(wrapperFile, `export * from "${pkg.name}";
27067
+ const wrapperFile = join9(tmpDir, `${safeName}.ts`);
27068
+ writeFileSync7(wrapperFile, `export * from "${pkg.name}";
26968
27069
  `);
26969
27070
  entrypoints.push(wrapperFile);
26970
27071
  }
@@ -26998,13 +27099,13 @@ async function buildModulesBundle(rootDir, outDir, packages, cache, noCache) {
26998
27099
  console.error(log2);
26999
27100
  throw new Error("Module build failed");
27000
27101
  }
27001
- await finalizeTranslations(rootDir, join8(outDir, ".."), i18nCollector);
27002
- rmSync(tmpDir, { recursive: true, force: true });
27102
+ await finalizeTranslations(rootDir, join9(outDir, ".."), i18nCollector);
27103
+ rmSync2(tmpDir, { recursive: true, force: true });
27003
27104
  const outputHashes = {};
27004
27105
  const modules = result.outputs.filter((o) => o.kind === "entry-point").map((o) => {
27005
27106
  const file = basename2(o.path);
27006
27107
  const safeName = file.replace(/\.js$/, "");
27007
- const bytes = readFileSync7(o.path);
27108
+ const bytes = readFileSync8(o.path);
27008
27109
  const hash = sha256Hex(bytes);
27009
27110
  outputHashes[safeName] = hash;
27010
27111
  return {
@@ -27018,21 +27119,21 @@ async function buildModulesBundle(rootDir, outDir, packages, cache, noCache) {
27018
27119
  }
27019
27120
  }
27020
27121
  async function buildTranslations(rootDir, arcDir, cache, noCache) {
27021
- const localesDir = join8(rootDir, "locales");
27022
- if (!existsSync7(localesDir))
27122
+ const localesDir = join9(rootDir, "locales");
27123
+ if (!existsSync8(localesDir))
27023
27124
  return;
27024
27125
  const unitId = "translations";
27025
- const poFiles = readdirSync4(localesDir).filter((f) => f.endsWith(".po")).map((f) => join8(localesDir, f));
27126
+ const poFiles = readdirSync5(localesDir).filter((f) => f.endsWith(".po")).map((f) => join9(localesDir, f));
27026
27127
  if (poFiles.length === 0)
27027
27128
  return;
27028
27129
  const inputHash = sha256OfFiles(poFiles);
27029
- if (!noCache && isCacheHit(cache, unitId, inputHash, [join8(arcDir, "locales")])) {
27130
+ if (!noCache && isCacheHit(cache, unitId, inputHash, [join9(arcDir, "locales")])) {
27030
27131
  console.log(` \u2713 cached: translations`);
27031
27132
  return;
27032
27133
  }
27033
27134
  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));
27135
+ compileAllCatalogs(localesDir, join9(arcDir, "locales"));
27136
+ const jsonFiles = readdirSync5(join9(arcDir, "locales")).filter((f) => f.endsWith(".json")).map((f) => join9(arcDir, "locales", f));
27036
27137
  const outputHash = sha256OfFiles(jsonFiles);
27037
27138
  updateCache(cache, unitId, inputHash, { outputHash });
27038
27139
  }
@@ -27096,10 +27197,10 @@ var TAILWIND_INPUT_TEMPLATE = (rootRel) => `@import "tailwindcss";
27096
27197
  }
27097
27198
  `;
27098
27199
  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");
27200
+ mkdirSync7(arcDir, { recursive: true });
27201
+ const inputCss = join9(arcDir, "_input.css");
27202
+ const outputCss = join9(arcDir, "styles.css");
27203
+ const themeOutput = join9(arcDir, "theme.css");
27103
27204
  const rootRel = relative3(arcDir, rootDir).replace(/\\/g, "/");
27104
27205
  const inputCssContent = TAILWIND_INPUT_TEMPLATE(rootRel);
27105
27206
  const tsxFilter = (rel) => {
@@ -27111,15 +27212,15 @@ async function buildStyles(rootDir, arcDir, packages, themePath, cache, noCache)
27111
27212
  };
27112
27213
  const wsHashes = {};
27113
27214
  for (const p of packages) {
27114
- wsHashes[p.name] = sha256OfDir(join8(p.path, "src"), tsxFilter);
27215
+ wsHashes[p.name] = sha256OfDir(join9(p.path, "src"), tsxFilter);
27115
27216
  }
27116
- const platformSrc = join8(rootDir, "node_modules", "@arcote.tech", "platform", "src");
27117
- const arcDsSrc = join8(rootDir, "node_modules", "@arcote.tech", "arc-ds", "src");
27217
+ const platformSrc = join9(rootDir, "node_modules", "@arcote.tech", "platform", "src");
27218
+ const arcDsSrc = join9(rootDir, "node_modules", "@arcote.tech", "arc-ds", "src");
27118
27219
  const frameworkHashes = {
27119
27220
  platform: sha256OfDir(platformSrc, tsxFilter),
27120
27221
  arcDs: sha256OfDir(arcDsSrc, tsxFilter)
27121
27222
  };
27122
- const themeContent = themePath && existsSync7(join8(rootDir, themePath)) ? readFileSync7(join8(rootDir, themePath)) : null;
27223
+ const themeContent = themePath && existsSync8(join9(rootDir, themePath)) ? readFileSync8(join9(rootDir, themePath)) : null;
27123
27224
  const themeHash = themeContent ? sha256Hex(themeContent) : null;
27124
27225
  const unitId = "styles";
27125
27226
  const inputHash = sha256OfJson({
@@ -27136,21 +27237,198 @@ async function buildStyles(rootDir, arcDir, packages, themePath, cache, noCache)
27136
27237
  return;
27137
27238
  }
27138
27239
  console.log(` building: styles`);
27139
- writeFileSync6(inputCss, inputCssContent);
27240
+ writeFileSync7(inputCss, inputCssContent);
27140
27241
  execSync(`bunx @tailwindcss/cli -i ${inputCss} -o ${outputCss} --minify`, {
27141
27242
  cwd: rootDir,
27142
27243
  stdio: "inherit"
27143
27244
  });
27144
27245
  if (themePath && themeContent) {
27145
- writeFileSync6(themeOutput, themeContent);
27246
+ writeFileSync7(themeOutput, themeContent);
27146
27247
  }
27147
27248
  const outFiles = [outputCss];
27148
- if (themePath && existsSync7(themeOutput))
27249
+ if (themePath && existsSync8(themeOutput))
27149
27250
  outFiles.push(themeOutput);
27150
27251
  const outputHash = sha256OfFiles(outFiles);
27151
27252
  updateCache(cache, unitId, inputHash, { outputHash });
27152
27253
  }
27153
27254
 
27255
+ // src/builder/dependency-collector.ts
27256
+ import { createHash } from "crypto";
27257
+ import { copyFileSync, existsSync as existsSync9, mkdirSync as mkdirSync8, readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "fs";
27258
+ import { basename as basename3, join as join10 } from "path";
27259
+ var FRAMEWORK_PEERS = [
27260
+ "@arcote.tech/arc",
27261
+ "@arcote.tech/arc-ds",
27262
+ "@arcote.tech/arc-react",
27263
+ "@arcote.tech/platform",
27264
+ "react",
27265
+ "react-dom"
27266
+ ];
27267
+ function collectFrameworkDeps(arcDir, rootDir, packages) {
27268
+ mkdirSync8(arcDir, { recursive: true });
27269
+ const versions = resolveFrameworkVersions(rootDir, packages);
27270
+ const manifest = {
27271
+ name: "arc-platform-framework",
27272
+ private: true,
27273
+ type: "module",
27274
+ dependencies: versions
27275
+ };
27276
+ const manifestPath = join10(arcDir, "package.json");
27277
+ writeFileSync8(manifestPath, JSON.stringify(manifest, null, 2) + `
27278
+ `);
27279
+ const rootLock = join10(rootDir, "bun.lock");
27280
+ const targetLock = join10(arcDir, "bun.lock");
27281
+ if (existsSync9(rootLock)) {
27282
+ copyFileSync(rootLock, targetLock);
27283
+ }
27284
+ const hash = sha256OfFiles2([manifestPath, targetLock]);
27285
+ writeFileSync8(join10(arcDir, ".deps-hash"), hash + `
27286
+ `);
27287
+ return { hash, manifestPath };
27288
+ }
27289
+ function collectModuleDeps(arcDir, pkg) {
27290
+ const safeName = basename3(pkg.path);
27291
+ const moduleDir = join10(arcDir, "modules", safeName);
27292
+ mkdirSync8(moduleDir, { recursive: true });
27293
+ const pkgDeps = pkg.packageJson.dependencies ?? {};
27294
+ const filtered = {};
27295
+ const frameworkSet = new Set(FRAMEWORK_PEERS);
27296
+ for (const [name, spec] of Object.entries(pkgDeps)) {
27297
+ if (frameworkSet.has(name))
27298
+ continue;
27299
+ if (spec.startsWith("workspace:"))
27300
+ continue;
27301
+ filtered[name] = spec;
27302
+ }
27303
+ const manifest = {
27304
+ name: `arc-module-${safeName}`,
27305
+ private: true,
27306
+ type: "module",
27307
+ dependencies: filtered
27308
+ };
27309
+ const manifestPath = join10(moduleDir, "package.json");
27310
+ writeFileSync8(manifestPath, JSON.stringify(manifest, null, 2) + `
27311
+ `);
27312
+ const hash = sha256OfFiles2([manifestPath]);
27313
+ writeFileSync8(join10(moduleDir, ".deps-hash"), hash + `
27314
+ `);
27315
+ return { hash, manifestPath };
27316
+ }
27317
+ function resolveFrameworkVersions(rootDir, packages) {
27318
+ const rootPkg = JSON.parse(readFileSync9(join10(rootDir, "package.json"), "utf-8"));
27319
+ const rootDeps = rootPkg.dependencies ?? {};
27320
+ const out = {};
27321
+ for (const name of FRAMEWORK_PEERS) {
27322
+ const rootSpec = rootDeps[name];
27323
+ if (rootSpec) {
27324
+ out[name] = rootSpec;
27325
+ continue;
27326
+ }
27327
+ let found;
27328
+ for (const pkg of packages) {
27329
+ const spec = (pkg.packageJson.dependencies ?? {})[name];
27330
+ if (spec) {
27331
+ found = spec;
27332
+ break;
27333
+ }
27334
+ }
27335
+ out[name] = found ?? "*";
27336
+ }
27337
+ return out;
27338
+ }
27339
+ function sha256OfFiles2(paths) {
27340
+ const hash = createHash("sha256");
27341
+ for (const p of paths.sort()) {
27342
+ if (!existsSync9(p))
27343
+ continue;
27344
+ hash.update(p);
27345
+ hash.update("\x00");
27346
+ hash.update(readFileSync9(p));
27347
+ hash.update("\x00");
27348
+ }
27349
+ return hash.digest("hex");
27350
+ }
27351
+
27352
+ // src/builder/access-extractor.ts
27353
+ var {spawn: spawn2 } = globalThis.Bun;
27354
+ import { mkdirSync as mkdirSync9, readFileSync as readFileSync10, unlinkSync as unlinkSync2, writeFileSync as writeFileSync9 } from "fs";
27355
+ import { tmpdir } from "os";
27356
+ import { basename as basename4, join as join11 } from "path";
27357
+ async function extractAccessMap(arcDir, packages) {
27358
+ const serverBundles = packages.filter((p) => isContextPackage(p.packageJson)).map((p) => {
27359
+ const v06Path = join11(arcDir, "modules", basename4(p.path), "server.js");
27360
+ const legacyPath = join11(p.path, "dist", "server", "main", "index.js");
27361
+ return { name: p.name, path: v06Path, fallback: legacyPath };
27362
+ });
27363
+ const outPath = join11(arcDir, "access.json");
27364
+ mkdirSync9(arcDir, { recursive: true });
27365
+ const workerPath = join11(tmpdir(), `arc-access-extractor-${Date.now()}.mjs`);
27366
+ writeFileSync9(workerPath, WORKER_SOURCE);
27367
+ try {
27368
+ const proc2 = spawn2({
27369
+ cmd: ["bun", "run", workerPath],
27370
+ env: {
27371
+ ...process.env,
27372
+ ARC_ACCESS_BUNDLES: JSON.stringify(serverBundles),
27373
+ ARC_ACCESS_OUT: outPath
27374
+ },
27375
+ stdout: "pipe",
27376
+ stderr: "inherit"
27377
+ });
27378
+ const exit = await proc2.exited;
27379
+ if (exit !== 0) {
27380
+ throw new Error(`access-extractor subprocess exited with ${exit}`);
27381
+ }
27382
+ return JSON.parse(readFileSync10(outPath, "utf-8"));
27383
+ } finally {
27384
+ try {
27385
+ unlinkSync2(workerPath);
27386
+ } catch {}
27387
+ }
27388
+ }
27389
+ var WORKER_SOURCE = `
27390
+ import { existsSync } from "node:fs";
27391
+
27392
+ globalThis.ONLY_SERVER = true;
27393
+
27394
+ const bundles = JSON.parse(process.env.ARC_ACCESS_BUNDLES || "[]");
27395
+ const out = process.env.ARC_ACCESS_OUT;
27396
+ if (!out) {
27397
+ console.error("[access-extractor-worker] ARC_ACCESS_OUT not set");
27398
+ process.exit(2);
27399
+ }
27400
+
27401
+ const platform = await import("@arcote.tech/platform");
27402
+
27403
+ for (const { name, path, fallback } of bundles) {
27404
+ const target = existsSync(path) ? path : fallback;
27405
+ if (!target || !existsSync(target)) {
27406
+ // No server bundle on either path \u2014 module has no protected access rules
27407
+ // to discover. Skip silently rather than logging a misleading error.
27408
+ continue;
27409
+ }
27410
+ try {
27411
+ await import(target);
27412
+ } catch (e) {
27413
+ console.error("[access-extractor-worker] failed to import", name, "from", target, e);
27414
+ // Continue \u2014 partial access map is better than total failure.
27415
+ }
27416
+ }
27417
+
27418
+ const result = {};
27419
+ for (const [name, access] of platform.getAllModuleAccess()) {
27420
+ result[name] = {
27421
+ rules: (access.rules ?? []).map((r) => ({
27422
+ token: { name: r.token?.name ?? "" },
27423
+ hasCheck: typeof r.check === "function",
27424
+ })),
27425
+ };
27426
+ }
27427
+
27428
+ const { writeFileSync } = await import("node:fs");
27429
+ writeFileSync(out, JSON.stringify(result, null, 2) + "\\n");
27430
+ `.trim();
27431
+
27154
27432
  // src/platform/shared.ts
27155
27433
  var C = {
27156
27434
  reset: "\x1B[0m",
@@ -27168,9 +27446,9 @@ function resolveWorkspace() {
27168
27446
  process.exit(1);
27169
27447
  }
27170
27448
  const rootDir = dirname6(packageJsonPath);
27171
- const rootPkg = JSON.parse(readFileSync8(packageJsonPath, "utf-8"));
27449
+ const rootPkg = JSON.parse(readFileSync11(packageJsonPath, "utf-8"));
27172
27450
  const appName = rootPkg.name ?? "Arc App";
27173
- const arcDir = join9(rootDir, ".arc", "platform");
27451
+ const arcDir = join12(rootDir, ".arc", "platform");
27174
27452
  log2("Scanning workspaces...");
27175
27453
  const packages = discoverPackages(rootDir);
27176
27454
  ok(`Found ${packages.length} package(s): ${packages.map((p) => p.name).join(", ")}`);
@@ -27180,10 +27458,10 @@ function resolveWorkspace() {
27180
27458
  }
27181
27459
  let manifest;
27182
27460
  for (const name of ["manifest.json", "manifest.webmanifest"]) {
27183
- const manifestPath = join9(rootDir, name);
27184
- if (existsSync8(manifestPath)) {
27461
+ const manifestPath = join12(rootDir, name);
27462
+ if (existsSync10(manifestPath)) {
27185
27463
  try {
27186
- const data = JSON.parse(readFileSync8(manifestPath, "utf-8"));
27464
+ const data = JSON.parse(readFileSync11(manifestPath, "utf-8"));
27187
27465
  const icons = data.icons;
27188
27466
  manifest = {
27189
27467
  path: manifestPath,
@@ -27202,10 +27480,10 @@ function resolveWorkspace() {
27202
27480
  rootPkg,
27203
27481
  appName,
27204
27482
  arcDir,
27205
- modulesDir: join9(arcDir, "modules"),
27206
- shellDir: join9(arcDir, "shell"),
27207
- assetsDir: join9(arcDir, "assets"),
27208
- publicDir: join9(rootDir, "public"),
27483
+ modulesDir: join12(arcDir, "modules"),
27484
+ shellDir: join12(arcDir, "shell"),
27485
+ assetsDir: join12(arcDir, "assets"),
27486
+ publicDir: join12(rootDir, "public"),
27209
27487
  packages,
27210
27488
  manifest
27211
27489
  };
@@ -27218,14 +27496,23 @@ async function buildAll(ws, opts = {}) {
27218
27496
  const [, modulesResult] = await Promise.all([
27219
27497
  buildContextPackages(ws.rootDir, ws.packages, cache, noCache),
27220
27498
  buildModulesBundle(ws.rootDir, ws.modulesDir, ws.packages, cache, noCache),
27221
- buildShell(ws, cache, noCache),
27499
+ buildShell2(ws, cache, noCache),
27222
27500
  buildStyles(ws.rootDir, ws.arcDir, ws.packages, themePath, cache, noCache),
27223
27501
  copyBrowserAssets(ws, cache, noCache),
27224
27502
  buildTranslations(ws.rootDir, ws.arcDir, cache, noCache)
27225
27503
  ]);
27226
27504
  saveBuildCache(ws.arcDir, cache);
27505
+ collectFrameworkDeps(ws.arcDir, ws.rootDir, ws.packages);
27506
+ for (const pkg of ws.packages) {
27507
+ collectModuleDeps(ws.arcDir, pkg);
27508
+ }
27509
+ try {
27510
+ await extractAccessMap(ws.arcDir, ws.packages);
27511
+ } catch (e) {
27512
+ err(`access-extractor failed: ${e.message}`);
27513
+ }
27227
27514
  const finalManifest = assembleManifest(ws, modulesResult.modules, cache);
27228
- writeFileSync7(join9(ws.modulesDir, "manifest.json"), JSON.stringify(finalManifest, null, 2));
27515
+ writeFileSync10(join12(ws.modulesDir, "manifest.json"), JSON.stringify(finalManifest, null, 2));
27229
27516
  return finalManifest;
27230
27517
  }
27231
27518
  function assembleManifest(ws, modules, cache) {
@@ -27246,35 +27533,35 @@ function assembleManifest(ws, modules, cache) {
27246
27533
  }
27247
27534
  function resolveAssetSource(from, pkgDir, rootDir) {
27248
27535
  if (from.startsWith("./") || from.startsWith("../")) {
27249
- const resolved = join9(pkgDir, from);
27250
- return existsSync8(resolved) ? resolved : null;
27536
+ const resolved = join12(pkgDir, from);
27537
+ return existsSync10(resolved) ? resolved : null;
27251
27538
  }
27252
27539
  const candidates = [
27253
- join9(rootDir, "node_modules", from),
27254
- join9(pkgDir, "node_modules", from)
27540
+ join12(rootDir, "node_modules", from),
27541
+ join12(pkgDir, "node_modules", from)
27255
27542
  ];
27256
27543
  for (const c of candidates) {
27257
- if (existsSync8(c))
27544
+ if (existsSync10(c))
27258
27545
  return c;
27259
27546
  }
27260
- const bunCacheDir = join9(rootDir, "node_modules", ".bun");
27261
- if (existsSync8(bunCacheDir)) {
27262
- for (const entry of readdirSync5(bunCacheDir, { withFileTypes: true })) {
27547
+ const bunCacheDir = join12(rootDir, "node_modules", ".bun");
27548
+ if (existsSync10(bunCacheDir)) {
27549
+ for (const entry of readdirSync6(bunCacheDir, { withFileTypes: true })) {
27263
27550
  if (!entry.isDirectory())
27264
27551
  continue;
27265
- const candidate = join9(bunCacheDir, entry.name, "node_modules", from);
27266
- if (existsSync8(candidate))
27552
+ const candidate = join12(bunCacheDir, entry.name, "node_modules", from);
27553
+ if (existsSync10(candidate))
27267
27554
  return candidate;
27268
27555
  }
27269
27556
  }
27270
27557
  return null;
27271
27558
  }
27272
27559
  function readBrowserAssets(pkgDir) {
27273
- const pkgJsonPath = join9(pkgDir, "package.json");
27274
- if (!existsSync8(pkgJsonPath))
27560
+ const pkgJsonPath = join12(pkgDir, "package.json");
27561
+ if (!existsSync10(pkgJsonPath))
27275
27562
  return [];
27276
27563
  try {
27277
- const pkg = JSON.parse(readFileSync8(pkgJsonPath, "utf-8"));
27564
+ const pkg = JSON.parse(readFileSync11(pkgJsonPath, "utf-8"));
27278
27565
  const assets = pkg.arc?.browserAssets;
27279
27566
  if (!Array.isArray(assets))
27280
27567
  return [];
@@ -27284,14 +27571,14 @@ function readBrowserAssets(pkgDir) {
27284
27571
  }
27285
27572
  }
27286
27573
  function discoverBrowserAssets(ws) {
27287
- const arcDir = join9(ws.rootDir, "node_modules", "@arcote.tech");
27288
- if (!existsSync8(arcDir))
27574
+ const arcDir = join12(ws.rootDir, "node_modules", "@arcote.tech");
27575
+ if (!existsSync10(arcDir))
27289
27576
  return [];
27290
27577
  const out = [];
27291
- for (const entry of readdirSync5(arcDir, { withFileTypes: true })) {
27578
+ for (const entry of readdirSync6(arcDir, { withFileTypes: true })) {
27292
27579
  if (!entry.isDirectory() && !entry.isSymbolicLink())
27293
27580
  continue;
27294
- const pkgDir = join9(arcDir, entry.name);
27581
+ const pkgDir = join12(arcDir, entry.name);
27295
27582
  const assets = readBrowserAssets(pkgDir);
27296
27583
  for (const asset of assets) {
27297
27584
  const src = resolveAssetSource(asset.from, pkgDir, ws.rootDir);
@@ -27305,7 +27592,7 @@ function discoverBrowserAssets(ws) {
27305
27592
  return out;
27306
27593
  }
27307
27594
  async function copyBrowserAssets(ws, cache, noCache) {
27308
- mkdirSync7(ws.assetsDir, { recursive: true });
27595
+ mkdirSync10(ws.assetsDir, { recursive: true });
27309
27596
  const assets = discoverBrowserAssets(ws);
27310
27597
  if (assets.length === 0)
27311
27598
  return;
@@ -27316,7 +27603,7 @@ async function copyBrowserAssets(ws, cache, noCache) {
27316
27603
  to: a.to,
27317
27604
  mtime: mtimeOf(a.src)
27318
27605
  })));
27319
- const requiredOutputs = assets.map((a) => join9(ws.assetsDir, a.to));
27606
+ const requiredOutputs = assets.map((a) => join12(ws.assetsDir, a.to));
27320
27607
  if (!noCache && isCacheHit(cache, unitId, inputHash, requiredOutputs)) {
27321
27608
  console.log(` \u2713 cached: browser-assets (${assets.length})`);
27322
27609
  return;
@@ -27324,10 +27611,10 @@ async function copyBrowserAssets(ws, cache, noCache) {
27324
27611
  console.log(` building: browser-assets (${assets.length})`);
27325
27612
  const outputHashes = {};
27326
27613
  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));
27614
+ const dest = join12(ws.assetsDir, asset.to);
27615
+ mkdirSync10(dirname6(dest), { recursive: true });
27616
+ copyFileSync2(asset.src, dest);
27617
+ outputHashes[asset.to] = sha256Hex(readFileSync11(dest));
27331
27618
  }
27332
27619
  updateCache(cache, unitId, inputHash, { outputHashes });
27333
27620
  }
@@ -27348,7 +27635,7 @@ function collectArcPeerDeps(packages) {
27348
27635
  return [short, pkg];
27349
27636
  });
27350
27637
  }
27351
- var REACT_ENTRIES = [
27638
+ var REACT_ENTRIES2 = [
27352
27639
  [
27353
27640
  "react",
27354
27641
  `import React from "react";
@@ -27378,8 +27665,8 @@ export const { createPortal, flushSync } = ReactDOM;`
27378
27665
  `export { createRoot, hydrateRoot } from "react-dom/client";`
27379
27666
  ]
27380
27667
  ];
27381
- var REACT_OUTPUT_FILES = REACT_ENTRIES.map(([n]) => `${n}.js`);
27382
- var SHELL_BASE_EXTERNAL = [
27668
+ var REACT_OUTPUT_FILES = REACT_ENTRIES2.map(([n]) => `${n}.js`);
27669
+ var SHELL_BASE_EXTERNAL2 = [
27383
27670
  "react",
27384
27671
  "react-dom",
27385
27672
  "react/jsx-runtime",
@@ -27396,27 +27683,27 @@ var sourceFilter2 = (rel) => {
27396
27683
  return true;
27397
27684
  };
27398
27685
  function arcPkgSrcHash(rootDir, pkg) {
27399
- const srcDir = join9(rootDir, "node_modules", pkg, "src");
27400
- if (existsSync8(srcDir))
27686
+ const srcDir = join12(rootDir, "node_modules", pkg, "src");
27687
+ if (existsSync10(srcDir))
27401
27688
  return sha256OfDir(srcDir, sourceFilter2);
27402
- return sha256OfDir(join9(rootDir, "node_modules", pkg), sourceFilter2);
27689
+ return sha256OfDir(join12(rootDir, "node_modules", pkg), sourceFilter2);
27403
27690
  }
27404
27691
  async function buildShellReact(shellDir, tmpDir, rootDir, cache, noCache) {
27405
27692
  const unitId = "shell:react";
27406
27693
  const inputHash = sha256OfJson({
27407
27694
  react: readInstalledVersion(rootDir, "react"),
27408
27695
  "react-dom": readInstalledVersion(rootDir, "react-dom"),
27409
- entries: REACT_ENTRIES.map(([k, v]) => [k, v])
27696
+ entries: REACT_ENTRIES2.map(([k, v]) => [k, v])
27410
27697
  });
27411
- const requiredOutputs = REACT_OUTPUT_FILES.map((f) => join9(shellDir, f));
27698
+ const requiredOutputs = REACT_OUTPUT_FILES.map((f) => join12(shellDir, f));
27412
27699
  if (!noCache && isCacheHit(cache, unitId, inputHash, requiredOutputs)) {
27413
27700
  console.log(` \u2713 cached: shell:react`);
27414
27701
  return;
27415
27702
  }
27416
27703
  console.log(` building: shell:react`);
27417
27704
  const reactEps = [];
27418
- for (const [name, code] of REACT_ENTRIES) {
27419
- const f = join9(tmpDir, `${name}.ts`);
27705
+ for (const [name, code] of REACT_ENTRIES2) {
27706
+ const f = join12(tmpDir, `${name}.ts`);
27420
27707
  await Bun.write(f, code);
27421
27708
  reactEps.push(f);
27422
27709
  }
@@ -27443,16 +27730,16 @@ async function buildShellArcEntry(shortName, pkg, allArcPkgs, shellDir, tmpDir,
27443
27730
  pkg,
27444
27731
  version: readInstalledVersion(rootDir, pkg),
27445
27732
  src: arcPkgSrcHash(rootDir, pkg),
27446
- base: SHELL_BASE_EXTERNAL,
27733
+ base: SHELL_BASE_EXTERNAL2,
27447
27734
  others: [...otherExternals].sort()
27448
27735
  });
27449
- const outputFile = join9(shellDir, `${shortName}.js`);
27736
+ const outputFile = join12(shellDir, `${shortName}.js`);
27450
27737
  if (!noCache && isCacheHit(cache, unitId, inputHash, [outputFile])) {
27451
27738
  console.log(` \u2713 cached: ${unitId}`);
27452
27739
  return;
27453
27740
  }
27454
27741
  console.log(` building: ${unitId}`);
27455
- const f = join9(tmpDir, `${shortName}.ts`);
27742
+ const f = join12(tmpDir, `${shortName}.ts`);
27456
27743
  await Bun.write(f, `export * from "${pkg}";
27457
27744
  `);
27458
27745
  const r = await Bun.build({
@@ -27461,7 +27748,7 @@ async function buildShellArcEntry(shortName, pkg, allArcPkgs, shellDir, tmpDir,
27461
27748
  format: "esm",
27462
27749
  target: "browser",
27463
27750
  naming: "[name].[ext]",
27464
- external: [...SHELL_BASE_EXTERNAL, ...otherExternals],
27751
+ external: [...SHELL_BASE_EXTERNAL2, ...otherExternals],
27465
27752
  define: {
27466
27753
  ONLY_SERVER: "false",
27467
27754
  ONLY_BROWSER: "true",
@@ -27476,10 +27763,10 @@ async function buildShellArcEntry(shortName, pkg, allArcPkgs, shellDir, tmpDir,
27476
27763
  const outputHash = sha256OfFiles([outputFile]);
27477
27764
  updateCache(cache, unitId, inputHash, { outputHash });
27478
27765
  }
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 });
27766
+ async function buildShell2(ws, cache, noCache) {
27767
+ mkdirSync10(ws.shellDir, { recursive: true });
27768
+ const tmpDir = join12(ws.shellDir, "_tmp");
27769
+ mkdirSync10(tmpDir, { recursive: true });
27483
27770
  const arcEntries = collectArcPeerDeps(ws.packages);
27484
27771
  const allArcPkgs = arcEntries.map(([, pkg]) => pkg);
27485
27772
  const tasks = [
@@ -27487,7 +27774,7 @@ async function buildShell(ws, cache, noCache) {
27487
27774
  ...arcEntries.map(([short, pkg]) => () => buildShellArcEntry(short, pkg, allArcPkgs, ws.shellDir, tmpDir, ws.rootDir, cache, noCache))
27488
27775
  ];
27489
27776
  await pAll(tasks);
27490
- rmSync2(tmpDir, { recursive: true, force: true });
27777
+ rmSync3(tmpDir, { recursive: true, force: true });
27491
27778
  }
27492
27779
  async function loadServerContext(packages) {
27493
27780
  const ctxPackages = packages.filter((p) => isContextPackage(p.packageJson));
@@ -27496,13 +27783,13 @@ async function loadServerContext(packages) {
27496
27783
  globalThis.ONLY_SERVER = true;
27497
27784
  globalThis.ONLY_BROWSER = false;
27498
27785
  globalThis.ONLY_CLIENT = false;
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");
27786
+ const platformDir = join12(process.cwd(), "node_modules", "@arcote.tech", "platform");
27787
+ const platformPkg = JSON.parse(readFileSync11(join12(platformDir, "package.json"), "utf-8"));
27788
+ const platformEntry = join12(platformDir, platformPkg.main ?? "src/index.ts");
27502
27789
  await import(platformEntry);
27503
27790
  for (const ctx of ctxPackages) {
27504
- const serverDist = join9(ctx.path, "dist", "server", "main", "index.js");
27505
- if (!existsSync8(serverDist)) {
27791
+ const serverDist = join12(ctx.path, "dist", "server", "main", "index.js");
27792
+ if (!existsSync10(serverDist)) {
27506
27793
  err(`Context server dist not found: ${serverDist}`);
27507
27794
  continue;
27508
27795
  }
@@ -27533,19 +27820,19 @@ async function platformBuild(opts = {}) {
27533
27820
  }
27534
27821
 
27535
27822
  // src/commands/platform-deploy.ts
27536
- import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
27537
- import { join as join15 } from "path";
27823
+ import { existsSync as existsSync14, readFileSync as readFileSync14 } from "fs";
27824
+ import { join as join18 } from "path";
27538
27825
 
27539
27826
  // src/deploy/bootstrap.ts
27540
- import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync11 } from "fs";
27827
+ import { mkdirSync as mkdirSync13, writeFileSync as writeFileSync14 } from "fs";
27541
27828
  import { tmpdir as tmpdir3 } from "os";
27542
- import { join as join13 } from "path";
27829
+ import { join as join16 } from "path";
27543
27830
 
27544
27831
  // src/deploy/ansible.ts
27545
- var {spawn: spawn2 } = globalThis.Bun;
27546
- import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync8 } from "fs";
27547
- import { tmpdir } from "os";
27548
- import { join as join10 } from "path";
27832
+ import { spawn as nodeSpawn } from "child_process";
27833
+ import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync11 } from "fs";
27834
+ import { tmpdir as tmpdir2 } from "os";
27835
+ import { join as join13 } from "path";
27549
27836
 
27550
27837
  // src/deploy/assets.ts
27551
27838
  var TERRAFORM_MAIN_TF = `terraform {
@@ -27802,18 +28089,18 @@ var ASSETS = {
27802
28089
  }
27803
28090
  };
27804
28091
  async function materializeAssets(targetDir, files) {
27805
- const { mkdirSync: mkdirSync8, writeFileSync: writeFileSync8 } = await import("fs");
27806
- const { join: join10 } = await import("path");
27807
- mkdirSync8(targetDir, { recursive: true });
28092
+ const { mkdirSync: mkdirSync11, writeFileSync: writeFileSync11 } = await import("fs");
28093
+ const { join: join13 } = await import("path");
28094
+ mkdirSync11(targetDir, { recursive: true });
27808
28095
  for (const [name, content] of Object.entries(files)) {
27809
- writeFileSync8(join10(targetDir, name), content);
28096
+ writeFileSync11(join13(targetDir, name), content);
27810
28097
  }
27811
28098
  }
27812
28099
 
27813
28100
  // src/deploy/ansible.ts
27814
28101
  async function runAnsible(inputs) {
27815
- const workDir = join10(tmpdir(), "arc-deploy", `ansible-${Date.now()}`);
27816
- mkdirSync8(workDir, { recursive: true });
28102
+ const workDir = join13(tmpdir2(), "arc-deploy", `ansible-${Date.now()}`);
28103
+ mkdirSync11(workDir, { recursive: true });
27817
28104
  await materializeAssets(workDir, ASSETS.ansible);
27818
28105
  const user = inputs.asRoot ? "root" : inputs.target.user;
27819
28106
  const port = inputs.ansible?.sshPort ?? inputs.target.port;
@@ -27827,29 +28114,23 @@ async function runAnsible(inputs) {
27827
28114
  ""
27828
28115
  ].join(`
27829
28116
  `);
27830
- writeFileSync8(join10(workDir, "inventory.ini"), inventory);
27831
- const extraVars = [
27832
- `username=${inputs.target.user}`,
27833
- `ssh_port=${port}`
27834
- ];
27835
- if (inputs.ansible?.extraAllowedIps?.length) {
27836
- extraVars.push(`extra_allowed_ips=${JSON.stringify(inputs.ansible.extraAllowedIps)}`);
27837
- }
27838
- const proc2 = spawn2({
27839
- cmd: [
27840
- "ansible-playbook",
27841
- "-i",
27842
- "inventory.ini",
27843
- "site.yml",
27844
- "-e",
27845
- extraVars.join(" ")
27846
- ],
27847
- cwd: workDir,
27848
- stdout: "inherit",
27849
- stderr: "inherit",
27850
- env: { ...process.env, ANSIBLE_HOST_KEY_CHECKING: "False" }
28117
+ writeFileSync11(join13(workDir, "inventory.ini"), inventory);
28118
+ const extraVarsJson = JSON.stringify({
28119
+ username: inputs.target.user,
28120
+ ssh_port: port,
28121
+ extra_allowed_ips: inputs.ansible?.extraAllowedIps ?? []
28122
+ });
28123
+ const exit = await new Promise((resolve, reject) => {
28124
+ const proc2 = nodeSpawn("ansible-playbook", ["-i", "inventory.ini", "site.yml", "-e", extraVarsJson], {
28125
+ cwd: workDir,
28126
+ stdio: ["ignore", "pipe", "pipe"],
28127
+ env: { ...process.env, ANSIBLE_HOST_KEY_CHECKING: "False" }
28128
+ });
28129
+ proc2.stdout?.on("data", (chunk) => process.stdout.write(chunk));
28130
+ proc2.stderr?.on("data", (chunk) => process.stderr.write(chunk));
28131
+ proc2.on("error", reject);
28132
+ proc2.on("exit", (code) => resolve(code ?? 1));
27851
28133
  });
27852
- const exit = await proc2.exited;
27853
28134
  if (exit !== 0) {
27854
28135
  throw new Error(`ansible-playbook failed (exit ${exit})`);
27855
28136
  }
@@ -27895,7 +28176,8 @@ function generateCaddyfile(cfg) {
27895
28176
  }
27896
28177
 
27897
28178
  // src/deploy/compose.ts
27898
- function generateCompose({ cfg }) {
28179
+ var RESERVED_ENV = new Set(["PORT", "ARC_DEPLOY_API", "ARC_CLI_VERSION"]);
28180
+ function generateCompose({ cfg, cliVersion }) {
27899
28181
  const lines = [];
27900
28182
  lines.push("# Generated by `arc platform deploy` \u2014 do not edit by hand.");
27901
28183
  lines.push("");
@@ -27916,20 +28198,26 @@ function generateCompose({ cfg }) {
27916
28198
  lines.push("");
27917
28199
  for (const [name, env2] of Object.entries(cfg.envs)) {
27918
28200
  lines.push(` arc-${name}:`);
27919
- lines.push(" image: oven/bun:1-alpine");
28201
+ lines.push(" image: pkrasinski/arc-runtime:1");
27920
28202
  lines.push(" restart: unless-stopped");
27921
- lines.push(` working_dir: /app`);
27922
28203
  lines.push(" volumes:");
27923
- lines.push(` - ${cfg.target.remoteDir}/${name}:/app`);
28204
+ lines.push(` - arc-platform-${name}:/app/.arc/platform`);
27924
28205
  lines.push(` - arc-data-${name}:/app/.arc/data`);
28206
+ lines.push(" - arc-cli-cache:/app/.arc/cli");
28207
+ lines.push(" - arc-bun-cache:/root/.bun/install/cache");
27925
28208
  lines.push(" environment:");
27926
28209
  lines.push(" PORT: 5005");
27927
28210
  lines.push(' ARC_DEPLOY_API: "1"');
27928
- lines.push(" NODE_ENV: production");
27929
- for (const [k, v] of Object.entries(env2.envVars ?? {})) {
28211
+ lines.push(` ARC_CLI_VERSION: ${JSON.stringify(cliVersion)}`);
28212
+ const userEnv = env2.envVars ?? {};
28213
+ if (!("NODE_ENV" in userEnv)) {
28214
+ lines.push(" NODE_ENV: production");
28215
+ }
28216
+ for (const [k, v] of Object.entries(userEnv)) {
28217
+ if (RESERVED_ENV.has(k))
28218
+ continue;
27930
28219
  lines.push(` ${k}: ${JSON.stringify(v)}`);
27931
28220
  }
27932
- lines.push(' command: ["node_modules/.bin/arc", "platform", "start"]');
27933
28221
  lines.push(" networks:");
27934
28222
  lines.push(" - arc-net");
27935
28223
  lines.push(" expose:");
@@ -27942,7 +28230,10 @@ function generateCompose({ cfg }) {
27942
28230
  lines.push("volumes:");
27943
28231
  lines.push(" caddy_data:");
27944
28232
  lines.push(" caddy_config:");
28233
+ lines.push(" arc-cli-cache:");
28234
+ lines.push(" arc-bun-cache:");
27945
28235
  for (const [name] of Object.entries(cfg.envs)) {
28236
+ lines.push(` arc-platform-${name}:`);
27946
28237
  lines.push(` arc-data-${name}:`);
27947
28238
  }
27948
28239
  return lines.join(`
@@ -27951,16 +28242,18 @@ function generateCompose({ cfg }) {
27951
28242
  }
27952
28243
 
27953
28244
  // src/deploy/terraform.ts
27954
- var {spawn: spawn3 } = globalThis.Bun;
27955
- import { existsSync as existsSync9, mkdirSync as mkdirSync9, writeFileSync as writeFileSync9 } from "fs";
27956
- import { tmpdir as tmpdir2 } from "os";
27957
- import { join as join11 } from "path";
28245
+ import { spawn as nodeSpawn2 } from "child_process";
28246
+ import { createHash as createHash2 } from "crypto";
28247
+ import { existsSync as existsSync11, mkdirSync as mkdirSync12, writeFileSync as writeFileSync12 } from "fs";
28248
+ import { homedir } from "os";
28249
+ import { join as join14 } from "path";
27958
28250
  async function runTerraform(inputs) {
27959
- const workDir = join11(tmpdir2(), "arc-deploy", `tf-${Date.now()}`);
27960
- mkdirSync9(workDir, { recursive: true });
28251
+ const wsHash = createHash2("sha256").update(inputs.workspaceDir).digest("hex").slice(0, 16);
28252
+ const workDir = join14(homedir(), ".arc-deploy", wsHash, "tf");
28253
+ mkdirSync12(workDir, { recursive: true });
27961
28254
  await materializeAssets(workDir, ASSETS.terraform);
27962
28255
  const sshPubKey = inputs.tf.sshPublicKey ?? expandHome("~/.ssh/id_ed25519.pub");
27963
- if (!existsSync9(expandHome(sshPubKey))) {
28256
+ if (!existsSync11(expandHome(sshPubKey))) {
27964
28257
  throw new Error(`SSH public key not found at ${sshPubKey}. Set provision.terraform.sshPublicKey in deploy.arc.json.`);
27965
28258
  }
27966
28259
  const tfvars = [
@@ -27973,7 +28266,7 @@ async function runTerraform(inputs) {
27973
28266
  ].join(`
27974
28267
  `) + `
27975
28268
  `;
27976
- writeFileSync9(join11(workDir, "terraform.tfvars"), tfvars);
28269
+ writeFileSync12(join14(workDir, "terraform.tfvars"), tfvars);
27977
28270
  await runTf(workDir, ["init", "-input=false", "-no-color"]);
27978
28271
  await runTf(workDir, [
27979
28272
  "apply",
@@ -27990,28 +28283,34 @@ async function runTerraform(inputs) {
27990
28283
  return { serverIp: ip.trim(), serverName: inputs.serverName, workDir };
27991
28284
  }
27992
28285
  async function runTf(workDir, args) {
27993
- const proc2 = spawn3({
27994
- cmd: ["terraform", ...args],
27995
- cwd: workDir,
27996
- stdout: "inherit",
27997
- stderr: "inherit"
28286
+ const exit = await new Promise((resolve, reject) => {
28287
+ const proc2 = nodeSpawn2("terraform", args, {
28288
+ cwd: workDir,
28289
+ stdio: ["ignore", "pipe", "pipe"]
28290
+ });
28291
+ proc2.stdout?.on("data", (chunk) => process.stdout.write(chunk));
28292
+ proc2.stderr?.on("data", (chunk) => process.stderr.write(chunk));
28293
+ proc2.on("error", reject);
28294
+ proc2.on("exit", (code) => resolve(code ?? 1));
27998
28295
  });
27999
- const exit = await proc2.exited;
28000
28296
  if (exit !== 0) {
28001
28297
  throw new Error(`terraform ${args[0]} failed (exit ${exit})`);
28002
28298
  }
28003
28299
  }
28004
28300
  async function runTfCapture(workDir, args) {
28005
- const proc2 = spawn3({
28006
- cmd: ["terraform", ...args],
28007
- cwd: workDir,
28008
- stdout: "pipe",
28009
- stderr: "pipe"
28301
+ let stdout = "";
28302
+ const exit = await new Promise((resolve, reject) => {
28303
+ const proc2 = nodeSpawn2("terraform", args, {
28304
+ cwd: workDir,
28305
+ stdio: ["ignore", "pipe", "pipe"]
28306
+ });
28307
+ proc2.stdout?.on("data", (chunk) => {
28308
+ stdout += chunk.toString("utf-8");
28309
+ });
28310
+ proc2.stderr?.on("data", (chunk) => process.stderr.write(chunk));
28311
+ proc2.on("error", reject);
28312
+ proc2.on("exit", (code) => resolve(code ?? 1));
28010
28313
  });
28011
- const [stdout, exit] = await Promise.all([
28012
- new Response(proc2.stdout).text(),
28013
- proc2.exited
28014
- ]);
28015
28314
  if (exit !== 0) {
28016
28315
  throw new Error(`terraform ${args[0]} failed (exit ${exit})`);
28017
28316
  }
@@ -28025,21 +28324,21 @@ function expandHome(p) {
28025
28324
  }
28026
28325
 
28027
28326
  // src/deploy/config.ts
28028
- import { existsSync as existsSync10, readFileSync as readFileSync9, writeFileSync as writeFileSync10 } from "fs";
28029
- import { join as join12 } from "path";
28327
+ import { existsSync as existsSync12, readFileSync as readFileSync12, writeFileSync as writeFileSync13 } from "fs";
28328
+ import { join as join15 } from "path";
28030
28329
  var DEPLOY_CONFIG_FILE = "deploy.arc.json";
28031
28330
  function deployConfigPath(rootDir) {
28032
- return join12(rootDir, DEPLOY_CONFIG_FILE);
28331
+ return join15(rootDir, DEPLOY_CONFIG_FILE);
28033
28332
  }
28034
28333
  function deployConfigExists(rootDir) {
28035
- return existsSync10(deployConfigPath(rootDir));
28334
+ return existsSync12(deployConfigPath(rootDir));
28036
28335
  }
28037
28336
  function loadDeployConfig(rootDir) {
28038
28337
  const path4 = deployConfigPath(rootDir);
28039
- if (!existsSync10(path4)) {
28338
+ if (!existsSync12(path4)) {
28040
28339
  throw new Error(`Missing ${DEPLOY_CONFIG_FILE} at ${path4}`);
28041
28340
  }
28042
- const raw = readFileSync9(path4, "utf-8");
28341
+ const raw = readFileSync12(path4, "utf-8");
28043
28342
  let parsed;
28044
28343
  try {
28045
28344
  parsed = JSON.parse(raw);
@@ -28050,7 +28349,10 @@ function loadDeployConfig(rootDir) {
28050
28349
  return validateDeployConfig(expanded);
28051
28350
  }
28052
28351
  function saveDeployConfig(rootDir, cfg) {
28053
- writeFileSync10(deployConfigPath(rootDir), JSON.stringify(cfg, null, 2) + `
28352
+ const path4 = deployConfigPath(rootDir);
28353
+ const raw = existsSync12(path4) ? JSON.parse(readFileSync12(path4, "utf-8")) : {};
28354
+ raw.target = { ...raw.target, ...cfg.target };
28355
+ writeFileSync13(path4, JSON.stringify(raw, null, 2) + `
28054
28356
  `);
28055
28357
  }
28056
28358
  var VAR_REGEX = /\$\{([A-Z0-9_]+)\}|\$([A-Z0-9_]+)/g;
@@ -28201,7 +28503,7 @@ function cfgErr(path4, expected) {
28201
28503
  }
28202
28504
 
28203
28505
  // src/deploy/ssh.ts
28204
- var {spawn: spawn4 } = globalThis.Bun;
28506
+ var {spawn: spawn3 } = globalThis.Bun;
28205
28507
  async function streamToString(stream2) {
28206
28508
  if (!stream2 || typeof stream2 === "number")
28207
28509
  return "";
@@ -28227,7 +28529,7 @@ async function sshExec(target, cmd, opts = {}) {
28227
28529
  "--",
28228
28530
  cmd
28229
28531
  ];
28230
- const proc2 = spawn4({
28532
+ const proc2 = spawn3({
28231
28533
  cmd: ["ssh", ...args],
28232
28534
  stdin: opts.stdin ? "pipe" : "ignore",
28233
28535
  stdout: "pipe",
@@ -28283,7 +28585,7 @@ async function scpUpload(target, localPath, remotePath) {
28283
28585
  if (target.sshKey)
28284
28586
  args.push("-i", target.sshKey);
28285
28587
  args.push(localPath, `${target.user}@${target.host}:${remotePath}`);
28286
- const proc2 = spawn4({ cmd: ["scp", ...args], stderr: "pipe" });
28588
+ const proc2 = spawn3({ cmd: ["scp", ...args], stderr: "pipe" });
28287
28589
  const [stderr, exitCode] = await Promise.all([
28288
28590
  streamToString(proc2.stderr),
28289
28591
  proc2.exited
@@ -28292,29 +28594,6 @@ async function scpUpload(target, localPath, remotePath) {
28292
28594
  throw new Error(`scp failed (${exitCode}): ${stderr}`);
28293
28595
  }
28294
28596
  }
28295
- async function rsyncDir(target, localDir, remoteDir, opts = {}) {
28296
- const sshCmdParts = ["ssh", "-p", String(target.port)];
28297
- if (target.sshKey)
28298
- sshCmdParts.push("-i", target.sshKey);
28299
- const sshCmd = sshCmdParts.join(" ");
28300
- const args = ["-azL", "-e", sshCmd];
28301
- if (opts.delete)
28302
- args.push("--delete");
28303
- const src = localDir.endsWith("/") ? localDir : `${localDir}/`;
28304
- args.push(src, `${target.user}@${target.host}:${remoteDir}`);
28305
- const proc2 = spawn4({
28306
- cmd: ["rsync", ...args],
28307
- stderr: "pipe",
28308
- stdout: "pipe"
28309
- });
28310
- const [stderr, exitCode] = await Promise.all([
28311
- streamToString(proc2.stderr),
28312
- proc2.exited
28313
- ]);
28314
- if (exitCode !== 0) {
28315
- throw new Error(`rsync failed (${exitCode}): ${stderr}`);
28316
- }
28317
- }
28318
28597
  async function openTunnel(target, localPort, remoteHost, remotePort) {
28319
28598
  const args = [
28320
28599
  ...baseSshArgs(target),
@@ -28323,7 +28602,7 @@ async function openTunnel(target, localPort, remoteHost, remotePort) {
28323
28602
  `${localPort}:${remoteHost}:${remotePort}`,
28324
28603
  `${target.user}@${target.host}`
28325
28604
  ];
28326
- const proc2 = spawn4({
28605
+ const proc2 = spawn3({
28327
28606
  cmd: ["ssh", ...args],
28328
28607
  stdin: "ignore",
28329
28608
  stdout: "pipe",
@@ -28369,6 +28648,9 @@ async function detectRemoteState(cfg) {
28369
28648
  return { kind: "unreachable", reason: "target.host not yet set" };
28370
28649
  }
28371
28650
  if (!await canSsh(cfg.target)) {
28651
+ if (await canSsh({ ...cfg.target, user: "root" })) {
28652
+ return { kind: "no-docker" };
28653
+ }
28372
28654
  return { kind: "unreachable", reason: "ssh connection failed" };
28373
28655
  }
28374
28656
  const dockerCheck = await sshExec(cfg.target, "command -v docker", {
@@ -28419,7 +28701,8 @@ async function bootstrap(inputs) {
28419
28701
  const tfOut = await runTerraform({
28420
28702
  tf: cfg.provision.terraform,
28421
28703
  token,
28422
- serverName: `arc-${Object.keys(cfg.envs)[0] ?? "host"}`
28704
+ serverName: `arc-${Object.keys(cfg.envs)[0] ?? "host"}`,
28705
+ workspaceDir: rootDir
28423
28706
  });
28424
28707
  ok(`Server provisioned: ${tfOut.serverIp}`);
28425
28708
  cfg.target.host = tfOut.serverIp;
@@ -28430,7 +28713,8 @@ async function bootstrap(inputs) {
28430
28713
  }
28431
28714
  if (state.kind === "unreachable" || state.kind === "no-docker") {
28432
28715
  log2("Running Ansible bootstrap (Docker + firewall + SSH hardening)...");
28433
- const asRoot = state.kind === "unreachable";
28716
+ const deployUserWorks = state.kind === "no-docker" && await canSsh(cfg.target);
28717
+ const asRoot = !deployUserWorks;
28434
28718
  await runAnsible({
28435
28719
  target: cfg.target,
28436
28720
  ansible: cfg.provision?.ansible,
@@ -28450,125 +28734,185 @@ async function bootstrap(inputs) {
28450
28734
  }
28451
28735
  async function upStack(inputs) {
28452
28736
  const { cfg } = inputs;
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 }));
28737
+ const workDir = join16(tmpdir3(), "arc-deploy", `stack-${Date.now()}`);
28738
+ mkdirSync13(workDir, { recursive: true });
28739
+ writeFileSync14(join16(workDir, "Caddyfile"), generateCaddyfile(cfg));
28740
+ writeFileSync14(join16(workDir, "docker-compose.yml"), generateCompose({ cfg, cliVersion: inputs.cliVersion }));
28457
28741
  await assertExec(cfg.target, `sudo mkdir -p ${cfg.target.remoteDir} && sudo chown ${cfg.target.user}:${cfg.target.user} ${cfg.target.remoteDir}`);
28458
28742
  for (const name of Object.keys(cfg.envs)) {
28459
28743
  await assertExec(cfg.target, `mkdir -p ${cfg.target.remoteDir}/${name}`);
28460
28744
  }
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`);
28745
+ await scpUpload(cfg.target, join16(workDir, "Caddyfile"), `${cfg.target.remoteDir}/Caddyfile`);
28746
+ await scpUpload(cfg.target, join16(workDir, "docker-compose.yml"), `${cfg.target.remoteDir}/docker-compose.yml`);
28463
28747
  await assertExec(cfg.target, `cd ${cfg.target.remoteDir} && docker compose pull --ignore-pull-failures && docker compose up -d`);
28464
28748
  }
28465
28749
 
28466
28750
  // src/deploy/remote-sync.ts
28467
- import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
28468
- import { join as join14, relative as relative4 } from "path";
28751
+ import { existsSync as existsSync13, readFileSync as readFileSync13 } from "fs";
28752
+ import { basename as basename5, join as join17 } from "path";
28469
28753
  function diffManifests(local, remote) {
28470
28754
  const remoteByName = new Map(remote.modules.map((m) => [m.name, m]));
28471
28755
  const changedModules = local.modules.filter((m) => remoteByName.get(m.name)?.hash !== m.hash);
28472
28756
  return {
28473
28757
  changedModules: [...changedModules],
28474
- shellChanged: local.shellHash !== remote.shellHash,
28475
28758
  stylesChanged: local.stylesHash !== remote.stylesHash
28476
28759
  };
28477
28760
  }
28478
28761
  async function syncEnv(inputs) {
28479
- const { cfg, env: env2, ws, projectDir } = inputs;
28762
+ const { cfg, env: env2, ws } = inputs;
28480
28763
  const envConfig = cfg.envs[env2];
28481
28764
  if (!envConfig)
28482
28765
  throw new Error(`Unknown env: ${env2}`);
28483
- const remotePath = `${cfg.target.remoteDir}/${env2}`;
28484
- await rsyncDir(cfg.target, projectDir, remotePath);
28485
- const localManifestPath = join14(ws.modulesDir, "manifest.json");
28486
- if (!existsSync11(localManifestPath)) {
28766
+ const localManifestPath = join17(ws.modulesDir, "manifest.json");
28767
+ if (!existsSync13(localManifestPath)) {
28487
28768
  throw new Error(`Local build missing at ${localManifestPath}. Run arc platform build first.`);
28488
28769
  }
28489
- const localManifest = JSON.parse(readFileSync10(localManifestPath, "utf-8"));
28490
- const localPort = 15500 + hashEnvToOffset(env2);
28491
- const tunnel = await openTunnel(cfg.target, localPort, "127.0.0.1", 2019);
28770
+ const localManifest = JSON.parse(readFileSync13(localManifestPath, "utf-8"));
28771
+ const pkgByName = new Map(ws.packages.map((p) => [p.name.includes("/") ? p.name.split("/").pop() : p.name, p]));
28772
+ let tunnel = await openTunnel(cfg.target, 15500 + hashEnvToOffset(env2), "127.0.0.1", 2019);
28773
+ let restarts = 0;
28774
+ let frameworkChanged = false;
28492
28775
  try {
28493
- const base2 = `http://127.0.0.1:${localPort}/env/${env2}`;
28494
- const remoteManifestRes = await fetch(`${base2}/api/deploy/manifest`);
28495
- if (!remoteManifestRes.ok) {
28496
- throw new Error(`Failed to fetch remote manifest: ${remoteManifestRes.status}`);
28497
- }
28498
- const remoteManifest = await remoteManifestRes.json();
28499
- const diff = diffManifests(localManifest, remoteManifest);
28500
- if (diff.shellChanged) {
28501
- const shellFiles = collectFiles(ws.shellDir);
28776
+ const base2 = () => `http://127.0.0.1:${tunnel.localPort}/env/${env2}`;
28777
+ const localFrameworkHash = readDepsHash(join17(ws.arcDir, ".deps-hash"));
28778
+ const remoteFwRes = await fetch(`${base2()}/api/deploy/framework`);
28779
+ const remoteFw = remoteFwRes.ok ? await remoteFwRes.json() : { depsHash: null };
28780
+ if (localFrameworkHash && localFrameworkHash !== remoteFw.depsHash) {
28781
+ console.log("[arc] Pushing framework deps...");
28502
28782
  const form = new FormData;
28503
- for (const absPath of shellFiles) {
28504
- const rel = relative4(ws.shellDir, absPath);
28505
- form.append(rel, new Blob([readFileSync10(absPath)]), rel);
28783
+ form.append("package.json", new Blob([readFileSync13(join17(ws.arcDir, "package.json"))]), "package.json");
28784
+ const lockPath = join17(ws.arcDir, "bun.lock");
28785
+ if (existsSync13(lockPath)) {
28786
+ form.append("bun.lock", new Blob([readFileSync13(lockPath)]), "bun.lock");
28506
28787
  }
28507
- const res2 = await fetch(`${base2}/api/deploy/shell`, {
28788
+ const res = await fetch(`${base2()}/api/deploy/framework`, {
28508
28789
  method: "POST",
28509
28790
  body: form
28510
28791
  });
28511
- if (!res2.ok)
28512
- throw new Error(`Shell upload failed: ${res2.status} ${await res2.text()}`);
28513
- }
28514
- if (diff.stylesChanged) {
28792
+ if (!res.ok) {
28793
+ throw new Error(`framework push failed: ${res.status} ${await res.text()}`);
28794
+ }
28795
+ frameworkChanged = true;
28796
+ const result = await res.json();
28797
+ if (result.needsRestart) {
28798
+ tunnel = await restartAndReopen(cfg, env2, tunnel);
28799
+ restarts += 1;
28800
+ }
28801
+ }
28802
+ const remoteManifestRes = await fetch(`${base2()}/api/deploy/manifest`);
28803
+ const remoteManifest = remoteManifestRes.ok ? await remoteManifestRes.json() : {
28804
+ modules: [],
28805
+ shellHash: "",
28806
+ stylesHash: "",
28807
+ buildTime: ""
28808
+ };
28809
+ const diff = diffManifests(localManifest, remoteManifest);
28810
+ for (const mod of diff.changedModules) {
28811
+ const safeName = sanitizeName(mod.name);
28812
+ const moduleDir = join17(ws.modulesDir, safeName);
28813
+ const browserPath = join17(moduleDir, "browser.js");
28814
+ const serverPath = join17(moduleDir, "server.js");
28815
+ const pkgPath = join17(moduleDir, "package.json");
28816
+ const accessPath = join17(moduleDir, "access.json");
28817
+ const browserActual = existsSync13(browserPath) ? browserPath : join17(ws.modulesDir, `${safeName}.js`);
28818
+ if (!existsSync13(browserActual)) {
28819
+ throw new Error(`Missing browser bundle for module ${mod.name}`);
28820
+ }
28515
28821
  const form = new FormData;
28516
- for (const name of ["styles.css", "theme.css"]) {
28517
- const p = join14(ws.arcDir, name);
28518
- if (existsSync11(p)) {
28519
- form.append(name, new Blob([readFileSync10(p)]), name);
28520
- }
28822
+ form.append("browser.js", new Blob([readFileSync13(browserActual)]), "browser.js");
28823
+ const pkg = pkgByName.get(safeName);
28824
+ if (pkg && isContextPackage(pkg.packageJson) && existsSync13(serverPath)) {
28825
+ form.append("server.js", new Blob([readFileSync13(serverPath)]), "server.js");
28826
+ }
28827
+ if (existsSync13(pkgPath)) {
28828
+ form.append("package.json", new Blob([readFileSync13(pkgPath)]), "package.json");
28521
28829
  }
28522
- const res2 = await fetch(`${base2}/api/deploy/shell`, {
28830
+ if (existsSync13(accessPath)) {
28831
+ form.append("access.json", new Blob([readFileSync13(accessPath)]), "access.json");
28832
+ }
28833
+ console.log(`[arc] Pushing module ${safeName}...`);
28834
+ const res = await fetch(`${base2()}/api/deploy/modules/${safeName}`, {
28523
28835
  method: "POST",
28524
28836
  body: form
28525
28837
  });
28526
- if (!res2.ok)
28527
- throw new Error(`Styles upload failed: ${res2.status} ${await res2.text()}`);
28838
+ if (!res.ok) {
28839
+ throw new Error(`module ${safeName} push failed: ${res.status} ${await res.text()}`);
28840
+ }
28528
28841
  }
28529
- if (diff.changedModules.length > 0) {
28842
+ if (diff.stylesChanged) {
28843
+ console.log("[arc] Pushing styles...");
28530
28844
  const form = new FormData;
28531
- for (const mod of diff.changedModules) {
28532
- const p = join14(ws.modulesDir, mod.file);
28533
- form.append(mod.file, new Blob([readFileSync10(p)]), mod.file);
28845
+ for (const name of ["styles.css", "theme.css"]) {
28846
+ const p = join17(ws.arcDir, name);
28847
+ if (existsSync13(p)) {
28848
+ form.append(name, new Blob([readFileSync13(p)]), name);
28849
+ }
28534
28850
  }
28535
- const res2 = await fetch(`${base2}/api/deploy/modules`, {
28851
+ const res = await fetch(`${base2()}/api/deploy/styles`, {
28536
28852
  method: "POST",
28537
28853
  body: form
28538
28854
  });
28539
- if (!res2.ok)
28540
- throw new Error(`Modules upload failed: ${res2.status} ${await res2.text()}`);
28855
+ if (!res.ok) {
28856
+ throw new Error(`styles push failed: ${res.status} ${await res.text()}`);
28857
+ }
28541
28858
  }
28542
- const res = await fetch(`${base2}/api/deploy/manifest`, {
28859
+ const commitRes = await fetch(`${base2()}/api/deploy/manifest`, {
28543
28860
  method: "POST",
28544
28861
  headers: { "Content-Type": "application/json" },
28545
28862
  body: JSON.stringify(localManifest)
28546
28863
  });
28547
- if (!res.ok)
28548
- throw new Error(`Manifest update failed: ${res.status} ${await res.text()}`);
28864
+ if (!commitRes.ok) {
28865
+ throw new Error(`manifest commit failed: ${commitRes.status} ${await commitRes.text()}`);
28866
+ }
28867
+ const commit = await commitRes.json();
28868
+ if (commit.needsRestart) {
28869
+ tunnel = await restartAndReopen(cfg, env2, tunnel);
28870
+ restarts += 1;
28871
+ }
28549
28872
  return {
28550
28873
  env: env2,
28874
+ frameworkChanged,
28551
28875
  changedModules: diff.changedModules.map((m) => m.name),
28552
- shellChanged: diff.shellChanged,
28553
- stylesChanged: diff.stylesChanged
28876
+ stylesChanged: diff.stylesChanged,
28877
+ restarts
28554
28878
  };
28555
28879
  } finally {
28556
28880
  tunnel.close();
28557
28881
  }
28558
28882
  }
28559
- function collectFiles(dir) {
28560
- if (!existsSync11(dir))
28561
- return [];
28562
- const { readdirSync: readdirSync6 } = __require("fs");
28563
- const out = [];
28564
- for (const entry of readdirSync6(dir, { withFileTypes: true })) {
28565
- const p = join14(dir, entry.name);
28566
- if (entry.isDirectory())
28567
- out.push(...collectFiles(p));
28568
- else if (entry.isFile())
28569
- out.push(p);
28883
+ function readDepsHash(path4) {
28884
+ if (!existsSync13(path4))
28885
+ return null;
28886
+ return readFileSync13(path4, "utf-8").trim() || null;
28887
+ }
28888
+ function sanitizeName(name) {
28889
+ return basename5(name);
28890
+ }
28891
+ async function restartAndReopen(cfg, env2, oldTunnel) {
28892
+ console.log(`[arc] Restarting arc-${env2}...`);
28893
+ oldTunnel.close();
28894
+ await assertExec(cfg.target, `docker restart arc-${env2}`);
28895
+ const tunnel = await openTunnel(cfg.target, 15500 + hashEnvToOffset(env2), "127.0.0.1", 2019);
28896
+ await waitForHealthy(`http://127.0.0.1:${tunnel.localPort}/env/${env2}`, 60000);
28897
+ return tunnel;
28898
+ }
28899
+ async function waitForHealthy(baseUrl, timeoutMs) {
28900
+ const deadline = Date.now() + timeoutMs;
28901
+ let lastErr;
28902
+ while (Date.now() < deadline) {
28903
+ try {
28904
+ const res = await fetch(`${baseUrl}/api/deploy/health`, {
28905
+ signal: AbortSignal.timeout(2000)
28906
+ });
28907
+ if (res.ok)
28908
+ return;
28909
+ lastErr = `status ${res.status}`;
28910
+ } catch (e) {
28911
+ lastErr = e;
28912
+ }
28913
+ await new Promise((r) => setTimeout(r, 1000));
28570
28914
  }
28571
- return out;
28915
+ throw new Error(`Health check timeout: ${String(lastErr)}`);
28572
28916
  }
28573
28917
  function hashEnvToOffset(env2) {
28574
28918
  let h = 0;
@@ -29274,13 +29618,13 @@ async function platformDeploy(envArg, options = {}) {
29274
29618
  err(`Unknown env "${envArg}". Known: ${Object.keys(cfg.envs).join(", ")}`);
29275
29619
  process.exit(1);
29276
29620
  })() : Object.keys(cfg.envs);
29277
- const manifestPath = join15(ws.modulesDir, "manifest.json");
29278
- const needBuild = options.rebuild || !existsSync12(manifestPath);
29621
+ const manifestPath = join18(ws.modulesDir, "manifest.json");
29622
+ const needBuild = options.rebuild || !existsSync14(manifestPath);
29279
29623
  if (needBuild && !options.skipBuild) {
29280
29624
  log2("Building platform...");
29281
29625
  await buildAll(ws, { noCache: options.rebuild });
29282
29626
  ok("Build complete");
29283
- } else if (!existsSync12(manifestPath)) {
29627
+ } else if (!existsSync14(manifestPath)) {
29284
29628
  err("No build found and --skip-build was set.");
29285
29629
  process.exit(1);
29286
29630
  }
@@ -29323,24 +29667,24 @@ async function platformDeploy(envArg, options = {}) {
29323
29667
  }
29324
29668
  function readCliVersion() {
29325
29669
  try {
29326
- const pkgPath = join15(import.meta.dir, "..", "..", "package.json");
29327
- const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
29670
+ const pkgPath = join18(import.meta.dir, "..", "..", "package.json");
29671
+ const pkg = JSON.parse(readFileSync14(pkgPath, "utf-8"));
29328
29672
  return pkg.version ?? "unknown";
29329
29673
  } catch {
29330
29674
  return "unknown";
29331
29675
  }
29332
29676
  }
29333
29677
  async function hashDeployConfig(rootDir) {
29334
- const p2 = join15(rootDir, "deploy.arc.json");
29335
- const content = readFileSync11(p2);
29678
+ const p2 = join18(rootDir, "deploy.arc.json");
29679
+ const content = readFileSync14(p2);
29336
29680
  const hasher = new Bun.CryptoHasher("sha256");
29337
29681
  hasher.update(content);
29338
29682
  return hasher.digest("hex").slice(0, 16);
29339
29683
  }
29340
29684
 
29341
29685
  // src/commands/platform-dev.ts
29342
- import { existsSync as existsSync15, watch } from "fs";
29343
- import { join as join18 } from "path";
29686
+ import { existsSync as existsSync17, watch } from "fs";
29687
+ import { join as join21 } from "path";
29344
29688
 
29345
29689
  // ../host/src/create-server.ts
29346
29690
  var import_jsonwebtoken = __toESM(require_jsonwebtoken(), 1);
@@ -30790,92 +31134,222 @@ async function createArcServer(config) {
30790
31134
  };
30791
31135
  }
30792
31136
  // src/platform/server.ts
30793
- import { existsSync as existsSync14, mkdirSync as mkdirSync12 } from "fs";
30794
- import { join as join17 } from "path";
31137
+ import { existsSync as existsSync16, mkdirSync as mkdirSync15 } from "fs";
31138
+ import { join as join20 } from "path";
30795
31139
 
30796
31140
  // src/platform/deploy-api.ts
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";
31141
+ var {spawn: spawn4 } = globalThis.Bun;
31142
+ import {
31143
+ cpSync,
31144
+ existsSync as existsSync15,
31145
+ mkdirSync as mkdirSync14,
31146
+ readFileSync as readFileSync15,
31147
+ rmSync as rmSync4,
31148
+ writeFileSync as writeFileSync15
31149
+ } from "fs";
31150
+ import { dirname as dirname7, join as join19, normalize as normalize2, resolve } from "path";
30799
31151
  function createDeployApiHandler(opts) {
30800
31152
  return async (req, url, ctx) => {
30801
31153
  const p3 = url.pathname;
30802
31154
  if (!p3.startsWith("/api/deploy/"))
30803
31155
  return null;
31156
+ const cors = ctx.corsHeaders;
30804
31157
  if (p3 === "/api/deploy/health" && req.method === "GET") {
30805
- return Response.json({ ok: true, modules: opts.getManifest().modules.length }, { headers: ctx.corsHeaders });
31158
+ return Response.json({ ok: true, modules: opts.getManifest().modules.length }, { headers: cors });
31159
+ }
31160
+ if (p3 === "/api/deploy/framework" && req.method === "GET") {
31161
+ const hashPath = join19(opts.ws.arcDir, ".deps-hash");
31162
+ const depsHash = existsSync15(hashPath) ? readFileSync15(hashPath, "utf-8").trim() : null;
31163
+ return Response.json({ depsHash }, { headers: cors });
30806
31164
  }
30807
31165
  if (p3 === "/api/deploy/manifest" && req.method === "GET") {
30808
- return Response.json(opts.getManifest(), { headers: ctx.corsHeaders });
31166
+ return Response.json(opts.getManifest(), { headers: cors });
30809
31167
  }
30810
- if (p3 === "/api/deploy/manifest" && req.method === "POST") {
30811
- const body = await req.json();
30812
- if (!validateManifest(body)) {
30813
- return Response.json({ error: "Invalid manifest body" }, { status: 400, headers: ctx.corsHeaders });
31168
+ if (p3 === "/api/deploy/framework" && req.method === "POST") {
31169
+ const form = await req.formData();
31170
+ const pkgFile = form.get("package.json");
31171
+ const lockFile = form.get("bun.lock");
31172
+ if (!isFile(pkgFile) || !isFile(lockFile)) {
31173
+ return badRequest("framework requires package.json + bun.lock", cors);
31174
+ }
31175
+ mkdirSync14(opts.ws.arcDir, { recursive: true });
31176
+ writeFileSync15(join19(opts.ws.arcDir, "package.json"), Buffer.from(await pkgFile.arrayBuffer()));
31177
+ writeFileSync15(join19(opts.ws.arcDir, "bun.lock"), Buffer.from(await lockFile.arrayBuffer()));
31178
+ const start = Date.now();
31179
+ const installOk = await runBun(["install", "--production", "--frozen-lockfile"], opts.ws.arcDir);
31180
+ if (!installOk) {
31181
+ return Response.json({ error: "bun install failed" }, { status: 500, headers: cors });
31182
+ }
31183
+ const cliBin = process.argv[1] ?? "arc";
31184
+ const shellOk = await runBun([
31185
+ "run",
31186
+ cliBin,
31187
+ "_build-shell",
31188
+ "--out",
31189
+ opts.ws.shellDir,
31190
+ "--from",
31191
+ join19(opts.ws.arcDir, "node_modules")
31192
+ ], opts.ws.arcDir);
31193
+ if (!shellOk) {
31194
+ return Response.json({ error: "shell build failed" }, { status: 500, headers: cors });
31195
+ }
31196
+ const newHash = sha256Hex(Buffer.concat([
31197
+ readFileSync15(join19(opts.ws.arcDir, "package.json")),
31198
+ readFileSync15(join19(opts.ws.arcDir, "bun.lock"))
31199
+ ]));
31200
+ writeFileSync15(join19(opts.ws.arcDir, ".deps-hash"), newHash + `
31201
+ `);
31202
+ return Response.json({
31203
+ changed: true,
31204
+ tookMs: Date.now() - start,
31205
+ needsRestart: true
31206
+ }, { headers: cors });
31207
+ }
31208
+ const moduleMatch = p3.match(/^\/api\/deploy\/modules\/([^/]+)$/);
31209
+ if (moduleMatch && req.method === "POST") {
31210
+ const safeName = sanitizeName2(moduleMatch[1]);
31211
+ if (!safeName)
31212
+ return badRequest("invalid module name", cors);
31213
+ const form = await req.formData();
31214
+ const browser = form.get("browser.js");
31215
+ if (!isFile(browser)) {
31216
+ return badRequest("modules/<name> requires browser.js", cors);
31217
+ }
31218
+ const stagingDir = join19(opts.ws.arcDir, ".staging", "modules", safeName);
31219
+ mkdirSync14(stagingDir, { recursive: true });
31220
+ await writeField(stagingDir, "browser.js", browser);
31221
+ const server = form.get("server.js");
31222
+ if (isFile(server)) {
31223
+ await writeField(stagingDir, "server.js", server);
31224
+ }
31225
+ const pkg = form.get("package.json");
31226
+ if (isFile(pkg)) {
31227
+ await writeField(stagingDir, "package.json", pkg);
31228
+ }
31229
+ const access = form.get("access.json");
31230
+ if (isFile(access)) {
31231
+ await writeField(stagingDir, "access.json", access);
31232
+ }
31233
+ const stagingPkgPath = join19(stagingDir, "package.json");
31234
+ const livePkgPath = join19(opts.ws.arcDir, "modules", safeName, "package.json");
31235
+ const stagingPkgBytes = existsSync15(stagingPkgPath) ? readFileSync15(stagingPkgPath) : Buffer.from("");
31236
+ const livePkgBytes = existsSync15(livePkgPath) ? readFileSync15(livePkgPath) : Buffer.from("");
31237
+ const depsChanged = sha256Hex(stagingPkgBytes) !== sha256Hex(livePkgBytes);
31238
+ writeFileSync15(join19(stagingDir, ".deps-hash"), sha256Hex(stagingPkgBytes) + `
31239
+ `);
31240
+ if (depsChanged && existsSync15(stagingPkgPath)) {
31241
+ const ok2 = await runBun(["install", "--production"], stagingDir);
31242
+ if (!ok2) {
31243
+ rmSync4(stagingDir, { recursive: true, force: true });
31244
+ return Response.json({ error: `bun install failed for module ${safeName}` }, { status: 500, headers: cors });
31245
+ }
31246
+ } else if (!depsChanged) {
31247
+ const liveNodeModules = join19(opts.ws.arcDir, "modules", safeName, "node_modules");
31248
+ if (existsSync15(liveNodeModules)) {
31249
+ cpSync(liveNodeModules, join19(stagingDir, "node_modules"), {
31250
+ recursive: true
31251
+ });
31252
+ }
30814
31253
  }
30815
- writeFileSync12(join16(opts.ws.modulesDir, "manifest.json"), JSON.stringify(body, null, 2));
30816
- opts.setManifest(body);
30817
- opts.notifyReload(body);
30818
- return Response.json({ ok: true, moduleCount: body.modules.length }, { headers: ctx.corsHeaders });
31254
+ return Response.json({
31255
+ ok: true,
31256
+ name: safeName,
31257
+ depsChanged,
31258
+ serverIncluded: isFile(server)
31259
+ }, { headers: cors });
30819
31260
  }
30820
- if (p3 === "/api/deploy/modules" && req.method === "POST") {
31261
+ if (p3 === "/api/deploy/styles" && req.method === "POST") {
30821
31262
  const form = await req.formData();
30822
- const written = await writeUploadedFiles(form, opts.ws.modulesDir);
30823
- return Response.json({ ok: true, written }, { headers: ctx.corsHeaders });
31263
+ const stagingDir = join19(opts.ws.arcDir, ".staging");
31264
+ mkdirSync14(stagingDir, { recursive: true });
31265
+ const written = [];
31266
+ for (const name of ["styles.css", "theme.css"]) {
31267
+ const f2 = form.get(name);
31268
+ if (isFile(f2)) {
31269
+ await writeField(stagingDir, name, f2);
31270
+ written.push(name);
31271
+ }
31272
+ }
31273
+ return Response.json({ ok: true, written }, { headers: cors });
30824
31274
  }
30825
- if (p3 === "/api/deploy/shell" && req.method === "POST") {
30826
- const form = await req.formData();
30827
- const shellFiles = [];
30828
- const rootFiles = [];
30829
- for (const [name, value] of form.entries()) {
30830
- if (!isFile(value))
30831
- continue;
30832
- if (name === "styles.css" || name === "theme.css") {
30833
- rootFiles.push({ name, file: value });
30834
- } else {
30835
- shellFiles.push(value);
31275
+ if (p3 === "/api/deploy/manifest" && req.method === "POST") {
31276
+ const body = await req.json();
31277
+ if (!validateManifest(body)) {
31278
+ return badRequest("invalid manifest body", cors);
31279
+ }
31280
+ const stagingRoot = join19(opts.ws.arcDir, ".staging");
31281
+ const stagingModules = join19(stagingRoot, "modules");
31282
+ let serverSideChanged = false;
31283
+ if (existsSync15(stagingModules)) {
31284
+ const stagedNames = readDir(stagingModules);
31285
+ for (const name of stagedNames) {
31286
+ const src2 = join19(stagingModules, name);
31287
+ const dst = join19(opts.ws.arcDir, "modules", name);
31288
+ if (existsSync15(join19(src2, "server.js")))
31289
+ serverSideChanged = true;
31290
+ if (existsSync15(dst)) {
31291
+ rmSync4(dst, { recursive: true, force: true });
31292
+ }
31293
+ mkdirSync14(dirname7(dst), { recursive: true });
31294
+ cpSync(src2, dst, { recursive: true });
30836
31295
  }
30837
31296
  }
30838
- const writtenShell = await writeUploadedFileList(shellFiles, opts.ws.shellDir);
30839
- const writtenRoot = [];
30840
- for (const { name, file } of rootFiles) {
30841
- const target = join16(opts.ws.arcDir, name);
30842
- writeFileSync12(target, Buffer.from(await file.arrayBuffer()));
30843
- writtenRoot.push(name);
31297
+ for (const name of ["styles.css", "theme.css"]) {
31298
+ const src2 = join19(stagingRoot, name);
31299
+ if (existsSync15(src2)) {
31300
+ cpSync(src2, join19(opts.ws.arcDir, name));
31301
+ }
31302
+ }
31303
+ writeFileSync15(join19(opts.ws.modulesDir, "manifest.json"), JSON.stringify(body, null, 2));
31304
+ opts.setManifest(body);
31305
+ rmSync4(stagingRoot, { recursive: true, force: true });
31306
+ if (!serverSideChanged) {
31307
+ opts.notifyReload(body);
30844
31308
  }
30845
- return Response.json({ ok: true, written: [...writtenShell, ...writtenRoot] }, { headers: ctx.corsHeaders });
31309
+ return Response.json({
31310
+ ok: true,
31311
+ moduleCount: body.modules.length,
31312
+ needsRestart: serverSideChanged
31313
+ }, { headers: cors });
30846
31314
  }
30847
- return new Response("Not Found", {
30848
- status: 404,
30849
- headers: ctx.corsHeaders
30850
- });
31315
+ return new Response("Not Found", { status: 404, headers: cors });
30851
31316
  };
30852
31317
  }
30853
- async function writeUploadedFiles(form, targetDir) {
30854
- const files = [];
30855
- for (const [, value] of form.entries()) {
30856
- if (isFile(value))
30857
- files.push(value);
30858
- }
30859
- return writeUploadedFileList(files, targetDir);
31318
+ function badRequest(msg, cors) {
31319
+ return Response.json({ error: msg }, { status: 400, headers: cors });
30860
31320
  }
30861
31321
  function isFile(v3) {
30862
31322
  return typeof v3 === "object" && v3 !== null && typeof v3.arrayBuffer === "function" && typeof v3.name === "string";
30863
31323
  }
30864
- async function writeUploadedFileList(files, targetDir) {
30865
- const written = [];
31324
+ async function writeField(targetDir, name, file) {
31325
+ const safe = normalize2(name);
30866
31326
  const safeRoot = resolve(targetDir);
30867
- mkdirSync11(safeRoot, { recursive: true });
30868
- for (const file of files) {
30869
- const rel = normalize2(file.name);
30870
- const full = resolve(safeRoot, rel);
30871
- if (!full.startsWith(safeRoot + "/") && full !== safeRoot) {
30872
- throw new Error(`Path traversal rejected: ${file.name}`);
30873
- }
30874
- mkdirSync11(dirname7(full), { recursive: true });
30875
- writeFileSync12(full, Buffer.from(await file.arrayBuffer()));
30876
- written.push(relative5(safeRoot, full) || rel);
31327
+ const full = resolve(safeRoot, safe);
31328
+ if (!full.startsWith(safeRoot + "/") && full !== safeRoot) {
31329
+ throw new Error(`Path traversal rejected: ${name}`);
30877
31330
  }
30878
- return written;
31331
+ mkdirSync14(dirname7(full), { recursive: true });
31332
+ writeFileSync15(full, Buffer.from(await file.arrayBuffer()));
31333
+ }
31334
+ function sanitizeName2(name) {
31335
+ if (!/^[a-zA-Z0-9_-]+$/.test(name))
31336
+ return null;
31337
+ return name;
31338
+ }
31339
+ function readDir(dir) {
31340
+ if (!existsSync15(dir))
31341
+ return [];
31342
+ return __require("fs").readdirSync(dir);
31343
+ }
31344
+ async function runBun(args, cwd) {
31345
+ const proc2 = spawn4({
31346
+ cmd: ["bun", ...args],
31347
+ cwd,
31348
+ stdout: "inherit",
31349
+ stderr: "inherit"
31350
+ });
31351
+ const exit = await proc2.exited;
31352
+ return exit === 0;
30879
31353
  }
30880
31354
  function validateManifest(m4) {
30881
31355
  if (!m4 || typeof m4 !== "object")
@@ -30948,7 +31422,7 @@ function getMime(path4) {
30948
31422
  return MIME[ext2] ?? "application/octet-stream";
30949
31423
  }
30950
31424
  function serveFile(filePath, headers = {}) {
30951
- if (!existsSync14(filePath))
31425
+ if (!existsSync16(filePath))
30952
31426
  return new Response("Not Found", { status: 404 });
30953
31427
  return new Response(Bun.file(filePath), {
30954
31428
  headers: { "Content-Type": getMime(filePath), ...headers }
@@ -31037,7 +31511,7 @@ function staticFilesHandler(ws, devMode, moduleAccessMap) {
31037
31511
  return (_req, url, ctx) => {
31038
31512
  const path4 = url.pathname;
31039
31513
  if (path4.startsWith("/shell/"))
31040
- return serveFile(join17(ws.shellDir, path4.slice(7)), ctx.corsHeaders);
31514
+ return serveFile(join20(ws.shellDir, path4.slice(7)), ctx.corsHeaders);
31041
31515
  if (path4.startsWith("/modules/")) {
31042
31516
  const fileWithParams = path4.slice(9);
31043
31517
  const filename = fileWithParams.split("?")[0];
@@ -31049,25 +31523,25 @@ function staticFilesHandler(ws, devMode, moduleAccessMap) {
31049
31523
  return new Response("Forbidden", { status: 403, headers: ctx.corsHeaders });
31050
31524
  }
31051
31525
  }
31052
- return serveFile(join17(ws.modulesDir, filename), {
31526
+ return serveFile(join20(ws.modulesDir, filename), {
31053
31527
  ...ctx.corsHeaders,
31054
31528
  "Cache-Control": devMode ? "no-cache" : "max-age=31536000,immutable"
31055
31529
  });
31056
31530
  }
31057
31531
  if (path4.startsWith("/locales/"))
31058
- return serveFile(join17(ws.arcDir, path4.slice(1)), ctx.corsHeaders);
31532
+ return serveFile(join20(ws.arcDir, path4.slice(1)), ctx.corsHeaders);
31059
31533
  if (path4.startsWith("/assets/"))
31060
- return serveFile(join17(ws.assetsDir, path4.slice(8)), ctx.corsHeaders);
31534
+ return serveFile(join20(ws.assetsDir, path4.slice(8)), ctx.corsHeaders);
31061
31535
  if (path4 === "/styles.css")
31062
- return serveFile(join17(ws.arcDir, "styles.css"), ctx.corsHeaders);
31536
+ return serveFile(join20(ws.arcDir, "styles.css"), ctx.corsHeaders);
31063
31537
  if (path4 === "/theme.css")
31064
- return serveFile(join17(ws.arcDir, "theme.css"), ctx.corsHeaders);
31538
+ return serveFile(join20(ws.arcDir, "theme.css"), ctx.corsHeaders);
31065
31539
  if ((path4 === "/manifest.json" || path4 === "/manifest.webmanifest") && ws.manifest) {
31066
31540
  return serveFile(ws.manifest.path, ctx.corsHeaders);
31067
31541
  }
31068
31542
  if (path4.lastIndexOf(".") > path4.lastIndexOf("/")) {
31069
- const publicFile = join17(ws.publicDir, path4.slice(1));
31070
- if (existsSync14(publicFile))
31543
+ const publicFile = join20(ws.publicDir, path4.slice(1));
31544
+ if (existsSync16(publicFile))
31071
31545
  return serveFile(publicFile, ctx.corsHeaders);
31072
31546
  }
31073
31547
  return null;
@@ -31205,10 +31679,10 @@ async function startPlatformServer(opts) {
31205
31679
  };
31206
31680
  }
31207
31681
  const { createBunSQLiteAdapterFactory: createBunSQLiteAdapterFactory2 } = await Promise.resolve().then(() => (init_dist(), exports_dist2));
31208
- const dbPath = opts.dbPath || join17(ws.arcDir, "data", "arc.db");
31682
+ const dbPath = opts.dbPath || join20(ws.arcDir, "data", "arc.db");
31209
31683
  const dbDir = dbPath.substring(0, dbPath.lastIndexOf("/"));
31210
31684
  if (dbDir)
31211
- mkdirSync12(dbDir, { recursive: true });
31685
+ mkdirSync15(dbDir, { recursive: true });
31212
31686
  const arcServer = await createArcServer({
31213
31687
  context,
31214
31688
  dbAdapterFactory: createBunSQLiteAdapterFactory2(dbPath),
@@ -31251,7 +31725,7 @@ async function platformDev(opts = {}) {
31251
31725
  manifest,
31252
31726
  context,
31253
31727
  moduleAccess,
31254
- dbPath: join18(ws.rootDir, ".arc", "data", "dev.db"),
31728
+ dbPath: join21(ws.rootDir, ".arc", "data", "dev.db"),
31255
31729
  devMode: true,
31256
31730
  arcEntries
31257
31731
  });
@@ -31282,8 +31756,8 @@ async function platformDev(opts = {}) {
31282
31756
  }, 300);
31283
31757
  };
31284
31758
  for (const pkg of ws.packages) {
31285
- const srcDir = join18(pkg.path, "src");
31286
- if (!existsSync15(srcDir))
31759
+ const srcDir = join21(pkg.path, "src");
31760
+ if (!existsSync17(srcDir))
31287
31761
  continue;
31288
31762
  watch(srcDir, { recursive: true }, (_event, filename) => {
31289
31763
  if (!filename || filename.includes(".arc") || filename.endsWith(".d.ts") || filename.includes("node_modules") || filename.includes("dist"))
@@ -31293,8 +31767,8 @@ async function platformDev(opts = {}) {
31293
31767
  triggerRebuild();
31294
31768
  });
31295
31769
  }
31296
- const localesDir = join18(ws.rootDir, "locales");
31297
- if (existsSync15(localesDir)) {
31770
+ const localesDir = join21(ws.rootDir, "locales");
31771
+ if (existsSync17(localesDir)) {
31298
31772
  watch(localesDir, { recursive: false }, (_event, filename) => {
31299
31773
  if (!filename?.endsWith(".po"))
31300
31774
  return;
@@ -31310,17 +31784,41 @@ async function platformDev(opts = {}) {
31310
31784
  }
31311
31785
 
31312
31786
  // src/commands/platform-start.ts
31313
- import { existsSync as existsSync16, readFileSync as readFileSync13 } from "fs";
31314
- import { join as join19 } from "path";
31787
+ import { existsSync as existsSync18, readFileSync as readFileSync16 } from "fs";
31788
+ import { join as join22 } from "path";
31315
31789
  async function platformStart() {
31316
31790
  const ws = resolveWorkspace();
31317
31791
  const port = parseInt(process.env.PORT || "5005", 10);
31318
- const manifestPath = join19(ws.modulesDir, "manifest.json");
31319
- if (!existsSync16(manifestPath)) {
31320
- err("No build found. Run `arc platform build` first.");
31321
- process.exit(1);
31792
+ const deployApi = process.env.ARC_DEPLOY_API === "1";
31793
+ const manifestPath = join22(ws.modulesDir, "manifest.json");
31794
+ if (!existsSync18(manifestPath)) {
31795
+ if (!deployApi) {
31796
+ err("No build found. Run `arc platform build` first.");
31797
+ process.exit(1);
31798
+ }
31799
+ log2("Pre-deploy mode \u2014 no manifest yet, awaiting first /api/deploy/*");
31800
+ const emptyManifest = {
31801
+ modules: [],
31802
+ shellHash: "",
31803
+ stylesHash: "",
31804
+ buildTime: new Date().toISOString()
31805
+ };
31806
+ const platform4 = await startPlatformServer({
31807
+ ws,
31808
+ port,
31809
+ manifest: emptyManifest,
31810
+ context: null,
31811
+ moduleAccess: new Map,
31812
+ dbPath: join22(ws.rootDir, ".arc", "data", "prod.db"),
31813
+ devMode: false,
31814
+ deployApi: true,
31815
+ arcEntries: []
31816
+ });
31817
+ ok(`Pre-deploy server on http://localhost:${port}`);
31818
+ registerSignalCleanup(platform4);
31819
+ return;
31322
31820
  }
31323
- const manifest = JSON.parse(readFileSync13(manifestPath, "utf-8"));
31821
+ const manifest = JSON.parse(readFileSync16(manifestPath, "utf-8"));
31324
31822
  log2("Loading server context...");
31325
31823
  const { context, moduleAccess } = await loadServerContext(ws.packages);
31326
31824
  if (context) {
@@ -31329,7 +31827,6 @@ async function platformStart() {
31329
31827
  log2("No context \u2014 server endpoints skipped");
31330
31828
  }
31331
31829
  const arcEntries = collectArcPeerDeps(ws.packages);
31332
- const deployApi = process.env.ARC_DEPLOY_API === "1";
31333
31830
  if (deployApi)
31334
31831
  ok("Deploy API enabled (/api/deploy/*)");
31335
31832
  const platform3 = await startPlatformServer({
@@ -31338,7 +31835,7 @@ async function platformStart() {
31338
31835
  manifest,
31339
31836
  context,
31340
31837
  moduleAccess,
31341
- dbPath: join19(ws.rootDir, ".arc", "data", "prod.db"),
31838
+ dbPath: join22(ws.rootDir, ".arc", "data", "prod.db"),
31342
31839
  devMode: false,
31343
31840
  deployApi,
31344
31841
  arcEntries
@@ -31346,6 +31843,9 @@ async function platformStart() {
31346
31843
  ok(`Server on http://localhost:${port}`);
31347
31844
  if (platform3.contextHandler)
31348
31845
  ok("Commands, queries, WebSocket \u2014 all on same port");
31846
+ registerSignalCleanup(platform3);
31847
+ }
31848
+ function registerSignalCleanup(platform3) {
31349
31849
  const cleanup = () => {
31350
31850
  platform3.stop();
31351
31851
  process.exit(0);
@@ -31364,6 +31864,7 @@ platform3.command("dev").description("Start platform in dev mode (Bun server + V
31364
31864
  platform3.command("build").description("Build platform for production").option("--no-cache", "Force full rebuild").action((opts) => platformBuild({ noCache: opts.cache === false }));
31365
31865
  platform3.command("start").description("Start platform in production mode (requires prior build)").action(platformStart);
31366
31866
  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));
31867
+ program2.command("_build-shell", { hidden: true }).description("Build framework shell bundles from a node_modules dir").requiredOption("--out <dir>", "Output directory for shell .js bundles").requiredOption("--from <dir>", "node_modules directory to discover packages in").action((opts) => buildShell(opts));
31367
31868
  program2.parse(process.argv);
31368
31869
  if (process.argv.length === 2) {
31369
31870
  program2.help();