@arcote.tech/arc-cli 0.7.5 → 0.7.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,7 +11,7 @@ import {
11
11
  import { existsSync, mkdirSync } from "fs";
12
12
  import { join } from "path";
13
13
  import { readTranslationsConfig } from "../i18n";
14
- import type { BuildManifest, WorkspaceInfo } from "./shared";
14
+ import { err, ok, type BuildManifest, type WorkspaceInfo } from "./shared";
15
15
  import type { BuildManifestGroup, ModuleAccess } from "@arcote.tech/platform";
16
16
 
17
17
  // ---------------------------------------------------------------------------
@@ -97,6 +97,21 @@ export function generateShellHtml(
97
97
  initial?: { file: string; hash: string },
98
98
  stylesHash?: string,
99
99
  ): string {
100
+ // OpenTelemetry config — injected as a global so the browser SDK chunk
101
+ // (lazy-loaded by start-app.ts) can pick it up without a fetch. Endpoint
102
+ // is same-origin so Caddy can apply CORS + auth uniformly.
103
+ const otelConfig = process.env.ARC_OTEL_ENABLED === "true"
104
+ ? {
105
+ enabled: true,
106
+ endpoint: "/otel",
107
+ serviceName: process.env.OTEL_SERVICE_NAME ?? `${appName}-browser`,
108
+ environment: process.env.NODE_ENV === "production" ? "production" : "development",
109
+ sampleRate: Number(process.env.ARC_OTEL_BROWSER_SAMPLE_RATE ?? "0.1"),
110
+ }
111
+ : null;
112
+ const otelTag = otelConfig
113
+ ? `\n <script>window.__ARC_OTEL_CONFIG=${JSON.stringify(otelConfig)};</script>`
114
+ : "";
100
115
  // Initial bundle carries framework, public modules, and PlatformApp re-export.
101
116
  // No importmap — single Bun.build with splitting:true inlines + dedups everything
102
117
  // across initial and per-token group bundles via auto-emitted chunk-<hash>.js.
@@ -117,7 +132,7 @@ export function generateShellHtml(
117
132
  <title>${manifest?.title ?? appName}</title>${manifest?.favicon ? `\n <link rel="icon" href="${manifest.favicon}">` : ""}${manifest ? `\n <link rel="manifest" href="/manifest.json">` : ""}
118
133
  <link rel="stylesheet" href="/styles.css${stylesQs}" />
119
134
  <link rel="stylesheet" href="/theme.css${stylesQs}" />
120
- <link rel="modulepreload" href="${initialUrl}" />
135
+ <link rel="modulepreload" href="${initialUrl}" />${otelTag}
121
136
  </head>
122
137
  <body>
123
138
  <div id="root"></div>
@@ -480,6 +495,30 @@ export async function startPlatformServer(
480
495
  ): Promise<PlatformServer> {
481
496
  const { ws, port, devMode, context } = opts;
482
497
  ensureModuleSigSecret(ws, !!devMode);
498
+
499
+ // OpenTelemetry — only when explicitly enabled (deploy injects the env
500
+ // when `observability.enabled` is set in deploy.arc.json). Dynamic import
501
+ // keeps the OTel SDK out of bundles that don't use it.
502
+ let telemetry: import("@arcote.tech/arc-otel").ArcTelemetry | undefined;
503
+ let telemetryShutdown: (() => Promise<void>) | undefined;
504
+ if (process.env.ARC_OTEL_ENABLED === "true") {
505
+ try {
506
+ const { initServerTelemetry } = await import("@arcote.tech/arc-otel/server");
507
+ const init = initServerTelemetry({
508
+ serviceName: process.env.OTEL_SERVICE_NAME ?? ws.appName,
509
+ environment: "server",
510
+ endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
511
+ mode: devMode ? "development" : "production",
512
+ sampleRate: devMode ? 1.0 : 1.0, // head-based 100%, collector tail-samples
513
+ });
514
+ telemetry = init.telemetry;
515
+ telemetryShutdown = init.shutdown;
516
+ ok("Telemetry enabled — exporting to " + process.env.OTEL_EXPORTER_OTLP_ENDPOINT);
517
+ } catch (e) {
518
+ err(`Failed to init telemetry: ${(e as Error).message}`);
519
+ }
520
+ }
521
+
483
522
  const moduleAccessMap = opts.moduleAccess ?? new Map();
484
523
  let manifest = opts.manifest;
485
524
  const getManifest = () => manifest;
@@ -561,7 +600,25 @@ export async function startPlatformServer(
561
600
  // Context available — use createArcServer with platform handlers on top.
562
601
  // `resolveDbAdapterFactory` picks SQLite or Postgres based on DATABASE_URL.
563
602
  const dbPath = opts.dbPath || join(ws.arcDir, "data", "arc.db");
564
- const dbAdapterFactory = await resolveDbAdapterFactory(dbPath);
603
+ const baseDbFactory = await resolveDbAdapterFactory(dbPath);
604
+
605
+ // Wrap the DB adapter for per-transaction spans when telemetry is on.
606
+ // Done here (not in arc-host) so arc-host stays free of arc-otel runtime
607
+ // imports — duplicate @opentelemetry/api copies silently drop spans.
608
+ let dbAdapterFactory = baseDbFactory;
609
+ if (telemetry) {
610
+ // Pull wrapDbAdapter from `/server` (not the root export) so Bun
611
+ // doesn't double-bundle arc-otel for the CLI bundle. Both /server
612
+ // and / share telemetry.ts but split bundling otherwise.
613
+ const { wrapDbAdapter } = await import("@arcote.tech/arc-otel/server");
614
+ const dbSystem: "postgresql" | "sqlite" = process.env.DATABASE_URL
615
+ ? "postgresql"
616
+ : "sqlite";
617
+ dbAdapterFactory = async (ctx: any) => {
618
+ const a = await baseDbFactory(ctx);
619
+ return wrapDbAdapter(a, telemetry, dbSystem);
620
+ };
621
+ }
565
622
 
566
623
  const arcServer = await createArcServer({
567
624
  context,
@@ -575,6 +632,7 @@ export async function startPlatformServer(
575
632
  spaFallbackHandler(getShellHtml),
576
633
  ],
577
634
  onWsClose: (clientId) => cleanupClientSubs(clientId),
635
+ telemetry,
578
636
  });
579
637
 
580
638
  return {
@@ -583,6 +641,9 @@ export async function startPlatformServer(
583
641
  connectionManager: arcServer.connectionManager,
584
642
  setManifest,
585
643
  notifyReload,
586
- stop: () => arcServer.stop(),
644
+ stop: () => {
645
+ arcServer.stop();
646
+ void telemetryShutdown?.();
647
+ },
587
648
  };
588
649
  }