@arcote.tech/arc-cli 0.7.20 → 0.7.22

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
@@ -853,7 +853,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
853
853
  this._exitCallback = (err) => {
854
854
  if (err.code !== "commander.executeSubCommandAsync") {
855
855
  throw err;
856
- } else {}
856
+ }
857
857
  };
858
858
  }
859
859
  return this;
@@ -34723,6 +34723,17 @@ function serverExternalsPlugin() {
34723
34723
  }
34724
34724
  };
34725
34725
  }
34726
+ function workspaceSourcePlugin(srcByName) {
34727
+ return {
34728
+ name: "workspace-source",
34729
+ setup(build2) {
34730
+ build2.onResolve({ filter: /^[^./]/ }, (args) => {
34731
+ const src = srcByName.get(args.path);
34732
+ return src ? { path: src, sideEffects: true } : null;
34733
+ });
34734
+ }
34735
+ };
34736
+ }
34726
34737
  function jsxDevShimPlugin() {
34727
34738
  return {
34728
34739
  name: "jsx-dev-runtime-shim",
@@ -34743,9 +34754,10 @@ export { Fragment };
34743
34754
  };
34744
34755
  }
34745
34756
  var CONTEXT_CLIENTS = [
34746
- { name: "server", target: "bun", defines: { ONLY_SERVER: "true", ONLY_BROWSER: "false", ONLY_CLIENT: "false" } },
34747
34757
  { name: "browser", target: "browser", defines: { ONLY_SERVER: "false", ONLY_BROWSER: "true", ONLY_CLIENT: "true" } }
34748
34758
  ];
34759
+ var SERVER_DEFINES = { ONLY_SERVER: "true", ONLY_BROWSER: "false", ONLY_CLIENT: "false" };
34760
+ var SERVER_ENTRY_FILE = "_server.js";
34749
34761
  function discoverPackages(rootDir) {
34750
34762
  const rootPkg = JSON.parse(readFileSync7(join8(rootDir, "package.json"), "utf-8"));
34751
34763
  const workspaceGlobs = rootPkg.workspaces ?? [];
@@ -34838,9 +34850,7 @@ async function buildContextClient(pkg, rootDir, client, cache, noCache) {
34838
34850
  console.log(` building: ${pkg.name} (${client.name})`);
34839
34851
  const peerDeps = Object.keys(pkg.packageJson.peerDependencies ?? {});
34840
34852
  const allDeps = pkg.packageJson.dependencies ?? {};
34841
- const isBrowser2 = client.name === "browser";
34842
- const workspaceDeps = isBrowser2 ? Object.keys(allDeps) : Object.entries(allDeps).filter(([, spec]) => !spec.startsWith("workspace:")).map(([name]) => name);
34843
- const externals = [...peerDeps, ...workspaceDeps];
34853
+ const externals = [...peerDeps, ...Object.keys(allDeps)];
34844
34854
  const result = await Bun.build({
34845
34855
  entrypoints: [pkg.entrypoint],
34846
34856
  outdir: join8(outDir, "main"),
@@ -34848,7 +34858,7 @@ async function buildContextClient(pkg, rootDir, client, cache, noCache) {
34848
34858
  format: "esm",
34849
34859
  naming: "index.[ext]",
34850
34860
  external: externals,
34851
- plugins: isBrowser2 ? [jsxDevShimPlugin()] : [jsxDevShimPlugin(), serverExternalsPlugin()],
34861
+ plugins: [jsxDevShimPlugin()],
34852
34862
  define: client.defines
34853
34863
  });
34854
34864
  if (!result.success) {
@@ -34911,6 +34921,80 @@ async function buildContextPackages(rootDir, packages, cache, noCache) {
34911
34921
  }
34912
34922
  return { declarationErrors };
34913
34923
  }
34924
+ async function buildServerApp(rootDir, serverDir, packages, cache, noCache) {
34925
+ const contexts = packages.filter((p) => isContextPackage(p.packageJson));
34926
+ mkdirSync6(serverDir, { recursive: true });
34927
+ const srcByName = new Map(packages.map((p) => [p.name, p.entrypoint]));
34928
+ const externalSet = new Set(FRAMEWORK_PEERS);
34929
+ for (const p of packages) {
34930
+ for (const name of Object.keys(p.packageJson.peerDependencies ?? {})) {
34931
+ externalSet.add(name);
34932
+ }
34933
+ for (const [name, spec] of Object.entries(p.packageJson.dependencies ?? {})) {
34934
+ if (!spec.startsWith("workspace:"))
34935
+ externalSet.add(name);
34936
+ }
34937
+ }
34938
+ const external = [...externalSet];
34939
+ const unitId = "server-app";
34940
+ const inputHash = sha256OfJson({
34941
+ members: packages.map((p) => ({ name: p.name, src: pkgSourceHash(p) })).sort((a, b) => a.name.localeCompare(b.name)),
34942
+ contexts: contexts.map((p) => p.name).sort(),
34943
+ external: [...external].sort(),
34944
+ defines: SERVER_DEFINES
34945
+ });
34946
+ const entryFileAbs = join8(serverDir, SERVER_ENTRY_FILE);
34947
+ if (!noCache && isCacheHit(cache, unitId, inputHash, [entryFileAbs])) {
34948
+ console.log(` \u2713 cached: ${unitId}`);
34949
+ return { entryFile: SERVER_ENTRY_FILE, cached: true };
34950
+ }
34951
+ console.log(` building: ${unitId} (${contexts.length} server modules)`);
34952
+ for (const f of readdirSync4(serverDir)) {
34953
+ if (f.endsWith(".js"))
34954
+ rmSync(join8(serverDir, f), { force: true });
34955
+ }
34956
+ const tmpDir = join8(serverDir, "_entries");
34957
+ mkdirSync6(tmpDir, { recursive: true });
34958
+ const entrySrc = join8(tmpDir, SERVER_ENTRY_FILE.replace(/\.js$/, ".ts"));
34959
+ writeFileSync6(entrySrc, contexts.map((p) => `import "${p.name}";`).join(`
34960
+ `) + `
34961
+ `);
34962
+ let result;
34963
+ try {
34964
+ result = await Bun.build({
34965
+ entrypoints: [entrySrc],
34966
+ outdir: serverDir,
34967
+ target: "bun",
34968
+ format: "esm",
34969
+ splitting: true,
34970
+ naming: "[name].[ext]",
34971
+ external,
34972
+ plugins: [
34973
+ jsxDevShimPlugin(),
34974
+ serverExternalsPlugin(),
34975
+ workspaceSourcePlugin(srcByName)
34976
+ ],
34977
+ define: SERVER_DEFINES
34978
+ });
34979
+ } finally {
34980
+ rmSync(tmpDir, { recursive: true, force: true });
34981
+ }
34982
+ if (!result.success) {
34983
+ for (const log2 of result.logs)
34984
+ console.error(log2);
34985
+ throw new Error("Server app build failed");
34986
+ }
34987
+ const entryOut = result.outputs.find((o) => o.kind === "entry-point");
34988
+ if (!entryOut) {
34989
+ throw new Error("Server app build: entry not found in outputs");
34990
+ }
34991
+ if (basename2(entryOut.path) !== SERVER_ENTRY_FILE) {
34992
+ throw new Error(`Server app build: unexpected entry name ${basename2(entryOut.path)} (wanted ${SERVER_ENTRY_FILE})`);
34993
+ }
34994
+ const outputHash = sha256OfDir(serverDir);
34995
+ updateCache(cache, unitId, inputHash, { outputHash });
34996
+ return { entryFile: SERVER_ENTRY_FILE, cached: false };
34997
+ }
34914
34998
  async function buildBrowserApp(rootDir, outDir, plan, cache, noCache, i18nCollector) {
34915
34999
  mkdirSync6(outDir, { recursive: true });
34916
35000
  const publicMembers = plan.groups.get("public") ?? [];
@@ -35216,11 +35300,8 @@ import {
35216
35300
  writeFileSync as writeFileSync7
35217
35301
  } from "fs";
35218
35302
  import { join as join9 } from "path";
35219
- async function extractAccessMap(rootDir, packages) {
35220
- const serverBundles = packages.filter((p) => isContextPackage(p.packageJson)).map((p) => ({
35221
- name: p.name,
35222
- path: join9(p.path, "dist", "server", "main", "index.js")
35223
- })).filter((b) => existsSync8(b.path));
35303
+ async function extractAccessMap(rootDir, serverBundlePath) {
35304
+ const serverBundles = existsSync8(serverBundlePath) ? [{ name: "server", path: serverBundlePath }] : [];
35224
35305
  const workerDir = join9(rootDir, ".arc", ".tmp");
35225
35306
  mkdirSync7(workerDir, { recursive: true });
35226
35307
  const workerPath = join9(workerDir, `access-extractor-${Date.now()}.mjs`);
@@ -35549,8 +35630,9 @@ async function buildAll(ws, opts = {}) {
35549
35630
  log2(`Building (concurrency parallel${noCache ? ", no-cache" : ""})...`);
35550
35631
  assertOneModulePerPackage(ws.packages);
35551
35632
  await buildContextPackages(ws.rootDir, ws.packages, cache, noCache);
35552
- copyContextServerBundles(ws);
35553
- const accessMap = await extractAccessMap(ws.rootDir, ws.packages);
35633
+ const serverDir = join12(ws.arcDir, "server");
35634
+ const { entryFile: serverEntry } = await buildServerApp(ws.rootDir, serverDir, ws.packages, cache, noCache);
35635
+ const accessMap = await extractAccessMap(ws.rootDir, join12(serverDir, serverEntry));
35554
35636
  mkdirSync9(ws.arcDir, { recursive: true });
35555
35637
  writeFileSync9(join12(ws.arcDir, "access.json"), JSON.stringify(accessMap, null, 2) + `
35556
35638
  `);
@@ -35581,22 +35663,6 @@ function assembleManifest(ws, browser, cache) {
35581
35663
  buildTime: new Date().toISOString()
35582
35664
  };
35583
35665
  }
35584
- function copyContextServerBundles(ws) {
35585
- const outDir = join12(ws.arcDir, "server");
35586
- mkdirSync9(outDir, { recursive: true });
35587
- for (const pkg of ws.packages) {
35588
- if (!isContextPackage(pkg.packageJson))
35589
- continue;
35590
- const src = join12(pkg.path, "dist", "server", "main", "index.js");
35591
- if (!existsSync10(src)) {
35592
- err(`Server bundle missing for ${pkg.name}: ${src}`);
35593
- continue;
35594
- }
35595
- const safeName = pkg.path.split("/").pop();
35596
- const dst = join12(outDir, `${safeName}.js`);
35597
- copyFileSync(src, dst);
35598
- }
35599
- }
35600
35666
  function resolveAssetSource(from, pkgDir, rootDir) {
35601
35667
  if (from.startsWith("./") || from.startsWith("../")) {
35602
35668
  const resolved = join12(pkgDir, from);
@@ -35692,34 +35758,15 @@ async function loadServerContext(ws) {
35692
35758
  const platformPkg = JSON.parse(readFileSync11(join12(platformDir, "package.json"), "utf-8"));
35693
35759
  const platformEntry = join12(platformDir, platformPkg.main ?? "src/index.ts");
35694
35760
  await import(platformEntry);
35695
- const serverDir = join12(ws.arcDir, "server");
35696
- const bundles = existsSync10(serverDir) ? readdirSync6(serverDir).filter((f) => f.endsWith(".js")) : [];
35697
- if (bundles.length > 0) {
35698
- for (const file of bundles) {
35699
- const bundlePath = join12(serverDir, file);
35700
- try {
35701
- await import(bundlePath);
35702
- } catch (e) {
35703
- err(`Failed to load server bundle ${file}: ${e}`);
35704
- }
35705
- }
35706
- } else if (ws.packages.length > 0) {
35707
- const ctxPackages = ws.packages.filter((p) => isContextPackage(p.packageJson));
35708
- for (const ctx of ctxPackages) {
35709
- const serverDist = join12(ctx.path, "dist", "server", "main", "index.js");
35710
- if (!existsSync10(serverDist)) {
35711
- err(`Context server dist not found: ${serverDist}`);
35712
- continue;
35713
- }
35714
- try {
35715
- await import(serverDist);
35716
- } catch (e) {
35717
- err(`Failed to load server context from ${ctx.name}: ${e}`);
35718
- }
35719
- }
35720
- } else {
35761
+ const serverEntry = join12(ws.arcDir, "server", SERVER_ENTRY_FILE);
35762
+ if (!existsSync10(serverEntry)) {
35721
35763
  return { context: null, moduleAccess: new Map };
35722
35764
  }
35765
+ try {
35766
+ await import(serverEntry);
35767
+ } catch (e) {
35768
+ err(`Failed to load server bundle ${SERVER_ENTRY_FILE}: ${e}`);
35769
+ }
35723
35770
  const { getContext, getAllModuleAccess } = await import(platformEntry);
35724
35771
  return {
35725
35772
  context: getContext() ?? null,
@@ -36553,8 +36600,13 @@ ${envNames.map((name) => ` - "https://${cfg.envs[name].domain}"`).joi
36553
36600
 
36554
36601
  # Per-container CPU / memory / network / block-IO + restarts straight from
36555
36602
  # the Docker daemon (socket bind-mounted read-only, see compose).
36603
+ # api_version pinned: the receiver defaults to Docker API 1.25, which modern
36604
+ # daemons (Engine 25+ require >= 1.40) reject \u2014 without this the receiver
36605
+ # fails to start and takes the whole collector down. Quoted so YAML doesn't
36606
+ # parse 1.40 \u2192 1.4. Must be <= the daemon's max; 1.40 is the safe floor.
36556
36607
  docker_stats:
36557
36608
  endpoint: unix:///var/run/docker.sock
36609
+ api_version: "1.40"
36558
36610
  collection_interval: 30s
36559
36611
  metrics:
36560
36612
  container.restarts:
@@ -36824,6 +36876,15 @@ function generateAlloyConfig() {
36824
36876
  discovery.docker "containers" {
36825
36877
  host = "unix:///var/run/docker.sock"
36826
36878
  refresh_interval = "15s"
36879
+
36880
+ // Only containers managed by a compose project (our stack). Ad-hoc / rogue
36881
+ // containers (manual debug runs, other stacks) are excluded \u2014 one bad
36882
+ // stream (e.g. log entries older than Loki's reject window) otherwise 400s
36883
+ // the whole loki.write batch and drops good app logs with it.
36884
+ filter {
36885
+ name = "label"
36886
+ values = ["com.docker.compose.project"]
36887
+ }
36827
36888
  }
36828
36889
 
36829
36890
  discovery.relabel "containers" {
@@ -38030,9 +38091,20 @@ async function streamToString(stream2) {
38030
38091
  return "";
38031
38092
  return new Response(stream2).text();
38032
38093
  }
38094
+ function sshMuxArgs() {
38095
+ return [
38096
+ "-o",
38097
+ "ControlMaster=auto",
38098
+ "-o",
38099
+ `ControlPath=${join17(homedir3(), ".ssh", "cm-arc-%C")}`,
38100
+ "-o",
38101
+ "ControlPersist=120"
38102
+ ];
38103
+ }
38033
38104
  function baseSshArgs(target) {
38034
38105
  const key = pickSshKey(target);
38035
38106
  const args = [
38107
+ ...sshMuxArgs(),
38036
38108
  "-o",
38037
38109
  "BatchMode=yes",
38038
38110
  "-o",
@@ -38096,6 +38168,7 @@ async function canSsh(target) {
38096
38168
  async function scpUpload(target, localPath, remotePath) {
38097
38169
  const key = pickSshKey(target);
38098
38170
  const args = [
38171
+ ...sshMuxArgs(),
38099
38172
  "-o",
38100
38173
  "BatchMode=yes",
38101
38174
  "-o",
@@ -38120,6 +38193,22 @@ async function scpUpload(target, localPath, remotePath) {
38120
38193
  throw new Error(`scp failed (${exitCode}): ${stderr}`);
38121
38194
  }
38122
38195
  }
38196
+ async function closeSshMaster(target) {
38197
+ try {
38198
+ const proc2 = spawn3({
38199
+ cmd: [
38200
+ "ssh",
38201
+ ...baseSshArgs(target),
38202
+ "-O",
38203
+ "exit",
38204
+ `${target.user}@${target.host}`
38205
+ ],
38206
+ stdout: "ignore",
38207
+ stderr: "ignore"
38208
+ });
38209
+ await proc2.exited;
38210
+ } catch {}
38211
+ }
38123
38212
 
38124
38213
  // src/deploy/remote-state.ts
38125
38214
  var STATE_MARKER_PATH = "/opt/arc/.arc-state.json";
@@ -38127,38 +38216,55 @@ async function detectRemoteState(cfg) {
38127
38216
  if (cfg.target.host === "PENDING_TERRAFORM" || !cfg.target.host) {
38128
38217
  return { kind: "unreachable", reason: "target.host not yet set" };
38129
38218
  }
38130
- if (!await canSsh(cfg.target)) {
38219
+ const composeDir = cfg.target.remoteDir;
38220
+ const probe = [
38221
+ `command -v docker >/dev/null 2>&1 && echo DOCKER=1 || echo DOCKER=0`,
38222
+ `echo '---PS---'`,
38223
+ `[ -f ${composeDir}/docker-compose.yml ] && (cd ${composeDir} && docker compose ps --format '{{.Service}}' 2>/dev/null) || true`,
38224
+ `echo '---MARKER---'`,
38225
+ `cat ${STATE_MARKER_PATH} 2>/dev/null || true`
38226
+ ].join(`
38227
+ `);
38228
+ const res = await sshExec(cfg.target, probe, { quiet: true });
38229
+ if (res.exitCode !== 0) {
38131
38230
  if (await canSsh({ ...cfg.target, user: "root" })) {
38132
38231
  return { kind: "no-docker" };
38133
38232
  }
38134
38233
  return { kind: "unreachable", reason: "ssh connection failed" };
38135
38234
  }
38136
- const dockerCheck = await sshExec(cfg.target, "command -v docker", {
38137
- quiet: true
38138
- });
38139
- if (dockerCheck.exitCode !== 0) {
38235
+ const out = res.stdout;
38236
+ if (!out.includes("DOCKER=1")) {
38140
38237
  return { kind: "no-docker" };
38141
38238
  }
38142
- const composeDir = `${cfg.target.remoteDir}`;
38143
- const psCheck = await sshExec(cfg.target, `test -f ${composeDir}/docker-compose.yml && cd ${composeDir} && docker compose ps --format '{{.Service}}' || true`, { quiet: true });
38144
- if (psCheck.exitCode !== 0 || psCheck.stdout.trim() === "") {
38239
+ const psSection = sectionBetween(out, "---PS---", "---MARKER---").trim();
38240
+ if (psSection === "") {
38145
38241
  return { kind: "no-stack" };
38146
38242
  }
38147
- const running = psCheck.stdout.split(`
38243
+ const running = psSection.split(`
38148
38244
  `).map((l) => l.trim()).filter((l) => l.startsWith("arc-")).map((l) => l.replace(/^arc-/, ""));
38149
- const markerRaw = await sshExec(cfg.target, `cat ${STATE_MARKER_PATH}`, {
38150
- quiet: true
38151
- });
38245
+ const markerSection = afterMarker(out, "---MARKER---").trim();
38152
38246
  let marker = null;
38153
- if (markerRaw.exitCode === 0) {
38247
+ if (markerSection) {
38154
38248
  try {
38155
- marker = JSON.parse(markerRaw.stdout);
38249
+ marker = JSON.parse(markerSection);
38156
38250
  } catch {
38157
38251
  marker = null;
38158
38252
  }
38159
38253
  }
38160
38254
  return { kind: "ready", runningEnvs: running, marker };
38161
38255
  }
38256
+ function sectionBetween(s, start, end) {
38257
+ const i = s.indexOf(start);
38258
+ if (i < 0)
38259
+ return "";
38260
+ const from = i + start.length;
38261
+ const j = s.indexOf(end, from);
38262
+ return s.slice(from, j < 0 ? undefined : j);
38263
+ }
38264
+ function afterMarker(s, marker) {
38265
+ const i = s.indexOf(marker);
38266
+ return i < 0 ? "" : s.slice(i + marker.length);
38267
+ }
38162
38268
  async function writeStateMarker(target, marker) {
38163
38269
  const json = JSON.stringify(marker, null, 2);
38164
38270
  await assertExec(target, `sudo tee ${STATE_MARKER_PATH} > /dev/null <<'JSON'
@@ -38415,31 +38521,20 @@ async function updateEnvDeployment(opts) {
38415
38521
  const envVarName = `ARC_IMAGE_${upperEnv}`;
38416
38522
  const envPath = `${cfg.target.remoteDir}/.env`;
38417
38523
  const escapedRef = fullRef.replace(/"/g, "\\\"");
38418
- const updateScript = [
38419
- `touch ${envPath} && `,
38420
- `awk -v line="${envVarName}=${escapedRef}" -v key="${envVarName}=" '`,
38421
- ` BEGIN { replaced=0 } `,
38422
- ` $0 ~ "^"key { print line; replaced=1; next } `,
38423
- ` { print } `,
38424
- ` END { if (!replaced) print line } `,
38425
- `' ${envPath} > ${envPath}.tmp && mv ${envPath}.tmp ${envPath}`
38426
- ].join("");
38427
- await assertExec(target, updateScript);
38428
- await assertExec(target, `cd ${cfg.target.remoteDir} && docker compose pull arc-${env2}`);
38429
- await assertExec(target, `cd ${cfg.target.remoteDir} && docker compose up -d arc-${env2}`);
38430
38524
  const retain = opts.retainImages ?? 3;
38431
38525
  const imageBaseName = imageBaseFromRef(fullRef);
38432
- if (imageBaseName) {
38433
- const pruneScript = [
38434
- `docker images "${imageBaseName}" --format "{{.Repository}}:{{.Tag}} {{.CreatedAt}}" `,
38435
- `| grep -v ":latest " `,
38436
- `| sort -k2,3 -r `,
38437
- `| tail -n +${retain + 1} `,
38438
- `| awk '{print $1}' `,
38439
- `| xargs -r docker rmi 2>/dev/null || true`
38440
- ].join("");
38441
- await sshExec(target, pruneScript, { quiet: true });
38442
- }
38526
+ const pruneCmd = imageBaseName ? `docker images "${imageBaseName}" --format "{{.Repository}}:{{.Tag}} {{.CreatedAt}}" | grep -v ":latest " | sort -k2,3 -r | tail -n +${retain + 1} | awk '{print $1}' | xargs -r docker rmi 2>/dev/null || true` : `true`;
38527
+ const script = [
38528
+ `set -e`,
38529
+ `cd ${cfg.target.remoteDir}`,
38530
+ `touch ${envPath}`,
38531
+ `awk -v line="${envVarName}=${escapedRef}" -v key="${envVarName}=" 'BEGIN{replaced=0} $0 ~ "^"key {print line; replaced=1; next} {print} END{if(!replaced) print line}' ${envPath} > ${envPath}.tmp && mv ${envPath}.tmp ${envPath}`,
38532
+ `docker compose pull arc-${env2}`,
38533
+ `docker compose up -d arc-${env2}`,
38534
+ pruneCmd
38535
+ ].join(`
38536
+ `);
38537
+ await assertExec(target, script);
38443
38538
  const ok2 = await healthCheck(target, env2);
38444
38539
  return { env: env2, fullRef, redeployed: ok2 };
38445
38540
  }
@@ -39480,38 +39575,42 @@ async function platformDeploy(envArg, options = {}) {
39480
39575
  }
39481
39576
  }
39482
39577
  log2("Inspecting remote server...");
39483
- const state = await detectRemoteState(cfg);
39484
- log2(`Remote state: ${state.kind}`);
39485
- const cliVersion = readCliVersion2();
39486
- const configHash = await hashDeployConfig(ws.rootDir);
39487
- await bootstrap({
39488
- cfg,
39489
- rootDir: ws.rootDir,
39490
- state,
39491
- cliVersion,
39492
- configHash,
39493
- forceAnsible: options.forceBootstrap
39494
- });
39495
- if (!options.imageTag) {
39496
- log2(`Logging in to ${cfg.registry.domain}...`);
39497
- await dockerLogin(cfg.registry);
39498
- log2(`Pushing ${fullRef}...`);
39499
- await dockerPush(fullRef);
39500
- ok("Image pushed");
39501
- }
39502
- for (const env2 of targetEnvs) {
39503
- log2(`Updating env "${env2}"...`);
39504
- const outcome = await updateEnvDeployment({
39505
- target: cfg.target,
39578
+ try {
39579
+ const state = await detectRemoteState(cfg);
39580
+ log2(`Remote state: ${state.kind}`);
39581
+ const cliVersion = readCliVersion2();
39582
+ const configHash = await hashDeployConfig(ws.rootDir);
39583
+ await bootstrap({
39506
39584
  cfg,
39507
- env: env2,
39508
- fullRef
39585
+ rootDir: ws.rootDir,
39586
+ state,
39587
+ cliVersion,
39588
+ configHash,
39589
+ forceAnsible: options.forceBootstrap
39509
39590
  });
39510
- if (outcome.redeployed) {
39511
- ok(`${env2}: live at ${fullRef}`);
39512
- } else {
39513
- err(`${env2}: deployed but health check did not pass within retries \u2014 check \`docker logs arc-${env2}\``);
39591
+ if (!options.imageTag) {
39592
+ log2(`Logging in to ${cfg.registry.domain}...`);
39593
+ await dockerLogin(cfg.registry);
39594
+ log2(`Pushing ${fullRef}...`);
39595
+ await dockerPush(fullRef);
39596
+ ok("Image pushed");
39597
+ }
39598
+ for (const env2 of targetEnvs) {
39599
+ log2(`Updating env "${env2}"...`);
39600
+ const outcome = await updateEnvDeployment({
39601
+ target: cfg.target,
39602
+ cfg,
39603
+ env: env2,
39604
+ fullRef
39605
+ });
39606
+ if (outcome.redeployed) {
39607
+ ok(`${env2}: live at ${fullRef}`);
39608
+ } else {
39609
+ err(`${env2}: deployed but health check did not pass within retries \u2014 check \`docker logs arc-${env2}\``);
39610
+ }
39514
39611
  }
39612
+ } finally {
39613
+ await closeSshMaster(cfg.target);
39515
39614
  }
39516
39615
  }
39517
39616
  function readCliVersion2() {
@@ -40790,6 +40889,7 @@ async function createArcServer(config) {
40790
40889
  const cronScheduler = new CronScheduler(contextHandler);
40791
40890
  cronScheduler.start();
40792
40891
  const connectionManager = new ConnectionManager;
40892
+ const coep = config.coep ?? "unsafe-none";
40793
40893
  function buildCorsHeaders(req) {
40794
40894
  const origin = req?.headers.get("Origin") || "*";
40795
40895
  return {
@@ -40798,7 +40898,7 @@ async function createArcServer(config) {
40798
40898
  "Access-Control-Allow-Headers": "Content-Type, Authorization, X-Arc-Scope, X-Arc-Tokens",
40799
40899
  "Access-Control-Allow-Credentials": "true",
40800
40900
  "Cross-Origin-Opener-Policy": "same-origin",
40801
- "Cross-Origin-Embedder-Policy": "require-corp",
40901
+ "Cross-Origin-Embedder-Policy": coep,
40802
40902
  "Cross-Origin-Resource-Policy": "cross-origin"
40803
40903
  };
40804
40904
  }
@@ -41259,6 +41359,7 @@ function spaFallbackHandler(getShellHtml) {
41259
41359
  }
41260
41360
  async function startPlatformServer(opts) {
41261
41361
  const { ws, port, devMode, context: context2 } = opts;
41362
+ const coep = process.env.ARC_COEP ?? opts.coep ?? "unsafe-none";
41262
41363
  ensureModuleSigSecret(ws, !!devMode);
41263
41364
  let telemetry;
41264
41365
  let telemetryShutdown;
@@ -41307,7 +41408,7 @@ async function startPlatformServer(opts) {
41307
41408
  "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
41308
41409
  "Access-Control-Allow-Headers": "Content-Type, Authorization, X-Arc-Tokens",
41309
41410
  "Cross-Origin-Opener-Policy": "same-origin",
41310
- "Cross-Origin-Embedder-Policy": "require-corp",
41411
+ "Cross-Origin-Embedder-Policy": coep ?? "unsafe-none",
41311
41412
  "Cross-Origin-Resource-Policy": "cross-origin"
41312
41413
  };
41313
41414
  const server = Bun.serve({
@@ -41359,6 +41460,7 @@ async function startPlatformServer(opts) {
41359
41460
  context: context2,
41360
41461
  dbAdapterFactory,
41361
41462
  port,
41463
+ coep,
41362
41464
  httpHandlers: [
41363
41465
  apiEndpointsHandler(ws, getManifest, null, moduleAccessMap, telemetry),
41364
41466
  devReloadHandler(sseClients),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcote.tech/arc-cli",
3
- "version": "0.7.20",
3
+ "version": "0.7.22",
4
4
  "description": "CLI tool for Arc framework",
5
5
  "module": "index.ts",
6
6
  "main": "dist/index.js",
@@ -12,13 +12,13 @@
12
12
  "build": "bun build --target=bun ./src/index.ts --outdir=dist --external @arcote.tech/arc --external @arcote.tech/arc-ds --external @arcote.tech/arc-react --external @arcote.tech/platform --external '@opentelemetry/*' && chmod +x dist/index.js"
13
13
  },
14
14
  "dependencies": {
15
- "@arcote.tech/arc": "^0.7.20",
16
- "@arcote.tech/arc-ds": "^0.7.20",
17
- "@arcote.tech/arc-react": "^0.7.20",
18
- "@arcote.tech/arc-host": "^0.7.20",
19
- "@arcote.tech/arc-adapter-db-sqlite": "^0.7.20",
20
- "@arcote.tech/arc-adapter-db-postgres": "^0.7.20",
21
- "@arcote.tech/arc-otel": "^0.7.20",
15
+ "@arcote.tech/arc": "^0.7.22",
16
+ "@arcote.tech/arc-ds": "^0.7.22",
17
+ "@arcote.tech/arc-react": "^0.7.22",
18
+ "@arcote.tech/arc-host": "^0.7.22",
19
+ "@arcote.tech/arc-adapter-db-sqlite": "^0.7.22",
20
+ "@arcote.tech/arc-adapter-db-postgres": "^0.7.22",
21
+ "@arcote.tech/arc-otel": "^0.7.22",
22
22
  "@opentelemetry/api": "^1.9.0",
23
23
  "@opentelemetry/api-logs": "^0.57.0",
24
24
  "@opentelemetry/core": "^1.30.0",
@@ -31,7 +31,7 @@
31
31
  "@opentelemetry/sdk-trace-base": "^1.30.0",
32
32
  "@opentelemetry/sdk-trace-node": "^1.30.0",
33
33
  "@opentelemetry/semantic-conventions": "^1.27.0",
34
- "@arcote.tech/platform": "^0.7.20",
34
+ "@arcote.tech/platform": "^0.7.22",
35
35
  "@clack/prompts": "^0.9.0",
36
36
  "commander": "^11.1.0",
37
37
  "chokidar": "^3.5.3",
@@ -7,7 +7,6 @@ import {
7
7
  writeFileSync,
8
8
  } from "fs";
9
9
  import { join } from "path";
10
- import { isContextPackage, type WorkspacePackage } from "./module-builder";
11
10
 
12
11
  // ---------------------------------------------------------------------------
13
12
  // access-extractor — discovers per-module access rules (`protectedBy(...)`)
@@ -27,8 +26,8 @@ import { isContextPackage, type WorkspacePackage } from "./module-builder";
27
26
  // non-context (pure-browser) packages are skipped. This is sensible: access
28
27
  // checks logically belong with server state, not display components.
29
28
  //
30
- // Order requirement: buildContextPackages MUST run before extractAccessMap
31
- // server bundles at `packages/<pkg>/dist/server/main/index.js` must exist.
29
+ // Order requirement: buildServerApp MUST run before extractAccessMap — the
30
+ // combined server bundle at `<arcDir>/server/_server.js` must exist.
32
31
  // ---------------------------------------------------------------------------
33
32
 
34
33
  export interface SerializedAccessRule {
@@ -44,19 +43,16 @@ export type SerializedAccessMap = Record<string, SerializedModuleAccess>;
44
43
 
45
44
  export async function extractAccessMap(
46
45
  rootDir: string,
47
- packages: readonly WorkspacePackage[],
46
+ serverBundlePath: string,
48
47
  ): Promise<SerializedAccessMap> {
49
- // Each entry: a context-package server bundle path that the worker will
50
- // dynamically import. Bun resolves the bundle's internal external imports
51
- // (`@arcote.tech/platform` etc.) via node_modules walking from the bundle
52
- // location workspace symlinks point back to source.
53
- const serverBundles = packages
54
- .filter((p) => isContextPackage(p.packageJson))
55
- .map((p) => ({
56
- name: p.name,
57
- path: join(p.path, "dist", "server", "main", "index.js"),
58
- }))
59
- .filter((b) => existsSync(b.path));
48
+ // The combined server bundle (`<arcDir>/server/_server.js`) side-effect-
49
+ // registers every module via the platform singleton when imported. The
50
+ // worker imports just this entry; Bun resolves its `chunk-<hash>.js` siblings
51
+ // and external peers (`@arcote.tech/platform` etc.) by walking node_modules
52
+ // from the bundle's directory.
53
+ const serverBundles = existsSync(serverBundlePath)
54
+ ? [{ name: "server", path: serverBundlePath }]
55
+ : [];
60
56
 
61
57
  // Worker must live INSIDE the workspace tree so Bun's module resolver can
62
58
  // walk up to <rootDir>/node_modules and find @arcote.tech/platform via the