@arcote.tech/arc-cli 0.5.8 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,22 +27446,22 @@ 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(", ")}`);
27177
- if (packages.length === 0) {
27455
+ if (packages.length === 0 && process.env.ARC_DEPLOY_API !== "1") {
27178
27456
  err("No workspace packages found.");
27179
27457
  process.exit(1);
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 { dirname as dirname7, 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,24 +28503,26 @@ 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 "";
28208
28510
  return new Response(stream2).text();
28209
28511
  }
28210
28512
  function baseSshArgs(target) {
28211
- const args = [
28513
+ const key = target.sshKey ?? `${process.env.HOME}/.ssh/id_ed25519`;
28514
+ return [
28212
28515
  "-o",
28213
28516
  "BatchMode=yes",
28214
28517
  "-o",
28215
28518
  "StrictHostKeyChecking=accept-new",
28519
+ "-o",
28520
+ "IdentitiesOnly=yes",
28521
+ "-i",
28522
+ key,
28216
28523
  "-p",
28217
28524
  String(target.port)
28218
28525
  ];
28219
- if (target.sshKey)
28220
- args.push("-i", target.sshKey);
28221
- return args;
28222
28526
  }
28223
28527
  async function sshExec(target, cmd, opts = {}) {
28224
28528
  const args = [
@@ -28227,7 +28531,7 @@ async function sshExec(target, cmd, opts = {}) {
28227
28531
  "--",
28228
28532
  cmd
28229
28533
  ];
28230
- const proc2 = spawn4({
28534
+ const proc2 = spawn3({
28231
28535
  cmd: ["ssh", ...args],
28232
28536
  stdin: opts.stdin ? "pipe" : "ignore",
28233
28537
  stdout: "pipe",
@@ -28272,18 +28576,21 @@ async function waitForSsh(target, opts = {}) {
28272
28576
  throw new Error(`Timed out waiting for SSH on ${target.user}@${target.host}`);
28273
28577
  }
28274
28578
  async function scpUpload(target, localPath, remotePath) {
28579
+ const key = target.sshKey ?? `${process.env.HOME}/.ssh/id_ed25519`;
28275
28580
  const args = [
28276
28581
  "-o",
28277
28582
  "BatchMode=yes",
28278
28583
  "-o",
28279
28584
  "StrictHostKeyChecking=accept-new",
28585
+ "-o",
28586
+ "IdentitiesOnly=yes",
28587
+ "-i",
28588
+ key,
28280
28589
  "-P",
28281
28590
  String(target.port)
28282
28591
  ];
28283
- if (target.sshKey)
28284
- args.push("-i", target.sshKey);
28285
28592
  args.push(localPath, `${target.user}@${target.host}:${remotePath}`);
28286
- const proc2 = spawn4({ cmd: ["scp", ...args], stderr: "pipe" });
28593
+ const proc2 = spawn3({ cmd: ["scp", ...args], stderr: "pipe" });
28287
28594
  const [stderr, exitCode] = await Promise.all([
28288
28595
  streamToString(proc2.stderr),
28289
28596
  proc2.exited
@@ -28292,29 +28599,6 @@ async function scpUpload(target, localPath, remotePath) {
28292
28599
  throw new Error(`scp failed (${exitCode}): ${stderr}`);
28293
28600
  }
28294
28601
  }
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
28602
  async function openTunnel(target, localPort, remoteHost, remotePort) {
28319
28603
  const args = [
28320
28604
  ...baseSshArgs(target),
@@ -28323,7 +28607,7 @@ async function openTunnel(target, localPort, remoteHost, remotePort) {
28323
28607
  `${localPort}:${remoteHost}:${remotePort}`,
28324
28608
  `${target.user}@${target.host}`
28325
28609
  ];
28326
- const proc2 = spawn4({
28610
+ const proc2 = spawn3({
28327
28611
  cmd: ["ssh", ...args],
28328
28612
  stdin: "ignore",
28329
28613
  stdout: "pipe",
@@ -28369,6 +28653,9 @@ async function detectRemoteState(cfg) {
28369
28653
  return { kind: "unreachable", reason: "target.host not yet set" };
28370
28654
  }
28371
28655
  if (!await canSsh(cfg.target)) {
28656
+ if (await canSsh({ ...cfg.target, user: "root" })) {
28657
+ return { kind: "no-docker" };
28658
+ }
28372
28659
  return { kind: "unreachable", reason: "ssh connection failed" };
28373
28660
  }
28374
28661
  const dockerCheck = await sshExec(cfg.target, "command -v docker", {
@@ -28419,7 +28706,8 @@ async function bootstrap(inputs) {
28419
28706
  const tfOut = await runTerraform({
28420
28707
  tf: cfg.provision.terraform,
28421
28708
  token,
28422
- serverName: `arc-${Object.keys(cfg.envs)[0] ?? "host"}`
28709
+ serverName: `arc-${Object.keys(cfg.envs)[0] ?? "host"}`,
28710
+ workspaceDir: rootDir
28423
28711
  });
28424
28712
  ok(`Server provisioned: ${tfOut.serverIp}`);
28425
28713
  cfg.target.host = tfOut.serverIp;
@@ -28430,7 +28718,8 @@ async function bootstrap(inputs) {
28430
28718
  }
28431
28719
  if (state.kind === "unreachable" || state.kind === "no-docker") {
28432
28720
  log2("Running Ansible bootstrap (Docker + firewall + SSH hardening)...");
28433
- const asRoot = state.kind === "unreachable";
28721
+ const deployUserWorks = state.kind === "no-docker" && await canSsh(cfg.target);
28722
+ const asRoot = !deployUserWorks;
28434
28723
  await runAnsible({
28435
28724
  target: cfg.target,
28436
28725
  ansible: cfg.provision?.ansible,
@@ -28450,125 +28739,185 @@ async function bootstrap(inputs) {
28450
28739
  }
28451
28740
  async function upStack(inputs) {
28452
28741
  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 }));
28742
+ const workDir = join16(tmpdir3(), "arc-deploy", `stack-${Date.now()}`);
28743
+ mkdirSync13(workDir, { recursive: true });
28744
+ writeFileSync14(join16(workDir, "Caddyfile"), generateCaddyfile(cfg));
28745
+ writeFileSync14(join16(workDir, "docker-compose.yml"), generateCompose({ cfg, cliVersion: inputs.cliVersion }));
28457
28746
  await assertExec(cfg.target, `sudo mkdir -p ${cfg.target.remoteDir} && sudo chown ${cfg.target.user}:${cfg.target.user} ${cfg.target.remoteDir}`);
28458
28747
  for (const name of Object.keys(cfg.envs)) {
28459
28748
  await assertExec(cfg.target, `mkdir -p ${cfg.target.remoteDir}/${name}`);
28460
28749
  }
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`);
28750
+ await scpUpload(cfg.target, join16(workDir, "Caddyfile"), `${cfg.target.remoteDir}/Caddyfile`);
28751
+ await scpUpload(cfg.target, join16(workDir, "docker-compose.yml"), `${cfg.target.remoteDir}/docker-compose.yml`);
28463
28752
  await assertExec(cfg.target, `cd ${cfg.target.remoteDir} && docker compose pull --ignore-pull-failures && docker compose up -d`);
28464
28753
  }
28465
28754
 
28466
28755
  // 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";
28756
+ import { existsSync as existsSync13, readFileSync as readFileSync13 } from "fs";
28757
+ import { basename as basename5, join as join17 } from "path";
28469
28758
  function diffManifests(local, remote) {
28470
28759
  const remoteByName = new Map(remote.modules.map((m) => [m.name, m]));
28471
28760
  const changedModules = local.modules.filter((m) => remoteByName.get(m.name)?.hash !== m.hash);
28472
28761
  return {
28473
28762
  changedModules: [...changedModules],
28474
- shellChanged: local.shellHash !== remote.shellHash,
28475
28763
  stylesChanged: local.stylesHash !== remote.stylesHash
28476
28764
  };
28477
28765
  }
28478
28766
  async function syncEnv(inputs) {
28479
- const { cfg, env: env2, ws, projectDir } = inputs;
28767
+ const { cfg, env: env2, ws } = inputs;
28480
28768
  const envConfig = cfg.envs[env2];
28481
28769
  if (!envConfig)
28482
28770
  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)) {
28771
+ const localManifestPath = join17(ws.modulesDir, "manifest.json");
28772
+ if (!existsSync13(localManifestPath)) {
28487
28773
  throw new Error(`Local build missing at ${localManifestPath}. Run arc platform build first.`);
28488
28774
  }
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);
28775
+ const localManifest = JSON.parse(readFileSync13(localManifestPath, "utf-8"));
28776
+ const pkgByName = new Map(ws.packages.map((p) => [p.name.includes("/") ? p.name.split("/").pop() : p.name, p]));
28777
+ let tunnel = await openTunnel(cfg.target, 15500 + hashEnvToOffset(env2), "127.0.0.1", 2019);
28778
+ let restarts = 0;
28779
+ let frameworkChanged = false;
28492
28780
  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);
28781
+ const base2 = () => `http://127.0.0.1:${tunnel.localPort}/env/${env2}`;
28782
+ const localFrameworkHash = readDepsHash(join17(ws.arcDir, ".deps-hash"));
28783
+ const remoteFwRes = await fetch(`${base2()}/api/deploy/framework`);
28784
+ const remoteFw = remoteFwRes.ok ? await remoteFwRes.json() : { depsHash: null };
28785
+ if (localFrameworkHash && localFrameworkHash !== remoteFw.depsHash) {
28786
+ console.log("[arc] Pushing framework deps...");
28502
28787
  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);
28788
+ form.append("package.json", new Blob([readFileSync13(join17(ws.arcDir, "package.json"))]), "package.json");
28789
+ const lockPath = join17(ws.arcDir, "bun.lock");
28790
+ if (existsSync13(lockPath)) {
28791
+ form.append("bun.lock", new Blob([readFileSync13(lockPath)]), "bun.lock");
28506
28792
  }
28507
- const res2 = await fetch(`${base2}/api/deploy/shell`, {
28793
+ const res = await fetch(`${base2()}/api/deploy/framework`, {
28508
28794
  method: "POST",
28509
28795
  body: form
28510
28796
  });
28511
- if (!res2.ok)
28512
- throw new Error(`Shell upload failed: ${res2.status} ${await res2.text()}`);
28513
- }
28514
- if (diff.stylesChanged) {
28797
+ if (!res.ok) {
28798
+ throw new Error(`framework push failed: ${res.status} ${await res.text()}`);
28799
+ }
28800
+ frameworkChanged = true;
28801
+ const result = await res.json();
28802
+ if (result.needsRestart) {
28803
+ tunnel = await restartAndReopen(cfg, env2, tunnel);
28804
+ restarts += 1;
28805
+ }
28806
+ }
28807
+ const remoteManifestRes = await fetch(`${base2()}/api/deploy/manifest`);
28808
+ const remoteManifest = remoteManifestRes.ok ? await remoteManifestRes.json() : {
28809
+ modules: [],
28810
+ shellHash: "",
28811
+ stylesHash: "",
28812
+ buildTime: ""
28813
+ };
28814
+ const diff = diffManifests(localManifest, remoteManifest);
28815
+ for (const mod of diff.changedModules) {
28816
+ const safeName = sanitizeName(mod.name);
28817
+ const moduleDir = join17(ws.modulesDir, safeName);
28818
+ const browserPath = join17(moduleDir, "browser.js");
28819
+ const serverPath = join17(moduleDir, "server.js");
28820
+ const pkgPath = join17(moduleDir, "package.json");
28821
+ const accessPath = join17(moduleDir, "access.json");
28822
+ const browserActual = existsSync13(browserPath) ? browserPath : join17(ws.modulesDir, `${safeName}.js`);
28823
+ if (!existsSync13(browserActual)) {
28824
+ throw new Error(`Missing browser bundle for module ${mod.name}`);
28825
+ }
28515
28826
  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
- }
28827
+ form.append("browser.js", new Blob([readFileSync13(browserActual)]), "browser.js");
28828
+ const pkg = pkgByName.get(safeName);
28829
+ if (pkg && isContextPackage(pkg.packageJson) && existsSync13(serverPath)) {
28830
+ form.append("server.js", new Blob([readFileSync13(serverPath)]), "server.js");
28831
+ }
28832
+ if (existsSync13(pkgPath)) {
28833
+ form.append("package.json", new Blob([readFileSync13(pkgPath)]), "package.json");
28834
+ }
28835
+ if (existsSync13(accessPath)) {
28836
+ form.append("access.json", new Blob([readFileSync13(accessPath)]), "access.json");
28521
28837
  }
28522
- const res2 = await fetch(`${base2}/api/deploy/shell`, {
28838
+ console.log(`[arc] Pushing module ${safeName}...`);
28839
+ const res = await fetch(`${base2()}/api/deploy/modules/${safeName}`, {
28523
28840
  method: "POST",
28524
28841
  body: form
28525
28842
  });
28526
- if (!res2.ok)
28527
- throw new Error(`Styles upload failed: ${res2.status} ${await res2.text()}`);
28843
+ if (!res.ok) {
28844
+ throw new Error(`module ${safeName} push failed: ${res.status} ${await res.text()}`);
28845
+ }
28528
28846
  }
28529
- if (diff.changedModules.length > 0) {
28847
+ if (diff.stylesChanged) {
28848
+ console.log("[arc] Pushing styles...");
28530
28849
  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);
28850
+ for (const name of ["styles.css", "theme.css"]) {
28851
+ const p = join17(ws.arcDir, name);
28852
+ if (existsSync13(p)) {
28853
+ form.append(name, new Blob([readFileSync13(p)]), name);
28854
+ }
28534
28855
  }
28535
- const res2 = await fetch(`${base2}/api/deploy/modules`, {
28856
+ const res = await fetch(`${base2()}/api/deploy/styles`, {
28536
28857
  method: "POST",
28537
28858
  body: form
28538
28859
  });
28539
- if (!res2.ok)
28540
- throw new Error(`Modules upload failed: ${res2.status} ${await res2.text()}`);
28860
+ if (!res.ok) {
28861
+ throw new Error(`styles push failed: ${res.status} ${await res.text()}`);
28862
+ }
28541
28863
  }
28542
- const res = await fetch(`${base2}/api/deploy/manifest`, {
28864
+ const commitRes = await fetch(`${base2()}/api/deploy/manifest`, {
28543
28865
  method: "POST",
28544
28866
  headers: { "Content-Type": "application/json" },
28545
28867
  body: JSON.stringify(localManifest)
28546
28868
  });
28547
- if (!res.ok)
28548
- throw new Error(`Manifest update failed: ${res.status} ${await res.text()}`);
28869
+ if (!commitRes.ok) {
28870
+ throw new Error(`manifest commit failed: ${commitRes.status} ${await commitRes.text()}`);
28871
+ }
28872
+ const commit = await commitRes.json();
28873
+ if (commit.needsRestart) {
28874
+ tunnel = await restartAndReopen(cfg, env2, tunnel);
28875
+ restarts += 1;
28876
+ }
28549
28877
  return {
28550
28878
  env: env2,
28879
+ frameworkChanged,
28551
28880
  changedModules: diff.changedModules.map((m) => m.name),
28552
- shellChanged: diff.shellChanged,
28553
- stylesChanged: diff.stylesChanged
28881
+ stylesChanged: diff.stylesChanged,
28882
+ restarts
28554
28883
  };
28555
28884
  } finally {
28556
28885
  tunnel.close();
28557
28886
  }
28558
28887
  }
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);
28888
+ function readDepsHash(path4) {
28889
+ if (!existsSync13(path4))
28890
+ return null;
28891
+ return readFileSync13(path4, "utf-8").trim() || null;
28892
+ }
28893
+ function sanitizeName(name) {
28894
+ return basename5(name);
28895
+ }
28896
+ async function restartAndReopen(cfg, env2, oldTunnel) {
28897
+ console.log(`[arc] Restarting arc-${env2}...`);
28898
+ oldTunnel.close();
28899
+ await assertExec(cfg.target, `docker restart arc-${env2}`);
28900
+ const tunnel = await openTunnel(cfg.target, 15500 + hashEnvToOffset(env2), "127.0.0.1", 2019);
28901
+ await waitForHealthy(`http://127.0.0.1:${tunnel.localPort}/env/${env2}`, 60000);
28902
+ return tunnel;
28903
+ }
28904
+ async function waitForHealthy(baseUrl, timeoutMs) {
28905
+ const deadline = Date.now() + timeoutMs;
28906
+ let lastErr;
28907
+ while (Date.now() < deadline) {
28908
+ try {
28909
+ const res = await fetch(`${baseUrl}/api/deploy/health`, {
28910
+ signal: AbortSignal.timeout(2000)
28911
+ });
28912
+ if (res.ok)
28913
+ return;
28914
+ lastErr = `status ${res.status}`;
28915
+ } catch (e) {
28916
+ lastErr = e;
28917
+ }
28918
+ await new Promise((r) => setTimeout(r, 1000));
28570
28919
  }
28571
- return out;
28920
+ throw new Error(`Health check timeout: ${String(lastErr)}`);
28572
28921
  }
28573
28922
  function hashEnvToOffset(env2) {
28574
28923
  let h = 0;
@@ -29274,13 +29623,13 @@ async function platformDeploy(envArg, options = {}) {
29274
29623
  err(`Unknown env "${envArg}". Known: ${Object.keys(cfg.envs).join(", ")}`);
29275
29624
  process.exit(1);
29276
29625
  })() : Object.keys(cfg.envs);
29277
- const manifestPath = join15(ws.modulesDir, "manifest.json");
29278
- const needBuild = options.rebuild || !existsSync12(manifestPath);
29626
+ const manifestPath = join18(ws.modulesDir, "manifest.json");
29627
+ const needBuild = options.rebuild || !existsSync14(manifestPath);
29279
29628
  if (needBuild && !options.skipBuild) {
29280
29629
  log2("Building platform...");
29281
29630
  await buildAll(ws, { noCache: options.rebuild });
29282
29631
  ok("Build complete");
29283
- } else if (!existsSync12(manifestPath)) {
29632
+ } else if (!existsSync14(manifestPath)) {
29284
29633
  err("No build found and --skip-build was set.");
29285
29634
  process.exit(1);
29286
29635
  }
@@ -29322,25 +29671,35 @@ async function platformDeploy(envArg, options = {}) {
29322
29671
  }
29323
29672
  }
29324
29673
  function readCliVersion() {
29674
+ const candidates = [];
29675
+ const entry = process.argv[1];
29676
+ if (entry) {
29677
+ candidates.push(join18(dirname7(entry), "..", "package.json"));
29678
+ }
29325
29679
  try {
29326
- const pkgPath = join15(import.meta.dir, "..", "..", "package.json");
29327
- const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
29328
- return pkg.version ?? "unknown";
29329
- } catch {
29330
- return "unknown";
29680
+ candidates.push(join18(import.meta.dir, "..", "..", "package.json"));
29681
+ } catch {}
29682
+ for (const path4 of candidates) {
29683
+ try {
29684
+ const pkg = JSON.parse(readFileSync14(path4, "utf-8"));
29685
+ if (pkg.name === "@arcote.tech/arc-cli" && pkg.version) {
29686
+ return pkg.version;
29687
+ }
29688
+ } catch {}
29331
29689
  }
29690
+ return "unknown";
29332
29691
  }
29333
29692
  async function hashDeployConfig(rootDir) {
29334
- const p2 = join15(rootDir, "deploy.arc.json");
29335
- const content = readFileSync11(p2);
29693
+ const p2 = join18(rootDir, "deploy.arc.json");
29694
+ const content = readFileSync14(p2);
29336
29695
  const hasher = new Bun.CryptoHasher("sha256");
29337
29696
  hasher.update(content);
29338
29697
  return hasher.digest("hex").slice(0, 16);
29339
29698
  }
29340
29699
 
29341
29700
  // src/commands/platform-dev.ts
29342
- import { existsSync as existsSync15, watch } from "fs";
29343
- import { join as join18 } from "path";
29701
+ import { existsSync as existsSync17, watch } from "fs";
29702
+ import { join as join21 } from "path";
29344
29703
 
29345
29704
  // ../host/src/create-server.ts
29346
29705
  var import_jsonwebtoken = __toESM(require_jsonwebtoken(), 1);
@@ -30790,92 +31149,222 @@ async function createArcServer(config) {
30790
31149
  };
30791
31150
  }
30792
31151
  // src/platform/server.ts
30793
- import { existsSync as existsSync14, mkdirSync as mkdirSync12 } from "fs";
30794
- import { join as join17 } from "path";
31152
+ import { existsSync as existsSync16, mkdirSync as mkdirSync15 } from "fs";
31153
+ import { join as join20 } from "path";
30795
31154
 
30796
31155
  // 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";
31156
+ var {spawn: spawn4 } = globalThis.Bun;
31157
+ import {
31158
+ cpSync,
31159
+ existsSync as existsSync15,
31160
+ mkdirSync as mkdirSync14,
31161
+ readFileSync as readFileSync15,
31162
+ rmSync as rmSync4,
31163
+ writeFileSync as writeFileSync15
31164
+ } from "fs";
31165
+ import { dirname as dirname8, join as join19, normalize as normalize2, resolve } from "path";
30799
31166
  function createDeployApiHandler(opts) {
30800
31167
  return async (req, url, ctx) => {
30801
31168
  const p3 = url.pathname;
30802
31169
  if (!p3.startsWith("/api/deploy/"))
30803
31170
  return null;
31171
+ const cors = ctx.corsHeaders;
30804
31172
  if (p3 === "/api/deploy/health" && req.method === "GET") {
30805
- return Response.json({ ok: true, modules: opts.getManifest().modules.length }, { headers: ctx.corsHeaders });
31173
+ return Response.json({ ok: true, modules: opts.getManifest().modules.length }, { headers: cors });
31174
+ }
31175
+ if (p3 === "/api/deploy/framework" && req.method === "GET") {
31176
+ const hashPath = join19(opts.ws.arcDir, ".deps-hash");
31177
+ const depsHash = existsSync15(hashPath) ? readFileSync15(hashPath, "utf-8").trim() : null;
31178
+ return Response.json({ depsHash }, { headers: cors });
30806
31179
  }
30807
31180
  if (p3 === "/api/deploy/manifest" && req.method === "GET") {
30808
- return Response.json(opts.getManifest(), { headers: ctx.corsHeaders });
31181
+ return Response.json(opts.getManifest(), { headers: cors });
30809
31182
  }
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 });
31183
+ if (p3 === "/api/deploy/framework" && req.method === "POST") {
31184
+ const form = await req.formData();
31185
+ const pkgFile = form.get("package.json");
31186
+ const lockFile = form.get("bun.lock");
31187
+ if (!isFile(pkgFile) || !isFile(lockFile)) {
31188
+ return badRequest("framework requires package.json + bun.lock", cors);
31189
+ }
31190
+ mkdirSync14(opts.ws.arcDir, { recursive: true });
31191
+ writeFileSync15(join19(opts.ws.arcDir, "package.json"), Buffer.from(await pkgFile.arrayBuffer()));
31192
+ writeFileSync15(join19(opts.ws.arcDir, "bun.lock"), Buffer.from(await lockFile.arrayBuffer()));
31193
+ const start = Date.now();
31194
+ const installOk = await runBun(["install", "--production", "--frozen-lockfile"], opts.ws.arcDir);
31195
+ if (!installOk) {
31196
+ return Response.json({ error: "bun install failed" }, { status: 500, headers: cors });
31197
+ }
31198
+ const cliBin = process.argv[1] ?? "arc";
31199
+ const shellOk = await runBun([
31200
+ "run",
31201
+ cliBin,
31202
+ "_build-shell",
31203
+ "--out",
31204
+ opts.ws.shellDir,
31205
+ "--from",
31206
+ join19(opts.ws.arcDir, "node_modules")
31207
+ ], opts.ws.arcDir);
31208
+ if (!shellOk) {
31209
+ return Response.json({ error: "shell build failed" }, { status: 500, headers: cors });
31210
+ }
31211
+ const newHash = sha256Hex(Buffer.concat([
31212
+ readFileSync15(join19(opts.ws.arcDir, "package.json")),
31213
+ readFileSync15(join19(opts.ws.arcDir, "bun.lock"))
31214
+ ]));
31215
+ writeFileSync15(join19(opts.ws.arcDir, ".deps-hash"), newHash + `
31216
+ `);
31217
+ return Response.json({
31218
+ changed: true,
31219
+ tookMs: Date.now() - start,
31220
+ needsRestart: true
31221
+ }, { headers: cors });
31222
+ }
31223
+ const moduleMatch = p3.match(/^\/api\/deploy\/modules\/([^/]+)$/);
31224
+ if (moduleMatch && req.method === "POST") {
31225
+ const safeName = sanitizeName2(moduleMatch[1]);
31226
+ if (!safeName)
31227
+ return badRequest("invalid module name", cors);
31228
+ const form = await req.formData();
31229
+ const browser = form.get("browser.js");
31230
+ if (!isFile(browser)) {
31231
+ return badRequest("modules/<name> requires browser.js", cors);
31232
+ }
31233
+ const stagingDir = join19(opts.ws.arcDir, ".staging", "modules", safeName);
31234
+ mkdirSync14(stagingDir, { recursive: true });
31235
+ await writeField(stagingDir, "browser.js", browser);
31236
+ const server = form.get("server.js");
31237
+ if (isFile(server)) {
31238
+ await writeField(stagingDir, "server.js", server);
31239
+ }
31240
+ const pkg = form.get("package.json");
31241
+ if (isFile(pkg)) {
31242
+ await writeField(stagingDir, "package.json", pkg);
31243
+ }
31244
+ const access = form.get("access.json");
31245
+ if (isFile(access)) {
31246
+ await writeField(stagingDir, "access.json", access);
31247
+ }
31248
+ const stagingPkgPath = join19(stagingDir, "package.json");
31249
+ const livePkgPath = join19(opts.ws.arcDir, "modules", safeName, "package.json");
31250
+ const stagingPkgBytes = existsSync15(stagingPkgPath) ? readFileSync15(stagingPkgPath) : Buffer.from("");
31251
+ const livePkgBytes = existsSync15(livePkgPath) ? readFileSync15(livePkgPath) : Buffer.from("");
31252
+ const depsChanged = sha256Hex(stagingPkgBytes) !== sha256Hex(livePkgBytes);
31253
+ writeFileSync15(join19(stagingDir, ".deps-hash"), sha256Hex(stagingPkgBytes) + `
31254
+ `);
31255
+ if (depsChanged && existsSync15(stagingPkgPath)) {
31256
+ const ok2 = await runBun(["install", "--production"], stagingDir);
31257
+ if (!ok2) {
31258
+ rmSync4(stagingDir, { recursive: true, force: true });
31259
+ return Response.json({ error: `bun install failed for module ${safeName}` }, { status: 500, headers: cors });
31260
+ }
31261
+ } else if (!depsChanged) {
31262
+ const liveNodeModules = join19(opts.ws.arcDir, "modules", safeName, "node_modules");
31263
+ if (existsSync15(liveNodeModules)) {
31264
+ cpSync(liveNodeModules, join19(stagingDir, "node_modules"), {
31265
+ recursive: true
31266
+ });
31267
+ }
30814
31268
  }
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 });
31269
+ return Response.json({
31270
+ ok: true,
31271
+ name: safeName,
31272
+ depsChanged,
31273
+ serverIncluded: isFile(server)
31274
+ }, { headers: cors });
30819
31275
  }
30820
- if (p3 === "/api/deploy/modules" && req.method === "POST") {
31276
+ if (p3 === "/api/deploy/styles" && req.method === "POST") {
30821
31277
  const form = await req.formData();
30822
- const written = await writeUploadedFiles(form, opts.ws.modulesDir);
30823
- return Response.json({ ok: true, written }, { headers: ctx.corsHeaders });
31278
+ const stagingDir = join19(opts.ws.arcDir, ".staging");
31279
+ mkdirSync14(stagingDir, { recursive: true });
31280
+ const written = [];
31281
+ for (const name of ["styles.css", "theme.css"]) {
31282
+ const f2 = form.get(name);
31283
+ if (isFile(f2)) {
31284
+ await writeField(stagingDir, name, f2);
31285
+ written.push(name);
31286
+ }
31287
+ }
31288
+ return Response.json({ ok: true, written }, { headers: cors });
30824
31289
  }
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);
31290
+ if (p3 === "/api/deploy/manifest" && req.method === "POST") {
31291
+ const body = await req.json();
31292
+ if (!validateManifest(body)) {
31293
+ return badRequest("invalid manifest body", cors);
31294
+ }
31295
+ const stagingRoot = join19(opts.ws.arcDir, ".staging");
31296
+ const stagingModules = join19(stagingRoot, "modules");
31297
+ let serverSideChanged = false;
31298
+ if (existsSync15(stagingModules)) {
31299
+ const stagedNames = readDir(stagingModules);
31300
+ for (const name of stagedNames) {
31301
+ const src2 = join19(stagingModules, name);
31302
+ const dst = join19(opts.ws.arcDir, "modules", name);
31303
+ if (existsSync15(join19(src2, "server.js")))
31304
+ serverSideChanged = true;
31305
+ if (existsSync15(dst)) {
31306
+ rmSync4(dst, { recursive: true, force: true });
31307
+ }
31308
+ mkdirSync14(dirname8(dst), { recursive: true });
31309
+ cpSync(src2, dst, { recursive: true });
30836
31310
  }
30837
31311
  }
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);
31312
+ for (const name of ["styles.css", "theme.css"]) {
31313
+ const src2 = join19(stagingRoot, name);
31314
+ if (existsSync15(src2)) {
31315
+ cpSync(src2, join19(opts.ws.arcDir, name));
31316
+ }
31317
+ }
31318
+ writeFileSync15(join19(opts.ws.modulesDir, "manifest.json"), JSON.stringify(body, null, 2));
31319
+ opts.setManifest(body);
31320
+ rmSync4(stagingRoot, { recursive: true, force: true });
31321
+ if (!serverSideChanged) {
31322
+ opts.notifyReload(body);
30844
31323
  }
30845
- return Response.json({ ok: true, written: [...writtenShell, ...writtenRoot] }, { headers: ctx.corsHeaders });
31324
+ return Response.json({
31325
+ ok: true,
31326
+ moduleCount: body.modules.length,
31327
+ needsRestart: serverSideChanged
31328
+ }, { headers: cors });
30846
31329
  }
30847
- return new Response("Not Found", {
30848
- status: 404,
30849
- headers: ctx.corsHeaders
30850
- });
31330
+ return new Response("Not Found", { status: 404, headers: cors });
30851
31331
  };
30852
31332
  }
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);
31333
+ function badRequest(msg, cors) {
31334
+ return Response.json({ error: msg }, { status: 400, headers: cors });
30860
31335
  }
30861
31336
  function isFile(v3) {
30862
31337
  return typeof v3 === "object" && v3 !== null && typeof v3.arrayBuffer === "function" && typeof v3.name === "string";
30863
31338
  }
30864
- async function writeUploadedFileList(files, targetDir) {
30865
- const written = [];
31339
+ async function writeField(targetDir, name, file) {
31340
+ const safe = normalize2(name);
30866
31341
  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);
31342
+ const full = resolve(safeRoot, safe);
31343
+ if (!full.startsWith(safeRoot + "/") && full !== safeRoot) {
31344
+ throw new Error(`Path traversal rejected: ${name}`);
30877
31345
  }
30878
- return written;
31346
+ mkdirSync14(dirname8(full), { recursive: true });
31347
+ writeFileSync15(full, Buffer.from(await file.arrayBuffer()));
31348
+ }
31349
+ function sanitizeName2(name) {
31350
+ if (!/^[a-zA-Z0-9_-]+$/.test(name))
31351
+ return null;
31352
+ return name;
31353
+ }
31354
+ function readDir(dir) {
31355
+ if (!existsSync15(dir))
31356
+ return [];
31357
+ return __require("fs").readdirSync(dir);
31358
+ }
31359
+ async function runBun(args, cwd) {
31360
+ const proc2 = spawn4({
31361
+ cmd: ["bun", ...args],
31362
+ cwd,
31363
+ stdout: "inherit",
31364
+ stderr: "inherit"
31365
+ });
31366
+ const exit = await proc2.exited;
31367
+ return exit === 0;
30879
31368
  }
30880
31369
  function validateManifest(m4) {
30881
31370
  if (!m4 || typeof m4 !== "object")
@@ -30948,7 +31437,7 @@ function getMime(path4) {
30948
31437
  return MIME[ext2] ?? "application/octet-stream";
30949
31438
  }
30950
31439
  function serveFile(filePath, headers = {}) {
30951
- if (!existsSync14(filePath))
31440
+ if (!existsSync16(filePath))
30952
31441
  return new Response("Not Found", { status: 404 });
30953
31442
  return new Response(Bun.file(filePath), {
30954
31443
  headers: { "Content-Type": getMime(filePath), ...headers }
@@ -31037,7 +31526,7 @@ function staticFilesHandler(ws, devMode, moduleAccessMap) {
31037
31526
  return (_req, url, ctx) => {
31038
31527
  const path4 = url.pathname;
31039
31528
  if (path4.startsWith("/shell/"))
31040
- return serveFile(join17(ws.shellDir, path4.slice(7)), ctx.corsHeaders);
31529
+ return serveFile(join20(ws.shellDir, path4.slice(7)), ctx.corsHeaders);
31041
31530
  if (path4.startsWith("/modules/")) {
31042
31531
  const fileWithParams = path4.slice(9);
31043
31532
  const filename = fileWithParams.split("?")[0];
@@ -31049,25 +31538,25 @@ function staticFilesHandler(ws, devMode, moduleAccessMap) {
31049
31538
  return new Response("Forbidden", { status: 403, headers: ctx.corsHeaders });
31050
31539
  }
31051
31540
  }
31052
- return serveFile(join17(ws.modulesDir, filename), {
31541
+ return serveFile(join20(ws.modulesDir, filename), {
31053
31542
  ...ctx.corsHeaders,
31054
31543
  "Cache-Control": devMode ? "no-cache" : "max-age=31536000,immutable"
31055
31544
  });
31056
31545
  }
31057
31546
  if (path4.startsWith("/locales/"))
31058
- return serveFile(join17(ws.arcDir, path4.slice(1)), ctx.corsHeaders);
31547
+ return serveFile(join20(ws.arcDir, path4.slice(1)), ctx.corsHeaders);
31059
31548
  if (path4.startsWith("/assets/"))
31060
- return serveFile(join17(ws.assetsDir, path4.slice(8)), ctx.corsHeaders);
31549
+ return serveFile(join20(ws.assetsDir, path4.slice(8)), ctx.corsHeaders);
31061
31550
  if (path4 === "/styles.css")
31062
- return serveFile(join17(ws.arcDir, "styles.css"), ctx.corsHeaders);
31551
+ return serveFile(join20(ws.arcDir, "styles.css"), ctx.corsHeaders);
31063
31552
  if (path4 === "/theme.css")
31064
- return serveFile(join17(ws.arcDir, "theme.css"), ctx.corsHeaders);
31553
+ return serveFile(join20(ws.arcDir, "theme.css"), ctx.corsHeaders);
31065
31554
  if ((path4 === "/manifest.json" || path4 === "/manifest.webmanifest") && ws.manifest) {
31066
31555
  return serveFile(ws.manifest.path, ctx.corsHeaders);
31067
31556
  }
31068
31557
  if (path4.lastIndexOf(".") > path4.lastIndexOf("/")) {
31069
- const publicFile = join17(ws.publicDir, path4.slice(1));
31070
- if (existsSync14(publicFile))
31558
+ const publicFile = join20(ws.publicDir, path4.slice(1));
31559
+ if (existsSync16(publicFile))
31071
31560
  return serveFile(publicFile, ctx.corsHeaders);
31072
31561
  }
31073
31562
  return null;
@@ -31205,10 +31694,10 @@ async function startPlatformServer(opts) {
31205
31694
  };
31206
31695
  }
31207
31696
  const { createBunSQLiteAdapterFactory: createBunSQLiteAdapterFactory2 } = await Promise.resolve().then(() => (init_dist(), exports_dist2));
31208
- const dbPath = opts.dbPath || join17(ws.arcDir, "data", "arc.db");
31697
+ const dbPath = opts.dbPath || join20(ws.arcDir, "data", "arc.db");
31209
31698
  const dbDir = dbPath.substring(0, dbPath.lastIndexOf("/"));
31210
31699
  if (dbDir)
31211
- mkdirSync12(dbDir, { recursive: true });
31700
+ mkdirSync15(dbDir, { recursive: true });
31212
31701
  const arcServer = await createArcServer({
31213
31702
  context,
31214
31703
  dbAdapterFactory: createBunSQLiteAdapterFactory2(dbPath),
@@ -31251,7 +31740,7 @@ async function platformDev(opts = {}) {
31251
31740
  manifest,
31252
31741
  context,
31253
31742
  moduleAccess,
31254
- dbPath: join18(ws.rootDir, ".arc", "data", "dev.db"),
31743
+ dbPath: join21(ws.rootDir, ".arc", "data", "dev.db"),
31255
31744
  devMode: true,
31256
31745
  arcEntries
31257
31746
  });
@@ -31282,8 +31771,8 @@ async function platformDev(opts = {}) {
31282
31771
  }, 300);
31283
31772
  };
31284
31773
  for (const pkg of ws.packages) {
31285
- const srcDir = join18(pkg.path, "src");
31286
- if (!existsSync15(srcDir))
31774
+ const srcDir = join21(pkg.path, "src");
31775
+ if (!existsSync17(srcDir))
31287
31776
  continue;
31288
31777
  watch(srcDir, { recursive: true }, (_event, filename) => {
31289
31778
  if (!filename || filename.includes(".arc") || filename.endsWith(".d.ts") || filename.includes("node_modules") || filename.includes("dist"))
@@ -31293,8 +31782,8 @@ async function platformDev(opts = {}) {
31293
31782
  triggerRebuild();
31294
31783
  });
31295
31784
  }
31296
- const localesDir = join18(ws.rootDir, "locales");
31297
- if (existsSync15(localesDir)) {
31785
+ const localesDir = join21(ws.rootDir, "locales");
31786
+ if (existsSync17(localesDir)) {
31298
31787
  watch(localesDir, { recursive: false }, (_event, filename) => {
31299
31788
  if (!filename?.endsWith(".po"))
31300
31789
  return;
@@ -31310,17 +31799,41 @@ async function platformDev(opts = {}) {
31310
31799
  }
31311
31800
 
31312
31801
  // src/commands/platform-start.ts
31313
- import { existsSync as existsSync16, readFileSync as readFileSync13 } from "fs";
31314
- import { join as join19 } from "path";
31802
+ import { existsSync as existsSync18, readFileSync as readFileSync16 } from "fs";
31803
+ import { join as join22 } from "path";
31315
31804
  async function platformStart() {
31316
31805
  const ws = resolveWorkspace();
31317
31806
  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);
31807
+ const deployApi = process.env.ARC_DEPLOY_API === "1";
31808
+ const manifestPath = join22(ws.modulesDir, "manifest.json");
31809
+ if (!existsSync18(manifestPath)) {
31810
+ if (!deployApi) {
31811
+ err("No build found. Run `arc platform build` first.");
31812
+ process.exit(1);
31813
+ }
31814
+ log2("Pre-deploy mode \u2014 no manifest yet, awaiting first /api/deploy/*");
31815
+ const emptyManifest = {
31816
+ modules: [],
31817
+ shellHash: "",
31818
+ stylesHash: "",
31819
+ buildTime: new Date().toISOString()
31820
+ };
31821
+ const platform4 = await startPlatformServer({
31822
+ ws,
31823
+ port,
31824
+ manifest: emptyManifest,
31825
+ context: null,
31826
+ moduleAccess: new Map,
31827
+ dbPath: join22(ws.rootDir, ".arc", "data", "prod.db"),
31828
+ devMode: false,
31829
+ deployApi: true,
31830
+ arcEntries: []
31831
+ });
31832
+ ok(`Pre-deploy server on http://localhost:${port}`);
31833
+ registerSignalCleanup(platform4);
31834
+ return;
31322
31835
  }
31323
- const manifest = JSON.parse(readFileSync13(manifestPath, "utf-8"));
31836
+ const manifest = JSON.parse(readFileSync16(manifestPath, "utf-8"));
31324
31837
  log2("Loading server context...");
31325
31838
  const { context, moduleAccess } = await loadServerContext(ws.packages);
31326
31839
  if (context) {
@@ -31329,7 +31842,6 @@ async function platformStart() {
31329
31842
  log2("No context \u2014 server endpoints skipped");
31330
31843
  }
31331
31844
  const arcEntries = collectArcPeerDeps(ws.packages);
31332
- const deployApi = process.env.ARC_DEPLOY_API === "1";
31333
31845
  if (deployApi)
31334
31846
  ok("Deploy API enabled (/api/deploy/*)");
31335
31847
  const platform3 = await startPlatformServer({
@@ -31338,7 +31850,7 @@ async function platformStart() {
31338
31850
  manifest,
31339
31851
  context,
31340
31852
  moduleAccess,
31341
- dbPath: join19(ws.rootDir, ".arc", "data", "prod.db"),
31853
+ dbPath: join22(ws.rootDir, ".arc", "data", "prod.db"),
31342
31854
  devMode: false,
31343
31855
  deployApi,
31344
31856
  arcEntries
@@ -31346,6 +31858,9 @@ async function platformStart() {
31346
31858
  ok(`Server on http://localhost:${port}`);
31347
31859
  if (platform3.contextHandler)
31348
31860
  ok("Commands, queries, WebSocket \u2014 all on same port");
31861
+ registerSignalCleanup(platform3);
31862
+ }
31863
+ function registerSignalCleanup(platform3) {
31349
31864
  const cleanup = () => {
31350
31865
  platform3.stop();
31351
31866
  process.exit(0);
@@ -31364,6 +31879,7 @@ platform3.command("dev").description("Start platform in dev mode (Bun server + V
31364
31879
  platform3.command("build").description("Build platform for production").option("--no-cache", "Force full rebuild").action((opts) => platformBuild({ noCache: opts.cache === false }));
31365
31880
  platform3.command("start").description("Start platform in production mode (requires prior build)").action(platformStart);
31366
31881
  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));
31882
+ 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
31883
  program2.parse(process.argv);
31368
31884
  if (process.argv.length === 2) {
31369
31885
  program2.help();