@arcote.tech/arc-cli 0.7.7 → 0.7.9

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
@@ -14860,10 +14860,11 @@ class ArcFunction {
14860
14860
  params: schema instanceof ArcObject ? schema : new ArcObject(schema)
14861
14861
  });
14862
14862
  }
14863
- withResult(schema) {
14863
+ withResult(...schemas) {
14864
+ const results = schemas.map((s) => s instanceof ArcObject ? s : new ArcObject(s));
14864
14865
  return new ArcFunction({
14865
14866
  ...this.data,
14866
- result: schema instanceof ArcObject ? schema : new ArcObject(schema)
14867
+ results
14867
14868
  });
14868
14869
  }
14869
14870
  query(elements) {
@@ -14891,6 +14892,12 @@ class ArcFunction {
14891
14892
  description: desc
14892
14893
  });
14893
14894
  }
14895
+ private() {
14896
+ return new ArcFunction({
14897
+ ...this.data,
14898
+ isPrivate: true
14899
+ });
14900
+ }
14894
14901
  handle(handler) {
14895
14902
  return new ArcFunction({
14896
14903
  ...this.data,
@@ -14912,8 +14919,8 @@ class ArcFunction {
14912
14919
  get params() {
14913
14920
  return this.data.params;
14914
14921
  }
14915
- get result() {
14916
- return this.data.result;
14922
+ get results() {
14923
+ return this.data.results;
14917
14924
  }
14918
14925
  async verifyProtections(tokens) {
14919
14926
  if (!this.data.protections || this.data.protections.length === 0) {
@@ -14937,7 +14944,7 @@ class ArcFunction {
14937
14944
  toJsonSchema() {
14938
14945
  return {
14939
14946
  params: this.data.params?.toJsonSchema?.() ?? null,
14940
- result: this.data.result?.toJsonSchema?.() ?? null
14947
+ results: this.data.results.map((r2) => r2.toJsonSchema())
14941
14948
  };
14942
14949
  }
14943
14950
  }
@@ -15294,19 +15301,26 @@ function buildContextAccessor(context2, adapters, contextMethod, onCall) {
15294
15301
  if (typeof ctxFn !== "function")
15295
15302
  continue;
15296
15303
  const methods = ctxFn.call(element2, adapters);
15297
- if (!methods || typeof methods !== "object")
15304
+ if (!methods)
15305
+ continue;
15306
+ const elementName = element2.name;
15307
+ if (typeof methods === "function") {
15308
+ result[elementName] = (...args) => onCall({ element: elementName, method: "", args }, () => methods(...args));
15309
+ continue;
15310
+ }
15311
+ if (typeof methods !== "object")
15298
15312
  continue;
15299
15313
  const wrapped = {};
15300
15314
  for (const [methodName, method] of Object.entries(methods)) {
15301
15315
  if (typeof method !== "function")
15302
15316
  continue;
15303
- wrapped[methodName] = (...args) => onCall({ element: element2.name, method: methodName, args }, () => method(...args));
15317
+ wrapped[methodName] = (...args) => onCall({ element: elementName, method: methodName, args }, () => method(...args));
15304
15318
  }
15305
- result[element2.name] = wrapped;
15319
+ result[elementName] = wrapped;
15306
15320
  }
15307
15321
  return result;
15308
15322
  }
15309
- function executeDescriptor(descriptor, context2, adapters, contextMethod) {
15323
+ function executeDescriptor(descriptor, context2, adapters, contextMethod, options) {
15310
15324
  const element2 = context2.get(descriptor.element);
15311
15325
  if (!element2) {
15312
15326
  throw new Error(`Element '${descriptor.element}' not found in context`);
@@ -15316,10 +15330,19 @@ function executeDescriptor(descriptor, context2, adapters, contextMethod) {
15316
15330
  throw new Error(`Element '${descriptor.element}' has no ${contextMethod}`);
15317
15331
  }
15318
15332
  const methods = ctxFn.call(element2, adapters);
15333
+ if (typeof methods === "function") {
15334
+ if (options?.fromWire && methods.__isPrivate) {
15335
+ throw new Error(`Element "${descriptor.element}" is private and not callable from a client.`);
15336
+ }
15337
+ return methods(...descriptor.args);
15338
+ }
15319
15339
  const method = methods?.[descriptor.method];
15320
15340
  if (typeof method !== "function") {
15321
15341
  throw new Error(`Method '${descriptor.method}' not found on '${descriptor.element}'`);
15322
15342
  }
15343
+ if (options?.fromWire && method.__isPrivate) {
15344
+ throw new Error(`Method "${descriptor.element}.${descriptor.method}" is private and not callable from a client.`);
15345
+ }
15323
15346
  return method(...descriptor.args);
15324
15347
  }
15325
15348
 
@@ -19202,7 +19225,7 @@ var init_dist = __esm(() => {
19202
19225
  this.data = data;
19203
19226
  this.#fn = fn ?? new ArcFunction({
19204
19227
  params: data.params,
19205
- result: null,
19228
+ results: data.results,
19206
19229
  queryElements: data.queryElements,
19207
19230
  mutationElements: data.mutationElements,
19208
19231
  protections: data.protections || [],
@@ -19233,7 +19256,8 @@ var init_dist = __esm(() => {
19233
19256
  }, newFn);
19234
19257
  }
19235
19258
  withResult(...schemas) {
19236
- return new ArcCommand({ ...this.data, results: schemas }, this.#fn);
19259
+ const newFn = this.#fn.withResult(...schemas);
19260
+ return new ArcCommand({ ...this.data, results: newFn.data.results }, newFn);
19237
19261
  }
19238
19262
  handle(handler) {
19239
19263
  const newFn = new ArcFunction({
@@ -19324,7 +19348,7 @@ var init_dist = __esm(() => {
19324
19348
  this.data = data;
19325
19349
  this.#fn = fn ?? new ArcFunction({
19326
19350
  params: null,
19327
- result: null,
19351
+ results: [],
19328
19352
  queryElements: data.queryElements,
19329
19353
  mutationElements: data.mutationElements,
19330
19354
  protections: [],
@@ -19430,7 +19454,7 @@ var init_dist = __esm(() => {
19430
19454
  this.data = data;
19431
19455
  this.#fn = fn ?? new ArcFunction({
19432
19456
  params: null,
19433
- result: null,
19457
+ results: [],
19434
19458
  queryElements: data.queryElements,
19435
19459
  mutationElements: data.mutationElements,
19436
19460
  protections: data.protections || [],
@@ -22193,10 +22217,11 @@ class ArcFunction2 {
22193
22217
  params: schema instanceof ArcObject2 ? schema : new ArcObject2(schema)
22194
22218
  });
22195
22219
  }
22196
- withResult(schema) {
22220
+ withResult(...schemas) {
22221
+ const results = schemas.map((s) => s instanceof ArcObject2 ? s : new ArcObject2(s));
22197
22222
  return new ArcFunction2({
22198
22223
  ...this.data,
22199
- result: schema instanceof ArcObject2 ? schema : new ArcObject2(schema)
22224
+ results
22200
22225
  });
22201
22226
  }
22202
22227
  query(elements) {
@@ -22224,6 +22249,12 @@ class ArcFunction2 {
22224
22249
  description: desc
22225
22250
  });
22226
22251
  }
22252
+ private() {
22253
+ return new ArcFunction2({
22254
+ ...this.data,
22255
+ isPrivate: true
22256
+ });
22257
+ }
22227
22258
  handle(handler) {
22228
22259
  return new ArcFunction2({
22229
22260
  ...this.data,
@@ -22245,8 +22276,8 @@ class ArcFunction2 {
22245
22276
  get params() {
22246
22277
  return this.data.params;
22247
22278
  }
22248
- get result() {
22249
- return this.data.result;
22279
+ get results() {
22280
+ return this.data.results;
22250
22281
  }
22251
22282
  async verifyProtections(tokens) {
22252
22283
  if (!this.data.protections || this.data.protections.length === 0) {
@@ -22270,7 +22301,7 @@ class ArcFunction2 {
22270
22301
  toJsonSchema() {
22271
22302
  return {
22272
22303
  params: this.data.params?.toJsonSchema?.() ?? null,
22273
- result: this.data.result?.toJsonSchema?.() ?? null
22304
+ results: this.data.results.map((r2) => r2.toJsonSchema())
22274
22305
  };
22275
22306
  }
22276
22307
  }
@@ -22627,19 +22658,26 @@ function buildContextAccessor2(context2, adapters, contextMethod, onCall) {
22627
22658
  if (typeof ctxFn !== "function")
22628
22659
  continue;
22629
22660
  const methods = ctxFn.call(element2, adapters);
22630
- if (!methods || typeof methods !== "object")
22661
+ if (!methods)
22662
+ continue;
22663
+ const elementName = element2.name;
22664
+ if (typeof methods === "function") {
22665
+ result[elementName] = (...args) => onCall({ element: elementName, method: "", args }, () => methods(...args));
22666
+ continue;
22667
+ }
22668
+ if (typeof methods !== "object")
22631
22669
  continue;
22632
22670
  const wrapped = {};
22633
22671
  for (const [methodName, method] of Object.entries(methods)) {
22634
22672
  if (typeof method !== "function")
22635
22673
  continue;
22636
- wrapped[methodName] = (...args) => onCall({ element: element2.name, method: methodName, args }, () => method(...args));
22674
+ wrapped[methodName] = (...args) => onCall({ element: elementName, method: methodName, args }, () => method(...args));
22637
22675
  }
22638
- result[element2.name] = wrapped;
22676
+ result[elementName] = wrapped;
22639
22677
  }
22640
22678
  return result;
22641
22679
  }
22642
- function executeDescriptor2(descriptor, context2, adapters, contextMethod) {
22680
+ function executeDescriptor2(descriptor, context2, adapters, contextMethod, options) {
22643
22681
  const element2 = context2.get(descriptor.element);
22644
22682
  if (!element2) {
22645
22683
  throw new Error(`Element '${descriptor.element}' not found in context`);
@@ -22649,10 +22687,19 @@ function executeDescriptor2(descriptor, context2, adapters, contextMethod) {
22649
22687
  throw new Error(`Element '${descriptor.element}' has no ${contextMethod}`);
22650
22688
  }
22651
22689
  const methods = ctxFn.call(element2, adapters);
22690
+ if (typeof methods === "function") {
22691
+ if (options?.fromWire && methods.__isPrivate) {
22692
+ throw new Error(`Element "${descriptor.element}" is private and not callable from a client.`);
22693
+ }
22694
+ return methods(...descriptor.args);
22695
+ }
22652
22696
  const method = methods?.[descriptor.method];
22653
22697
  if (typeof method !== "function") {
22654
22698
  throw new Error(`Method '${descriptor.method}' not found on '${descriptor.element}'`);
22655
22699
  }
22700
+ if (options?.fromWire && method.__isPrivate) {
22701
+ throw new Error(`Method "${descriptor.element}.${descriptor.method}" is private and not callable from a client.`);
22702
+ }
22656
22703
  return method(...descriptor.args);
22657
22704
  }
22658
22705
 
@@ -24970,7 +25017,7 @@ var init_dist2 = __esm(() => {
24970
25017
  this.data = data;
24971
25018
  this.#fn = fn ?? new ArcFunction2({
24972
25019
  params: data.params,
24973
- result: null,
25020
+ results: data.results,
24974
25021
  queryElements: data.queryElements,
24975
25022
  mutationElements: data.mutationElements,
24976
25023
  protections: data.protections || [],
@@ -25001,7 +25048,8 @@ var init_dist2 = __esm(() => {
25001
25048
  }, newFn);
25002
25049
  }
25003
25050
  withResult(...schemas) {
25004
- return new ArcCommand2({ ...this.data, results: schemas }, this.#fn);
25051
+ const newFn = this.#fn.withResult(...schemas);
25052
+ return new ArcCommand2({ ...this.data, results: newFn.data.results }, newFn);
25005
25053
  }
25006
25054
  handle(handler) {
25007
25055
  const newFn = new ArcFunction2({
@@ -25092,7 +25140,7 @@ var init_dist2 = __esm(() => {
25092
25140
  this.data = data;
25093
25141
  this.#fn = fn ?? new ArcFunction2({
25094
25142
  params: null,
25095
- result: null,
25143
+ results: [],
25096
25144
  queryElements: data.queryElements,
25097
25145
  mutationElements: data.mutationElements,
25098
25146
  protections: [],
@@ -25198,7 +25246,7 @@ var init_dist2 = __esm(() => {
25198
25246
  this.data = data;
25199
25247
  this.#fn = fn ?? new ArcFunction2({
25200
25248
  params: null,
25201
- result: null,
25249
+ results: [],
25202
25250
  queryElements: data.queryElements,
25203
25251
  mutationElements: data.mutationElements,
25204
25252
  protections: data.protections || [],
@@ -25770,6 +25818,7 @@ __export(exports_init_server, {
25770
25818
  wrapDbAdapter: () => wrapDbAdapter,
25771
25819
  initServerTelemetry: () => initServerTelemetry
25772
25820
  });
25821
+ import { diag, DiagConsoleLogger, DiagLogLevel } from "@opentelemetry/api";
25773
25822
  import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
25774
25823
  import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
25775
25824
  import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
@@ -26083,6 +26132,9 @@ function wrapDbAdapter(adapter, telemetry, dbSystem) {
26083
26132
  }
26084
26133
  function initServerTelemetry(config) {
26085
26134
  const telemetry = new ArcTelemetry({ ...config, environment: "server" });
26135
+ if (telemetry.config.debug) {
26136
+ diag.setLogger(new DiagConsoleLogger, DiagLogLevel.ALL);
26137
+ }
26086
26138
  if (!telemetry.config.enabled) {
26087
26139
  return { telemetry, shutdown: async () => {} };
26088
26140
  }
@@ -26131,7 +26183,8 @@ function initServerTelemetry(config) {
26131
26183
  serviceName: config.serviceName,
26132
26184
  endpoint,
26133
26185
  sampleRate: telemetry.config.sampleRate,
26134
- mode: telemetry.config.mode
26186
+ mode: telemetry.config.mode,
26187
+ active: telemetry.active
26135
26188
  });
26136
26189
  }
26137
26190
  const shutdown = async () => {
@@ -34965,8 +35018,11 @@ var TAILWIND_INPUT_TEMPLATE = (rootRel) => `@import "tailwindcss";
34965
35018
 
34966
35019
  @source "${rootRel}/packages/*/*.{ts,tsx}";
34967
35020
  @source "${rootRel}/packages/*/src/**/*.{ts,tsx}";
34968
- @source "${rootRel}/node_modules/@arcote.tech/platform/src/**/*.{ts,tsx}";
34969
- @source "${rootRel}/node_modules/@arcote.tech/arc-ds/src/**/*.{ts,tsx}";
35021
+ /* Skanuj wszystkie framework packages \u2014 fragmenty (arc-chat, arc-ai-voice\u2026)
35022
+ wnosz\u0105 w\u0142asne komponenty React z klasami Tailwind, kt\xF3re musz\u0105 trafi\u0107 do
35023
+ CSS. Bez tego absolute positioning, color/border klasy w nowych fragmentach
35024
+ s\u0105 ignorowane \u2014 komponent renderuje si\u0119 bez styl\xF3w. */
35025
+ @source "${rootRel}/node_modules/@arcote.tech/*/src/**/*.{ts,tsx}";
34970
35026
 
34971
35027
  @custom-variant dark (&:is(.dark *));
34972
35028
 
@@ -35233,11 +35289,21 @@ function collectFrameworkDeps(arcDir, rootDir, packages, sharedDeps = []) {
35233
35289
  } catch (e) {
35234
35290
  console.warn(`[arc-otel] could not resolve @arcote.tech/arc-otel \u2014 image will run without telemetry deps: ${e.message}`);
35235
35291
  }
35292
+ let rootArc;
35293
+ const rootPkgPath = join10(rootDir, "package.json");
35294
+ if (existsSync9(rootPkgPath)) {
35295
+ try {
35296
+ const rootPkg = JSON.parse(readFileSync9(rootPkgPath, "utf-8"));
35297
+ if (rootPkg.arc && typeof rootPkg.arc === "object")
35298
+ rootArc = rootPkg.arc;
35299
+ } catch {}
35300
+ }
35236
35301
  const manifest = {
35237
35302
  name: "arc-platform-framework",
35238
35303
  private: true,
35239
35304
  type: "module",
35240
- dependencies: versions
35305
+ dependencies: versions,
35306
+ ...rootArc ? { arc: rootArc } : {}
35241
35307
  };
35242
35308
  const manifestPath = join10(arcDir, "package.json");
35243
35309
  writeFileSync8(manifestPath, JSON.stringify(manifest, null, 2) + `
@@ -36066,6 +36132,10 @@ function generateCompose({ cfg }) {
36066
36132
  lines.push(" retries: 20");
36067
36133
  lines.push(" networks:");
36068
36134
  lines.push(" - arc-net");
36135
+ if (env2.db.exposeOnLoopback !== undefined) {
36136
+ lines.push(" ports:");
36137
+ lines.push(` - "127.0.0.1:${env2.db.exposeOnLoopback}:5432"`);
36138
+ }
36069
36139
  lines.push("");
36070
36140
  }
36071
36141
  if (cfg.observability?.enabled) {
@@ -36255,7 +36325,10 @@ processors:
36255
36325
  send_batch_max_size: 1024
36256
36326
 
36257
36327
  # Tail-based sampling \u2014 applied after a full trace has been assembled.
36258
- # Errors and slow traces are kept 100%, everything else at 10%.
36328
+ # Errors + slow traces zachowywane w 100%, normalne traces r\xF3wnie\u017C 100%
36329
+ # przy obecnej skali (boostrap produkcji). Tail sampling matchuje OR po
36330
+ # policies \u2014 bez "always" policy WSZYSTKIE OK traces by\u0142yby droppowane.
36331
+ # Obni\u017C 'random_100pct' do np. 10% gdy ruch eksploduje.
36259
36332
  tail_sampling:
36260
36333
  decision_wait: 10s
36261
36334
  num_traces: 50000
@@ -36267,9 +36340,9 @@ processors:
36267
36340
  - name: slow
36268
36341
  type: latency
36269
36342
  latency: { threshold_ms: 500 }
36270
- - name: random_10pct
36343
+ - name: random_100pct
36271
36344
  type: probabilistic
36272
- probabilistic: { sampling_percentage: 10 }
36345
+ probabilistic: { sampling_percentage: 100 }
36273
36346
 
36274
36347
  # Drop high-cardinality / PII attributes that might slip past app-side
36275
36348
  # sanitization. Belt-and-suspenders before they hit long-term storage.
@@ -37188,7 +37261,8 @@ function validateDeployConfig(input) {
37188
37261
  } else if (dbType === "postgres") {
37189
37262
  db = {
37190
37263
  type: "postgres",
37191
- image: optionalString(dbRaw, `envs.${name}.db.image`)
37264
+ image: optionalString(dbRaw, `envs.${name}.db.image`),
37265
+ exposeOnLoopback: optionalNumber(dbRaw, `envs.${name}.db.exposeOnLoopback`)
37192
37266
  };
37193
37267
  } else {
37194
37268
  throw new Error(`deploy.arc.json: envs.${name}.db.type must be "sqlite" or "postgres" (got "${dbType}")`);
@@ -39069,7 +39143,7 @@ class ContextHandler {
39069
39143
  console.log(`[ARC:Seed] Seeded ${data.length} row(s) into ${tableName}`);
39070
39144
  }
39071
39145
  }
39072
- async executeCommand(commandName, params, rawToken) {
39146
+ async executeCommand(commandName, params, rawToken, options) {
39073
39147
  const includePayloads = this.telemetry?.shouldIncludePayloads() ?? false;
39074
39148
  const baseAttrs = {
39075
39149
  "rpc.system": "arc",
@@ -39094,6 +39168,9 @@ class ContextHandler {
39094
39168
  if (!command) {
39095
39169
  throw new Error(`Command '${commandName}' not found`);
39096
39170
  }
39171
+ if (!options?.internal && command.__isPrivate) {
39172
+ throw new Error(`Command '${commandName}' is private and not callable from a client.`);
39173
+ }
39097
39174
  try {
39098
39175
  return await command(params);
39099
39176
  } catch (error) {
@@ -39701,7 +39778,7 @@ class CronScheduler {
39701
39778
  console.log(`[ARC:Cron] ${commandName}: executing for ${instances.length} instance(s)...`);
39702
39779
  for (const instance of instances) {
39703
39780
  try {
39704
- await this.contextHandler.executeCommand(commandName, { _id: instance._id }, null);
39781
+ await this.contextHandler.executeCommand(commandName, { _id: instance._id }, null, { internal: true });
39705
39782
  } catch (error) {
39706
39783
  console.error(`[ARC:Cron] ${commandName} failed for instance ${instance._id}:`, error);
39707
39784
  }
@@ -40585,7 +40662,7 @@ function staticFilesHandler(ws, devMode, getManifest) {
40585
40662
  return null;
40586
40663
  };
40587
40664
  }
40588
- function apiEndpointsHandler(ws, getManifest, cm, moduleAccessMap) {
40665
+ function apiEndpointsHandler(ws, getManifest, cm, moduleAccessMap, telemetry) {
40589
40666
  return (req, url, ctx) => {
40590
40667
  if (url.pathname === "/api/modules") {
40591
40668
  const arcTokensHeader = req.headers.get("X-Arc-Tokens");
@@ -40605,7 +40682,11 @@ function apiEndpointsHandler(ws, getManifest, cm, moduleAccessMap) {
40605
40682
  return Response.json({
40606
40683
  status: "ok",
40607
40684
  groups: Object.keys(getManifest().groups).length,
40608
- clients: cm?.clientCount ?? 0
40685
+ clients: cm?.clientCount ?? 0,
40686
+ otel: {
40687
+ enabled: telemetry?.config.enabled ?? false,
40688
+ active: telemetry?.active ?? false
40689
+ }
40609
40690
  }, { headers: ctx.corsHeaders });
40610
40691
  }
40611
40692
  return null;
@@ -40656,7 +40737,8 @@ async function startPlatformServer(opts) {
40656
40737
  environment: "server",
40657
40738
  endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
40658
40739
  mode: devMode ? "development" : "production",
40659
- sampleRate: devMode ? 1 : 1
40740
+ sampleRate: devMode ? 1 : 1,
40741
+ debug: process.env.ARC_OTEL_DEBUG === "true"
40660
40742
  });
40661
40743
  telemetry = init2.telemetry;
40662
40744
  telemetryShutdown = init2.shutdown;
@@ -40706,7 +40788,7 @@ async function startPlatformServer(opts) {
40706
40788
  corsHeaders: cors
40707
40789
  };
40708
40790
  const handlers = [
40709
- apiEndpointsHandler(ws, getManifest, null, moduleAccessMap),
40791
+ apiEndpointsHandler(ws, getManifest, null, moduleAccessMap, telemetry),
40710
40792
  devReloadHandler(sseClients),
40711
40793
  staticFilesHandler(ws, !!devMode, getManifest),
40712
40794
  spaFallbackHandler(getShellHtml)
@@ -40744,7 +40826,7 @@ async function startPlatformServer(opts) {
40744
40826
  dbAdapterFactory,
40745
40827
  port,
40746
40828
  httpHandlers: [
40747
- apiEndpointsHandler(ws, getManifest, null, moduleAccessMap),
40829
+ apiEndpointsHandler(ws, getManifest, null, moduleAccessMap, telemetry),
40748
40830
  devReloadHandler(sseClients),
40749
40831
  staticFilesHandler(ws, !!devMode, getManifest),
40750
40832
  spaFallbackHandler(getShellHtml)
@@ -40788,16 +40870,35 @@ async function startPlatform(opts) {
40788
40870
  } else {
40789
40871
  log2("No context \u2014 server endpoints skipped");
40790
40872
  }
40791
- const platform3 = await startPlatformServer({
40792
- ws,
40793
- port,
40794
- manifest,
40795
- context: context2,
40796
- moduleAccess,
40797
- dbPath,
40798
- devMode
40799
- });
40800
- ok(`Server on http://localhost:${port}`);
40873
+ const MAX_PORT_ATTEMPTS = devMode ? 20 : 1;
40874
+ let platform3 = null;
40875
+ let actualPort = port;
40876
+ for (let i = 0;i < MAX_PORT_ATTEMPTS; i++) {
40877
+ try {
40878
+ platform3 = await startPlatformServer({
40879
+ ws,
40880
+ port: actualPort,
40881
+ manifest,
40882
+ context: context2,
40883
+ moduleAccess,
40884
+ dbPath,
40885
+ devMode
40886
+ });
40887
+ break;
40888
+ } catch (e2) {
40889
+ const msg = e2.message || "";
40890
+ const inUse = msg.includes("EADDRINUSE") || msg.includes("address already in use") || msg.includes("in use");
40891
+ if (!inUse || i === MAX_PORT_ATTEMPTS - 1)
40892
+ throw e2;
40893
+ log2(`Port ${actualPort} in use, trying ${actualPort + 1}...`);
40894
+ actualPort++;
40895
+ }
40896
+ }
40897
+ if (!platform3) {
40898
+ err(`Failed to bind a free port starting from ${port}`);
40899
+ process.exit(1);
40900
+ }
40901
+ ok(`Server on http://localhost:${actualPort}`);
40801
40902
  if (platform3.contextHandler) {
40802
40903
  ok("Commands, queries, WebSocket \u2014 all on same port");
40803
40904
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcote.tech/arc-cli",
3
- "version": "0.7.7",
3
+ "version": "0.7.9",
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.7",
16
- "@arcote.tech/arc-ds": "^0.7.7",
17
- "@arcote.tech/arc-react": "^0.7.7",
18
- "@arcote.tech/arc-host": "^0.7.7",
19
- "@arcote.tech/arc-adapter-db-sqlite": "^0.7.7",
20
- "@arcote.tech/arc-adapter-db-postgres": "^0.7.7",
21
- "@arcote.tech/arc-otel": "^0.7.7",
15
+ "@arcote.tech/arc": "^0.7.9",
16
+ "@arcote.tech/arc-ds": "^0.7.9",
17
+ "@arcote.tech/arc-react": "^0.7.9",
18
+ "@arcote.tech/arc-host": "^0.7.9",
19
+ "@arcote.tech/arc-adapter-db-sqlite": "^0.7.9",
20
+ "@arcote.tech/arc-adapter-db-postgres": "^0.7.9",
21
+ "@arcote.tech/arc-otel": "^0.7.9",
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.7",
34
+ "@arcote.tech/platform": "^0.7.9",
35
35
  "@clack/prompts": "^0.9.0",
36
36
  "commander": "^11.1.0",
37
37
  "chokidar": "^3.5.3",
@@ -79,11 +79,27 @@ export function collectFrameworkDeps(
79
79
  `[arc-otel] could not resolve @arcote.tech/arc-otel — image will run without telemetry deps: ${(e as Error).message}`,
80
80
  );
81
81
  }
82
+ // Pole `arc` z root package.json (translations, theme, manifest...) musi
83
+ // trafić do obrazu runtime — `readTranslationsConfig` w server.ts czyta je
84
+ // z `ws.rootDir/package.json` (czyli /app/package.json w obrazie). Bez
85
+ // przepisania `/api/translations` zwraca pustą listę locale, `useI18nConfig`
86
+ // w PlatformApp nie ustawia stanu, `I18nProvider` nie zostaje mountowany
87
+ // i każde `useI18n()` wyrzuca błąd na produkcji.
88
+ let rootArc: unknown;
89
+ const rootPkgPath = join(rootDir, "package.json");
90
+ if (existsSync(rootPkgPath)) {
91
+ try {
92
+ const rootPkg = JSON.parse(readFileSync(rootPkgPath, "utf-8")) as Record<string, unknown>;
93
+ if (rootPkg.arc && typeof rootPkg.arc === "object") rootArc = rootPkg.arc;
94
+ } catch {}
95
+ }
96
+
82
97
  const manifest = {
83
98
  name: "arc-platform-framework",
84
99
  private: true,
85
100
  type: "module" as const,
86
101
  dependencies: versions,
102
+ ...(rootArc ? { arc: rootArc } : {}),
87
103
  };
88
104
  const manifestPath = join(arcDir, "package.json");
89
105
  writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
@@ -722,8 +722,11 @@ const TAILWIND_INPUT_TEMPLATE = (rootRel: string) => `@import "tailwindcss";
722
722
 
723
723
  @source "${rootRel}/packages/*/*.{ts,tsx}";
724
724
  @source "${rootRel}/packages/*/src/**/*.{ts,tsx}";
725
- @source "${rootRel}/node_modules/@arcote.tech/platform/src/**/*.{ts,tsx}";
726
- @source "${rootRel}/node_modules/@arcote.tech/arc-ds/src/**/*.{ts,tsx}";
725
+ /* Skanuj wszystkie framework packages — fragmenty (arc-chat, arc-ai-voice…)
726
+ wnoszą własne komponenty React z klasami Tailwind, które muszą trafić do
727
+ CSS. Bez tego absolute positioning, color/border klasy w nowych fragmentach
728
+ są ignorowane — komponent renderuje się bez stylów. */
729
+ @source "${rootRel}/node_modules/@arcote.tech/*/src/**/*.{ts,tsx}";
727
730
 
728
731
  @custom-variant dark (&:is(.dark *));
729
732
 
@@ -164,8 +164,15 @@ export function generateCompose({ cfg }: ComposeOptions): string {
164
164
  lines.push(" retries: 20");
165
165
  lines.push(" networks:");
166
166
  lines.push(" - arc-net");
167
- // No `ports:` — the database is only reachable from other containers
168
- // on `arc-net`. Exposing 5432 to the host would defeat the isolation.
167
+ // `ports:` is omitted by default — the database is only reachable from
168
+ // other containers on `arc-net`. When `exposeOnLoopback` is set in
169
+ // deploy.arc.json, bind to `127.0.0.1:<n>:5432` on the host so SSH
170
+ // tunneling (DBeaver/psql via `ssh -L`) works without exposing the
171
+ // port to the public internet (ufw only opens 22/80/443).
172
+ if (env.db.exposeOnLoopback !== undefined) {
173
+ lines.push(" ports:");
174
+ lines.push(` - "127.0.0.1:${env.db.exposeOnLoopback}:5432"`);
175
+ }
169
176
  lines.push("");
170
177
  }
171
178
 
@@ -31,7 +31,21 @@ export interface DeployTarget {
31
31
  */
32
32
  export type DeployEnvDb =
33
33
  | { type: "sqlite" }
34
- | { type: "postgres"; image?: string };
34
+ | {
35
+ type: "postgres";
36
+ image?: string;
37
+ /**
38
+ * Bind the postgres port to `127.0.0.1:<n>` on the host. Container
39
+ * pozostaje niewystawiony do internetu (ufw przepuszcza tylko
40
+ * 22/80/443), ale staje się dostępny przez SSH tunnel
41
+ * (`ssh -L <n>:localhost:<n> deploy@host`) np. dla DBeavera.
42
+ *
43
+ * Per-env różne porty unikają kolizji gdy kilka środowisk dzieli
44
+ * jeden VPS. Pomiń pole żeby zachować pełną izolację (tylko docker
45
+ * network).
46
+ */
47
+ exposeOnLoopback?: number;
48
+ };
35
49
 
36
50
  export interface DeployEnv {
37
51
  /** Subdomain or full domain routed by Caddy. */
@@ -301,6 +315,10 @@ export function validateDeployConfig(input: unknown): DeployConfig {
301
315
  db = {
302
316
  type: "postgres",
303
317
  image: optionalString(dbRaw, `envs.${name}.db.image`),
318
+ exposeOnLoopback: optionalNumber(
319
+ dbRaw,
320
+ `envs.${name}.db.exposeOnLoopback`,
321
+ ),
304
322
  };
305
323
  } else {
306
324
  throw new Error(
@@ -13,9 +13,11 @@ import type { DeployConfig, DeployObservability } from "./config";
13
13
  // - logs: 7d retention (Loki chunks on local disk)
14
14
  // - metrics: 30d retention (Prometheus TSDB on local disk)
15
15
  //
16
- // Tail sampling: every error + every span >500ms + 10% random. Decided in
17
- // the collector so per-service SDKs can be left at always-on without
18
- // flooding the backend.
16
+ // Tail sampling: every error + every span >500ms + 100% random przy obecnej
17
+ // skali. Tail_sampling matchuje OR po policies bez "always" policy WSZYSTKIE
18
+ // OK traces byłyby droppowane (errors + slow nie obejmują happy path).
19
+ // Obniż `random_100pct` w `tail_sampling.policies` gdy backend zacznie się
20
+ // zatkać; do tego momentu lepiej widzieć wszystko.
19
21
  // ---------------------------------------------------------------------------
20
22
 
21
23
  const DEFAULT_RETENTION = {
@@ -60,7 +62,10 @@ processors:
60
62
  send_batch_max_size: 1024
61
63
 
62
64
  # Tail-based sampling — applied after a full trace has been assembled.
63
- # Errors and slow traces are kept 100%, everything else at 10%.
65
+ # Errors + slow traces zachowywane w 100%, normalne traces również 100%
66
+ # przy obecnej skali (boostrap produkcji). Tail sampling matchuje OR po
67
+ # policies — bez "always" policy WSZYSTKIE OK traces byłyby droppowane.
68
+ # Obniż 'random_100pct' do np. 10% gdy ruch eksploduje.
64
69
  tail_sampling:
65
70
  decision_wait: 10s
66
71
  num_traces: 50000
@@ -72,9 +77,9 @@ processors:
72
77
  - name: slow
73
78
  type: latency
74
79
  latency: { threshold_ms: 500 }
75
- - name: random_10pct
80
+ - name: random_100pct
76
81
  type: probabilistic
77
- probabilistic: { sampling_percentage: 10 }
82
+ probabilistic: { sampling_percentage: 100 }
78
83
 
79
84
  # Drop high-cardinality / PII attributes that might slip past app-side
80
85
  # sanitization. Belt-and-suspenders before they hit long-term storage.
@@ -415,6 +415,7 @@ function apiEndpointsHandler(
415
415
  getManifest: () => BuildManifest,
416
416
  cm: ConnectionManager | null,
417
417
  moduleAccessMap: Map<string, ModuleAccess>,
418
+ telemetry?: import("@arcote.tech/arc-otel").ArcTelemetry,
418
419
  ): ArcHttpHandler {
419
420
  return (req, url, ctx) => {
420
421
  if (url.pathname === "/api/modules") {
@@ -442,6 +443,16 @@ function apiEndpointsHandler(
442
443
  status: "ok",
443
444
  groups: Object.keys(getManifest().groups).length,
444
445
  clients: cm?.clientCount ?? 0,
446
+ otel: {
447
+ // `enabled` — `ARC_OTEL_ENABLED` w env i init przeszedł
448
+ // (telemetry obiekt istnieje, config.enabled === true).
449
+ // `active` — to samo + `attach()` wywołane (tracer ustawiony).
450
+ // Rozjazd między tymi flagami zwykle oznacza że
451
+ // `initServerTelemetry` przeszło na wczesny return z
452
+ // `config.enabled === false`.
453
+ enabled: telemetry?.config.enabled ?? false,
454
+ active: telemetry?.active ?? false,
455
+ },
445
456
  },
446
457
  { headers: ctx.corsHeaders },
447
458
  );
@@ -510,6 +521,7 @@ export async function startPlatformServer(
510
521
  endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
511
522
  mode: devMode ? "development" : "production",
512
523
  sampleRate: devMode ? 1.0 : 1.0, // head-based 100%, collector tail-samples
524
+ debug: process.env.ARC_OTEL_DEBUG === "true",
513
525
  });
514
526
  telemetry = init.telemetry;
515
527
  telemetryShutdown = init.shutdown;
@@ -572,7 +584,7 @@ export async function startPlatformServer(
572
584
 
573
585
  // Platform handlers only
574
586
  const handlers: ArcHttpHandler[] = [
575
- apiEndpointsHandler(ws, getManifest, null, moduleAccessMap),
587
+ apiEndpointsHandler(ws, getManifest, null, moduleAccessMap, telemetry),
576
588
  devReloadHandler(sseClients),
577
589
  staticFilesHandler(ws, !!devMode, getManifest),
578
590
  spaFallbackHandler(getShellHtml),
@@ -626,7 +638,7 @@ export async function startPlatformServer(
626
638
  port,
627
639
  httpHandlers: [
628
640
  // Platform-specific handlers (checked AFTER arc handlers)
629
- apiEndpointsHandler(ws, getManifest, null, moduleAccessMap),
641
+ apiEndpointsHandler(ws, getManifest, null, moduleAccessMap, telemetry),
630
642
  devReloadHandler(sseClients),
631
643
  staticFilesHandler(ws, !!devMode, getManifest),
632
644
  spaFallbackHandler(getShellHtml),
@@ -74,17 +74,39 @@ export async function startPlatform(
74
74
 
75
75
  // 3. Start the platform server. Cache headers + SSE behaviour are
76
76
  // controlled inside the server by `devMode`; we just pass the flag.
77
- const platform = await startPlatformServer({
78
- ws,
79
- port,
80
- manifest,
81
- context,
82
- moduleAccess,
83
- dbPath,
84
- devMode,
85
- });
77
+ // In dev mode, if the port is busy, try incrementing up to 20 times.
78
+ const MAX_PORT_ATTEMPTS = devMode ? 20 : 1;
79
+ let platform: PlatformServer | null = null;
80
+ let actualPort = port;
81
+ for (let i = 0; i < MAX_PORT_ATTEMPTS; i++) {
82
+ try {
83
+ platform = await startPlatformServer({
84
+ ws,
85
+ port: actualPort,
86
+ manifest,
87
+ context,
88
+ moduleAccess,
89
+ dbPath,
90
+ devMode,
91
+ });
92
+ break;
93
+ } catch (e) {
94
+ const msg = (e as Error).message || "";
95
+ const inUse =
96
+ msg.includes("EADDRINUSE") ||
97
+ msg.includes("address already in use") ||
98
+ msg.includes("in use");
99
+ if (!inUse || i === MAX_PORT_ATTEMPTS - 1) throw e;
100
+ log(`Port ${actualPort} in use, trying ${actualPort + 1}...`);
101
+ actualPort++;
102
+ }
103
+ }
104
+ if (!platform) {
105
+ err(`Failed to bind a free port starting from ${port}`);
106
+ process.exit(1);
107
+ }
86
108
 
87
- ok(`Server on http://localhost:${port}`);
109
+ ok(`Server on http://localhost:${actualPort}`);
88
110
  if (platform.contextHandler) {
89
111
  ok("Commands, queries, WebSocket — all on same port");
90
112
  }