@camstack/addon-cloudflare 1.0.2 → 1.0.4

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.
@@ -5130,14 +5130,15 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
5130
5130
  EventCategory["DeviceSleeping"] = "device.sleeping";
5131
5131
  EventCategory["RetentionCleanup"] = "retention.cleanup";
5132
5132
  /**
5133
- * Progress snapshot emitted by `BulkUpdateCoordinator` on every state
5134
- * transition (item status change, phase change, completion, cancel).
5135
- * Payload is `BulkUpdateState`. Admin UI subscribes via `useLiveEvent`
5136
- * to drive the sticky `BulkUpdateBanner` and per-row `AddonRowBadge`.
5137
- *
5138
- * Spec: docs/superpowers/specs/2026-05-21-addons-bulk-update-progress-design.md
5133
+ * Legacy bulk-update progress snapshot (payload `BulkUpdateState`). No longer
5134
+ * emitted F3 removed the coordinator that produced it; "Update all" now runs
5135
+ * as one lifecycle engine job (`AddonsJobProgress`/`AddonsJobLog`). Retained
5136
+ * (with `BulkUpdateState`) only to avoid regenerating the event maps; removed
5137
+ * in F4 once live bulk progress is re-implemented over the engine events.
5139
5138
  */
5140
5139
  EventCategory["AddonsBulkUpdateProgress"] = "addons.bulk-update-progress";
5140
+ EventCategory["AddonsJobProgress"] = "addons.job-progress";
5141
+ EventCategory["AddonsJobLog"] = "addons.job-log";
5141
5142
  /**
5142
5143
  * A container's child visibility toggled (hidden/shown). Emitted by the
5143
5144
  * `accessories` cap when a child device is hidden or revealed.
@@ -16018,6 +16019,69 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
16018
16019
  kind: "mutation",
16019
16020
  auth: "admin"
16020
16021
  });
16022
+ var jobKindSchema = _enum([
16023
+ "install",
16024
+ "update",
16025
+ "uninstall",
16026
+ "restart"
16027
+ ]);
16028
+ var taskPhaseSchema = _enum([
16029
+ "queued",
16030
+ "fetching",
16031
+ "staged",
16032
+ "validating",
16033
+ "applying",
16034
+ "restarting",
16035
+ "applied",
16036
+ "done",
16037
+ "failed",
16038
+ "skipped"
16039
+ ]);
16040
+ var taskTargetSchema = _enum(["framework", "addon"]);
16041
+ var taskLogEntrySchema = object({
16042
+ tsMs: number(),
16043
+ nodeId: string(),
16044
+ packageName: string(),
16045
+ phase: taskPhaseSchema,
16046
+ message: string()
16047
+ });
16048
+ var lifecycleTaskSchema = object({
16049
+ taskId: string(),
16050
+ nodeId: string(),
16051
+ packageName: string(),
16052
+ fromVersion: string().nullable(),
16053
+ toVersion: string(),
16054
+ target: taskTargetSchema,
16055
+ phase: taskPhaseSchema,
16056
+ stagedPath: string().nullable(),
16057
+ attempts: number(),
16058
+ steps: array(taskLogEntrySchema),
16059
+ error: string().nullable(),
16060
+ startedAtMs: number().nullable(),
16061
+ finishedAtMs: number().nullable()
16062
+ });
16063
+ var lifecycleJobStateSchema = _enum([
16064
+ "running",
16065
+ "completed",
16066
+ "failed",
16067
+ "partially-failed",
16068
+ "cancelled"
16069
+ ]);
16070
+ var lifecycleJobScopeSchema = _enum([
16071
+ "single",
16072
+ "bulk",
16073
+ "cluster"
16074
+ ]);
16075
+ var lifecycleJobSchema = object({
16076
+ jobId: string(),
16077
+ kind: jobKindSchema,
16078
+ createdAtMs: number(),
16079
+ createdBy: string(),
16080
+ scope: lifecycleJobScopeSchema,
16081
+ tasks: array(lifecycleTaskSchema),
16082
+ state: lifecycleJobStateSchema,
16083
+ schemaVersion: literal(1)
16084
+ });
16021
16085
  /**
16022
16086
  * addons — system-scoped singleton capability for addon package
16023
16087
  * management (install, update, configure, restart) and per-addon log
@@ -16200,7 +16264,7 @@ var BulkUpdatePhaseSchema = _enum([
16200
16264
  "restarting",
16201
16265
  "finalizing"
16202
16266
  ]);
16203
- var BulkUpdateStateSchema = object({
16267
+ object({
16204
16268
  id: string(),
16205
16269
  nodeId: string(),
16206
16270
  startedAtMs: number(),
@@ -16303,20 +16367,7 @@ method(_void(), array(AddonListItemSchema).readonly()), method(object({
16303
16367
  }), UpdateFrameworkPackageResultSchema, {
16304
16368
  kind: "mutation",
16305
16369
  auth: "admin"
16306
- }), method(object({
16307
- nodeId: string(),
16308
- items: array(object({
16309
- name: string(),
16310
- version: string(),
16311
- isSystem: boolean()
16312
- })).readonly()
16313
- }), object({ id: string() }), {
16314
- kind: "mutation",
16315
- auth: "admin"
16316
- }), method(object({ id: string() }), BulkUpdateStateSchema.nullable(), { auth: "admin" }), method(object({ id: string() }), object({ cancelled: boolean() }), {
16317
- kind: "mutation",
16318
- auth: "admin"
16319
- }), method(object({ nodeId: string().optional() }), array(BulkUpdateStateSchema).readonly(), { auth: "admin" }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({ addonId: string() }), RestartAddonResultSchema, {
16370
+ }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({ addonId: string() }), RestartAddonResultSchema, {
16320
16371
  kind: "mutation",
16321
16372
  auth: "admin"
16322
16373
  }), method(object({ packageName: string() }), object({ success: literal(true) }), {
@@ -16338,6 +16389,24 @@ method(_void(), array(AddonListItemSchema).readonly()), method(object({
16338
16389
  kind: "mutation",
16339
16390
  auth: "admin"
16340
16391
  }), method(CustomActionInputSchema, unknown(), { kind: "mutation" }), method(object({
16392
+ kind: _enum([
16393
+ "install",
16394
+ "update",
16395
+ "uninstall",
16396
+ "restart"
16397
+ ]),
16398
+ targets: array(object({
16399
+ name: string().min(1),
16400
+ version: string().min(1)
16401
+ })).min(1),
16402
+ nodeIds: array(string()).optional()
16403
+ }), object({ jobId: string() }), {
16404
+ kind: "mutation",
16405
+ auth: "admin"
16406
+ }), method(object({ jobId: string() }), lifecycleJobSchema.nullable(), { auth: "admin" }), method(object({ activeOnly: boolean().optional() }), array(lifecycleJobSchema), { auth: "admin" }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
16407
+ kind: "mutation",
16408
+ auth: "admin"
16409
+ }), method(object({
16341
16410
  addonId: string(),
16342
16411
  level: LogLevelSchema$1.optional()
16343
16412
  }), LogStreamEntrySchema, { kind: "subscription" });
@@ -16378,7 +16447,7 @@ Object.freeze({
16378
16447
  addonId: null,
16379
16448
  access: "create"
16380
16449
  },
16381
- "addons.cancelBulkUpdate": {
16450
+ "addons.cancelJob": {
16382
16451
  capName: "addons",
16383
16452
  capScope: "system",
16384
16453
  addonId: null,
@@ -16408,7 +16477,7 @@ Object.freeze({
16408
16477
  addonId: null,
16409
16478
  access: "view"
16410
16479
  },
16411
- "addons.getBulkUpdateState": {
16480
+ "addons.getJob": {
16412
16481
  capName: "addons",
16413
16482
  capScope: "system",
16414
16483
  addonId: null,
@@ -16456,19 +16525,19 @@ Object.freeze({
16456
16525
  addonId: null,
16457
16526
  access: "view"
16458
16527
  },
16459
- "addons.listActiveBulkUpdates": {
16528
+ "addons.listCapabilityProviders": {
16460
16529
  capName: "addons",
16461
16530
  capScope: "system",
16462
16531
  addonId: null,
16463
16532
  access: "view"
16464
16533
  },
16465
- "addons.listCapabilityProviders": {
16534
+ "addons.listFrameworkPackages": {
16466
16535
  capName: "addons",
16467
16536
  capScope: "system",
16468
16537
  addonId: null,
16469
16538
  access: "view"
16470
16539
  },
16471
- "addons.listFrameworkPackages": {
16540
+ "addons.listJobs": {
16472
16541
  capName: "addons",
16473
16542
  capScope: "system",
16474
16543
  addonId: null,
@@ -16552,7 +16621,7 @@ Object.freeze({
16552
16621
  addonId: null,
16553
16622
  access: "create"
16554
16623
  },
16555
- "addons.startBulkUpdate": {
16624
+ "addons.startJob": {
16556
16625
  capName: "addons",
16557
16626
  capScope: "system",
16558
16627
  addonId: null,
@@ -20539,6 +20608,32 @@ Object.freeze({
20539
20608
  "network-access": "ingress",
20540
20609
  "smtp-provider": "email"
20541
20610
  });
20611
+ var frameworkSwapPackageSchema = object({
20612
+ name: string(),
20613
+ stagedPath: string(),
20614
+ backupPath: string(),
20615
+ toVersion: string(),
20616
+ fromVersion: string().nullable()
20617
+ });
20618
+ object({
20619
+ jobId: string(),
20620
+ taskId: string(),
20621
+ packages: array(frameworkSwapPackageSchema),
20622
+ requestedAtMs: number(),
20623
+ schemaVersion: literal(1)
20624
+ });
20625
+ object({
20626
+ jobId: string(),
20627
+ taskId: string(),
20628
+ backups: array(object({
20629
+ name: string(),
20630
+ backupPath: string(),
20631
+ livePath: string()
20632
+ })),
20633
+ appliedAtMs: number(),
20634
+ bootAttempts: number(),
20635
+ schemaVersion: literal(1)
20636
+ });
20542
20637
  //#endregion
20543
20638
  Object.defineProperty(exports, "BaseAddon", {
20544
20639
  enumerable: true,
@@ -5130,14 +5130,15 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
5130
5130
  EventCategory["DeviceSleeping"] = "device.sleeping";
5131
5131
  EventCategory["RetentionCleanup"] = "retention.cleanup";
5132
5132
  /**
5133
- * Progress snapshot emitted by `BulkUpdateCoordinator` on every state
5134
- * transition (item status change, phase change, completion, cancel).
5135
- * Payload is `BulkUpdateState`. Admin UI subscribes via `useLiveEvent`
5136
- * to drive the sticky `BulkUpdateBanner` and per-row `AddonRowBadge`.
5137
- *
5138
- * Spec: docs/superpowers/specs/2026-05-21-addons-bulk-update-progress-design.md
5133
+ * Legacy bulk-update progress snapshot (payload `BulkUpdateState`). No longer
5134
+ * emitted F3 removed the coordinator that produced it; "Update all" now runs
5135
+ * as one lifecycle engine job (`AddonsJobProgress`/`AddonsJobLog`). Retained
5136
+ * (with `BulkUpdateState`) only to avoid regenerating the event maps; removed
5137
+ * in F4 once live bulk progress is re-implemented over the engine events.
5139
5138
  */
5140
5139
  EventCategory["AddonsBulkUpdateProgress"] = "addons.bulk-update-progress";
5140
+ EventCategory["AddonsJobProgress"] = "addons.job-progress";
5141
+ EventCategory["AddonsJobLog"] = "addons.job-log";
5141
5142
  /**
5142
5143
  * A container's child visibility toggled (hidden/shown). Emitted by the
5143
5144
  * `accessories` cap when a child device is hidden or revealed.
@@ -16018,6 +16019,69 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
16018
16019
  kind: "mutation",
16019
16020
  auth: "admin"
16020
16021
  });
16022
+ var jobKindSchema = _enum([
16023
+ "install",
16024
+ "update",
16025
+ "uninstall",
16026
+ "restart"
16027
+ ]);
16028
+ var taskPhaseSchema = _enum([
16029
+ "queued",
16030
+ "fetching",
16031
+ "staged",
16032
+ "validating",
16033
+ "applying",
16034
+ "restarting",
16035
+ "applied",
16036
+ "done",
16037
+ "failed",
16038
+ "skipped"
16039
+ ]);
16040
+ var taskTargetSchema = _enum(["framework", "addon"]);
16041
+ var taskLogEntrySchema = object({
16042
+ tsMs: number(),
16043
+ nodeId: string(),
16044
+ packageName: string(),
16045
+ phase: taskPhaseSchema,
16046
+ message: string()
16047
+ });
16048
+ var lifecycleTaskSchema = object({
16049
+ taskId: string(),
16050
+ nodeId: string(),
16051
+ packageName: string(),
16052
+ fromVersion: string().nullable(),
16053
+ toVersion: string(),
16054
+ target: taskTargetSchema,
16055
+ phase: taskPhaseSchema,
16056
+ stagedPath: string().nullable(),
16057
+ attempts: number(),
16058
+ steps: array(taskLogEntrySchema),
16059
+ error: string().nullable(),
16060
+ startedAtMs: number().nullable(),
16061
+ finishedAtMs: number().nullable()
16062
+ });
16063
+ var lifecycleJobStateSchema = _enum([
16064
+ "running",
16065
+ "completed",
16066
+ "failed",
16067
+ "partially-failed",
16068
+ "cancelled"
16069
+ ]);
16070
+ var lifecycleJobScopeSchema = _enum([
16071
+ "single",
16072
+ "bulk",
16073
+ "cluster"
16074
+ ]);
16075
+ var lifecycleJobSchema = object({
16076
+ jobId: string(),
16077
+ kind: jobKindSchema,
16078
+ createdAtMs: number(),
16079
+ createdBy: string(),
16080
+ scope: lifecycleJobScopeSchema,
16081
+ tasks: array(lifecycleTaskSchema),
16082
+ state: lifecycleJobStateSchema,
16083
+ schemaVersion: literal(1)
16084
+ });
16021
16085
  /**
16022
16086
  * addons — system-scoped singleton capability for addon package
16023
16087
  * management (install, update, configure, restart) and per-addon log
@@ -16200,7 +16264,7 @@ var BulkUpdatePhaseSchema = _enum([
16200
16264
  "restarting",
16201
16265
  "finalizing"
16202
16266
  ]);
16203
- var BulkUpdateStateSchema = object({
16267
+ object({
16204
16268
  id: string(),
16205
16269
  nodeId: string(),
16206
16270
  startedAtMs: number(),
@@ -16303,20 +16367,7 @@ method(_void(), array(AddonListItemSchema).readonly()), method(object({
16303
16367
  }), UpdateFrameworkPackageResultSchema, {
16304
16368
  kind: "mutation",
16305
16369
  auth: "admin"
16306
- }), method(object({
16307
- nodeId: string(),
16308
- items: array(object({
16309
- name: string(),
16310
- version: string(),
16311
- isSystem: boolean()
16312
- })).readonly()
16313
- }), object({ id: string() }), {
16314
- kind: "mutation",
16315
- auth: "admin"
16316
- }), method(object({ id: string() }), BulkUpdateStateSchema.nullable(), { auth: "admin" }), method(object({ id: string() }), object({ cancelled: boolean() }), {
16317
- kind: "mutation",
16318
- auth: "admin"
16319
- }), method(object({ nodeId: string().optional() }), array(BulkUpdateStateSchema).readonly(), { auth: "admin" }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({ addonId: string() }), RestartAddonResultSchema, {
16370
+ }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({ addonId: string() }), RestartAddonResultSchema, {
16320
16371
  kind: "mutation",
16321
16372
  auth: "admin"
16322
16373
  }), method(object({ packageName: string() }), object({ success: literal(true) }), {
@@ -16338,6 +16389,24 @@ method(_void(), array(AddonListItemSchema).readonly()), method(object({
16338
16389
  kind: "mutation",
16339
16390
  auth: "admin"
16340
16391
  }), method(CustomActionInputSchema, unknown(), { kind: "mutation" }), method(object({
16392
+ kind: _enum([
16393
+ "install",
16394
+ "update",
16395
+ "uninstall",
16396
+ "restart"
16397
+ ]),
16398
+ targets: array(object({
16399
+ name: string().min(1),
16400
+ version: string().min(1)
16401
+ })).min(1),
16402
+ nodeIds: array(string()).optional()
16403
+ }), object({ jobId: string() }), {
16404
+ kind: "mutation",
16405
+ auth: "admin"
16406
+ }), method(object({ jobId: string() }), lifecycleJobSchema.nullable(), { auth: "admin" }), method(object({ activeOnly: boolean().optional() }), array(lifecycleJobSchema), { auth: "admin" }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
16407
+ kind: "mutation",
16408
+ auth: "admin"
16409
+ }), method(object({
16341
16410
  addonId: string(),
16342
16411
  level: LogLevelSchema$1.optional()
16343
16412
  }), LogStreamEntrySchema, { kind: "subscription" });
@@ -16378,7 +16447,7 @@ Object.freeze({
16378
16447
  addonId: null,
16379
16448
  access: "create"
16380
16449
  },
16381
- "addons.cancelBulkUpdate": {
16450
+ "addons.cancelJob": {
16382
16451
  capName: "addons",
16383
16452
  capScope: "system",
16384
16453
  addonId: null,
@@ -16408,7 +16477,7 @@ Object.freeze({
16408
16477
  addonId: null,
16409
16478
  access: "view"
16410
16479
  },
16411
- "addons.getBulkUpdateState": {
16480
+ "addons.getJob": {
16412
16481
  capName: "addons",
16413
16482
  capScope: "system",
16414
16483
  addonId: null,
@@ -16456,19 +16525,19 @@ Object.freeze({
16456
16525
  addonId: null,
16457
16526
  access: "view"
16458
16527
  },
16459
- "addons.listActiveBulkUpdates": {
16528
+ "addons.listCapabilityProviders": {
16460
16529
  capName: "addons",
16461
16530
  capScope: "system",
16462
16531
  addonId: null,
16463
16532
  access: "view"
16464
16533
  },
16465
- "addons.listCapabilityProviders": {
16534
+ "addons.listFrameworkPackages": {
16466
16535
  capName: "addons",
16467
16536
  capScope: "system",
16468
16537
  addonId: null,
16469
16538
  access: "view"
16470
16539
  },
16471
- "addons.listFrameworkPackages": {
16540
+ "addons.listJobs": {
16472
16541
  capName: "addons",
16473
16542
  capScope: "system",
16474
16543
  addonId: null,
@@ -16552,7 +16621,7 @@ Object.freeze({
16552
16621
  addonId: null,
16553
16622
  access: "create"
16554
16623
  },
16555
- "addons.startBulkUpdate": {
16624
+ "addons.startJob": {
16556
16625
  capName: "addons",
16557
16626
  capScope: "system",
16558
16627
  addonId: null,
@@ -20539,5 +20608,31 @@ Object.freeze({
20539
20608
  "network-access": "ingress",
20540
20609
  "smtp-provider": "email"
20541
20610
  });
20611
+ var frameworkSwapPackageSchema = object({
20612
+ name: string(),
20613
+ stagedPath: string(),
20614
+ backupPath: string(),
20615
+ toVersion: string(),
20616
+ fromVersion: string().nullable()
20617
+ });
20618
+ object({
20619
+ jobId: string(),
20620
+ taskId: string(),
20621
+ packages: array(frameworkSwapPackageSchema),
20622
+ requestedAtMs: number(),
20623
+ schemaVersion: literal(1)
20624
+ });
20625
+ object({
20626
+ jobId: string(),
20627
+ taskId: string(),
20628
+ backups: array(object({
20629
+ name: string(),
20630
+ backupPath: string(),
20631
+ livePath: string()
20632
+ })),
20633
+ appliedAtMs: number(),
20634
+ bootAttempts: number(),
20635
+ schemaVersion: literal(1)
20636
+ });
20542
20637
  //#endregion
20543
20638
  export { networkAccessCapability as a, array as c, literal as d, number as f, defineCustomActions as i, boolean as l, string as m, EventCategory as n, turnProviderCapability as o, object as p, customAction as r, _enum as s, BaseAddon as t, discriminatedUnion as u };
@@ -1,7 +1,38 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_dist = require("../dist-FQAxmoZY.js");
2
+ //#region \0rolldown/runtime.js
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
13
+ get: ((k) => from[k]).bind(null, key),
14
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
+ });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
20
+ value: mod,
21
+ enumerable: true
22
+ }) : target, mod));
23
+ //#endregion
24
+ const require_dist = require("../dist-BaLJK4dE.js");
25
+ let node_path = require("node:path");
26
+ node_path = __toESM(node_path);
3
27
  let node_crypto = require("node:crypto");
4
28
  let node_child_process = require("node:child_process");
29
+ let node_fs = require("node:fs");
30
+ node_fs = __toESM(node_fs);
31
+ let node_fs_promises = require("node:fs/promises");
32
+ node_fs_promises = __toESM(node_fs_promises);
33
+ let node_os = require("node:os");
34
+ node_os = __toESM(node_os);
35
+ let node_stream_promises = require("node:stream/promises");
5
36
  //#region src/tunnel/cloudflare-tunnel.ts
6
37
  /**
7
38
  * Direct child_process.spawn() driver for the `cloudflared` binary.
@@ -23,19 +54,23 @@ var CloudflareTunnelService = class CloudflareTunnelService {
23
54
  config;
24
55
  logger;
25
56
  eventBus;
57
+ resolveCloudflaredBin;
26
58
  id = "cloudflare-tunnel";
27
59
  type = "cloudflare";
28
60
  endpoint = null;
29
61
  lastError;
30
62
  child = null;
63
+ /** Resolved cloudflared path/command (set on first start, reused on restart). */
64
+ cloudflaredBin = "cloudflared";
31
65
  restartCount = 0;
32
66
  intentionalStop = false;
33
67
  static MAX_RESTARTS = 5;
34
68
  static STOP_GRACE_MS = 5e3;
35
- constructor(config, logger, eventBus) {
69
+ constructor(config, logger, eventBus, resolveCloudflaredBin = async () => "cloudflared") {
36
70
  this.config = config;
37
71
  this.logger = logger;
38
72
  this.eventBus = eventBus;
73
+ this.resolveCloudflaredBin = resolveCloudflaredBin;
39
74
  }
40
75
  async start() {
41
76
  this.logger.info("Starting Cloudflare tunnel", {
@@ -76,6 +111,18 @@ var CloudflareTunnelService = class CloudflareTunnelService {
76
111
  this.intentionalStop = false;
77
112
  this.restartCount = 0;
78
113
  this.lastError = void 0;
114
+ try {
115
+ this.cloudflaredBin = await this.resolveCloudflaredBin();
116
+ } catch (err) {
117
+ this.logger.error("Failed to provision cloudflared binary", {
118
+ meta: { error: err instanceof Error ? err.message : String(err) },
119
+ tags: {
120
+ topic: "tunnel",
121
+ phase: "provision-error"
122
+ }
123
+ });
124
+ throw err;
125
+ }
79
126
  this.spawnChild();
80
127
  const placeholderHost = this.config.mode === "custom" ? this.config.customHostname : "pending.trycloudflare.com";
81
128
  this.endpoint = {
@@ -192,7 +239,7 @@ var CloudflareTunnelService = class CloudflareTunnelService {
192
239
  const args = [...debugFlags, ...tunnelArgs];
193
240
  let child;
194
241
  try {
195
- child = (0, node_child_process.spawn)("cloudflared", args, { stdio: [
242
+ child = (0, node_child_process.spawn)(this.cloudflaredBin, args, { stdio: [
196
243
  "ignore",
197
244
  "pipe",
198
245
  "pipe"
@@ -340,6 +387,130 @@ var CloudflareTunnelService = class CloudflareTunnelService {
340
387
  });
341
388
  }
342
389
  };
390
+ var RELEASE_BASE = "https://github.com/cloudflare/cloudflared/releases/download";
391
+ /**
392
+ * Map a Node `platform`/`arch` to the matching cloudflared release asset.
393
+ * Pure + exported for testing. Throws on an unsupported combination.
394
+ */
395
+ function cloudflaredAsset(platform, arch) {
396
+ const a = arch === "x64" ? "amd64" : arch === "arm64" ? "arm64" : arch === "arm" ? "arm" : null;
397
+ if (a === null) throw new Error(`cloudflared: unsupported arch '${arch}'`);
398
+ switch (platform) {
399
+ case "linux": return {
400
+ fileName: `cloudflared-linux-${a}`,
401
+ isArchive: false
402
+ };
403
+ case "darwin": return {
404
+ fileName: `cloudflared-darwin-${a === "arm64" ? "amd64" : a}.tgz`,
405
+ isArchive: true
406
+ };
407
+ case "win32": return {
408
+ fileName: `cloudflared-windows-${a}.exe`,
409
+ isArchive: false
410
+ };
411
+ default: throw new Error(`cloudflared: unsupported platform '${platform}'`);
412
+ }
413
+ }
414
+ /** Resolve a working `cloudflared` from PATH, or null if not runnable. */
415
+ async function cloudflaredOnPath() {
416
+ return await runsOk("cloudflared") ? "cloudflared" : null;
417
+ }
418
+ /** True when `<bin> --version` exits 0 (binary present + executable). */
419
+ function runsOk(bin) {
420
+ return new Promise((resolve) => {
421
+ let done = false;
422
+ const finish = (ok) => {
423
+ if (!done) {
424
+ done = true;
425
+ resolve(ok);
426
+ }
427
+ };
428
+ try {
429
+ const child = (0, node_child_process.spawn)(bin, ["--version"], { stdio: "ignore" });
430
+ child.on("error", () => finish(false));
431
+ child.on("exit", (code) => finish(code === 0));
432
+ setTimeout(() => {
433
+ child.kill("SIGKILL");
434
+ finish(false);
435
+ }, 5e3);
436
+ } catch {
437
+ finish(false);
438
+ }
439
+ });
440
+ }
441
+ async function downloadTo(url, dest) {
442
+ const res = await fetch(url, { redirect: "follow" });
443
+ if (!res.ok || !res.body) throw new Error(`cloudflared download failed: HTTP ${res.status} for ${url}`);
444
+ const tmp = `${dest}.partial`;
445
+ await (0, node_stream_promises.pipeline)(res.body, node_fs.createWriteStream(tmp));
446
+ await node_fs_promises.rename(tmp, dest);
447
+ }
448
+ /** Extract the `cloudflared` entry from a downloaded .tgz into `destBin`. */
449
+ async function extractArchive(archivePath, destBin, logger) {
450
+ const dir = node_path.dirname(destBin);
451
+ await new Promise((resolve, reject) => {
452
+ const child = (0, node_child_process.spawn)("tar", [
453
+ "-xzf",
454
+ archivePath,
455
+ "-C",
456
+ dir
457
+ ], { stdio: "inherit" });
458
+ child.on("error", reject);
459
+ child.on("exit", (code) => code === 0 ? resolve() : reject(/* @__PURE__ */ new Error(`tar exited ${code}`)));
460
+ });
461
+ if (!node_fs.existsSync(destBin)) logger.warn("cloudflared archive did not yield expected binary name", {
462
+ meta: { destBin },
463
+ tags: {
464
+ topic: "tunnel",
465
+ phase: "extract"
466
+ }
467
+ });
468
+ await node_fs_promises.rm(archivePath, { force: true });
469
+ }
470
+ /**
471
+ * Return a path/command for a runnable `cloudflared`, downloading + caching it
472
+ * on first use. Prefers an existing PATH install; otherwise caches under
473
+ * `binDir`. Throws (with an actionable message) if it cannot be provisioned.
474
+ */
475
+ async function ensureCloudflared(opts) {
476
+ const onPath = await cloudflaredOnPath();
477
+ if (onPath) return onPath;
478
+ const cached = node_path.join(opts.binDir, node_os.platform() === "win32" ? "cloudflared.exe" : "cloudflared");
479
+ if (node_fs.existsSync(cached) && await runsOk(cached)) return cached;
480
+ const version = opts.version ?? "2025.4.2";
481
+ const asset = cloudflaredAsset(node_os.platform(), node_os.arch());
482
+ const url = `${RELEASE_BASE}/${version}/${asset.fileName}`;
483
+ await node_fs_promises.mkdir(opts.binDir, { recursive: true });
484
+ opts.logger.info("Provisioning cloudflared (not on PATH) — downloading", {
485
+ meta: {
486
+ version,
487
+ asset: asset.fileName,
488
+ dest: cached
489
+ },
490
+ tags: {
491
+ topic: "tunnel",
492
+ phase: "provision"
493
+ }
494
+ });
495
+ if (asset.isArchive) {
496
+ const archive = node_path.join(opts.binDir, asset.fileName);
497
+ await downloadTo(url, archive);
498
+ await extractArchive(archive, cached, opts.logger);
499
+ } else await downloadTo(url, cached);
500
+ await node_fs_promises.chmod(cached, 493);
501
+ if (!await runsOk(cached)) throw new Error(`cloudflared downloaded to ${cached} but is not runnable`);
502
+ opts.logger.info("cloudflared ready", {
503
+ meta: {
504
+ path: cached,
505
+ version
506
+ },
507
+ tags: {
508
+ topic: "tunnel",
509
+ phase: "provision-done"
510
+ }
511
+ });
512
+ return cached;
513
+ }
343
514
  //#endregion
344
515
  //#region src/tunnel/cloudflare-api.ts
345
516
  /**
@@ -666,6 +837,19 @@ var CloudflareTunnelAddon = class extends require_dist.BaseAddon {
666
837
  }
667
838
  return "127.0.0.1";
668
839
  }
840
+ /**
841
+ * Resolve the `cloudflared` binary path once per addon lifetime (PATH install,
842
+ * or a copy downloaded + cached under `<dataDir>/bin`). Memoized so the
843
+ * (possible) download happens at most once, on the first tunnel start.
844
+ */
845
+ cloudflaredBinPromise;
846
+ resolveCloudflared() {
847
+ this.cloudflaredBinPromise ??= ensureCloudflared({
848
+ binDir: node_path.join(this.ctx.dataDir, "bin"),
849
+ logger: this.ctx.logger
850
+ });
851
+ return this.cloudflaredBinPromise;
852
+ }
669
853
  async onInitialize() {
670
854
  if (this.config.localPort === 3001) {
671
855
  this.ctx.logger.warn("Resetting stale localPort=3001 (Vite admin-ui dev port) → 0 (auto)", { tags: {
@@ -679,7 +863,7 @@ var CloudflareTunnelAddon = class extends require_dist.BaseAddon {
679
863
  ...this.config,
680
864
  localPort: this.resolveLocalPort(),
681
865
  localHost
682
- }, this.ctx.logger, this.ctx.eventBus);
866
+ }, this.ctx.logger, this.ctx.eventBus, () => this.resolveCloudflared());
683
867
  this.ctx.logger.info("Cloudflare Tunnel addon initialized", { meta: {
684
868
  mode: this.config.mode,
685
869
  hasCustomToken: !!this.config.customTunnelToken
@@ -735,7 +919,7 @@ var CloudflareTunnelAddon = class extends require_dist.BaseAddon {
735
919
  ...this.config,
736
920
  localPort: this.resolveLocalPort(),
737
921
  localHost
738
- }, this.ctx.logger, this.ctx.eventBus);
922
+ }, this.ctx.logger, this.ctx.eventBus, () => this.resolveCloudflared());
739
923
  if (this.isRunCapable()) {
740
924
  this.ctx.logger.info("config changed — (re-)spawning tunnel", {
741
925
  meta: {
@@ -1,6 +1,11 @@
1
- import { a as networkAccessCapability, c as array, d as literal, f as number, i as defineCustomActions, l as boolean, m as string, n as EventCategory, p as object, r as customAction, s as _enum, t as BaseAddon } from "../dist-gS41k7Cx.mjs";
1
+ import { a as networkAccessCapability, c as array, d as literal, f as number, i as defineCustomActions, l as boolean, m as string, n as EventCategory, p as object, r as customAction, s as _enum, t as BaseAddon } from "../dist-COIj-4Zk.mjs";
2
+ import * as path from "node:path";
2
3
  import { randomUUID } from "node:crypto";
3
4
  import { spawn } from "node:child_process";
5
+ import * as fs from "node:fs";
6
+ import * as fsp from "node:fs/promises";
7
+ import * as os from "node:os";
8
+ import { pipeline } from "node:stream/promises";
4
9
  //#region src/tunnel/cloudflare-tunnel.ts
5
10
  /**
6
11
  * Direct child_process.spawn() driver for the `cloudflared` binary.
@@ -22,19 +27,23 @@ var CloudflareTunnelService = class CloudflareTunnelService {
22
27
  config;
23
28
  logger;
24
29
  eventBus;
30
+ resolveCloudflaredBin;
25
31
  id = "cloudflare-tunnel";
26
32
  type = "cloudflare";
27
33
  endpoint = null;
28
34
  lastError;
29
35
  child = null;
36
+ /** Resolved cloudflared path/command (set on first start, reused on restart). */
37
+ cloudflaredBin = "cloudflared";
30
38
  restartCount = 0;
31
39
  intentionalStop = false;
32
40
  static MAX_RESTARTS = 5;
33
41
  static STOP_GRACE_MS = 5e3;
34
- constructor(config, logger, eventBus) {
42
+ constructor(config, logger, eventBus, resolveCloudflaredBin = async () => "cloudflared") {
35
43
  this.config = config;
36
44
  this.logger = logger;
37
45
  this.eventBus = eventBus;
46
+ this.resolveCloudflaredBin = resolveCloudflaredBin;
38
47
  }
39
48
  async start() {
40
49
  this.logger.info("Starting Cloudflare tunnel", {
@@ -75,6 +84,18 @@ var CloudflareTunnelService = class CloudflareTunnelService {
75
84
  this.intentionalStop = false;
76
85
  this.restartCount = 0;
77
86
  this.lastError = void 0;
87
+ try {
88
+ this.cloudflaredBin = await this.resolveCloudflaredBin();
89
+ } catch (err) {
90
+ this.logger.error("Failed to provision cloudflared binary", {
91
+ meta: { error: err instanceof Error ? err.message : String(err) },
92
+ tags: {
93
+ topic: "tunnel",
94
+ phase: "provision-error"
95
+ }
96
+ });
97
+ throw err;
98
+ }
78
99
  this.spawnChild();
79
100
  const placeholderHost = this.config.mode === "custom" ? this.config.customHostname : "pending.trycloudflare.com";
80
101
  this.endpoint = {
@@ -191,7 +212,7 @@ var CloudflareTunnelService = class CloudflareTunnelService {
191
212
  const args = [...debugFlags, ...tunnelArgs];
192
213
  let child;
193
214
  try {
194
- child = spawn("cloudflared", args, { stdio: [
215
+ child = spawn(this.cloudflaredBin, args, { stdio: [
195
216
  "ignore",
196
217
  "pipe",
197
218
  "pipe"
@@ -339,6 +360,130 @@ var CloudflareTunnelService = class CloudflareTunnelService {
339
360
  });
340
361
  }
341
362
  };
363
+ var RELEASE_BASE = "https://github.com/cloudflare/cloudflared/releases/download";
364
+ /**
365
+ * Map a Node `platform`/`arch` to the matching cloudflared release asset.
366
+ * Pure + exported for testing. Throws on an unsupported combination.
367
+ */
368
+ function cloudflaredAsset(platform, arch) {
369
+ const a = arch === "x64" ? "amd64" : arch === "arm64" ? "arm64" : arch === "arm" ? "arm" : null;
370
+ if (a === null) throw new Error(`cloudflared: unsupported arch '${arch}'`);
371
+ switch (platform) {
372
+ case "linux": return {
373
+ fileName: `cloudflared-linux-${a}`,
374
+ isArchive: false
375
+ };
376
+ case "darwin": return {
377
+ fileName: `cloudflared-darwin-${a === "arm64" ? "amd64" : a}.tgz`,
378
+ isArchive: true
379
+ };
380
+ case "win32": return {
381
+ fileName: `cloudflared-windows-${a}.exe`,
382
+ isArchive: false
383
+ };
384
+ default: throw new Error(`cloudflared: unsupported platform '${platform}'`);
385
+ }
386
+ }
387
+ /** Resolve a working `cloudflared` from PATH, or null if not runnable. */
388
+ async function cloudflaredOnPath() {
389
+ return await runsOk("cloudflared") ? "cloudflared" : null;
390
+ }
391
+ /** True when `<bin> --version` exits 0 (binary present + executable). */
392
+ function runsOk(bin) {
393
+ return new Promise((resolve) => {
394
+ let done = false;
395
+ const finish = (ok) => {
396
+ if (!done) {
397
+ done = true;
398
+ resolve(ok);
399
+ }
400
+ };
401
+ try {
402
+ const child = spawn(bin, ["--version"], { stdio: "ignore" });
403
+ child.on("error", () => finish(false));
404
+ child.on("exit", (code) => finish(code === 0));
405
+ setTimeout(() => {
406
+ child.kill("SIGKILL");
407
+ finish(false);
408
+ }, 5e3);
409
+ } catch {
410
+ finish(false);
411
+ }
412
+ });
413
+ }
414
+ async function downloadTo(url, dest) {
415
+ const res = await fetch(url, { redirect: "follow" });
416
+ if (!res.ok || !res.body) throw new Error(`cloudflared download failed: HTTP ${res.status} for ${url}`);
417
+ const tmp = `${dest}.partial`;
418
+ await pipeline(res.body, fs.createWriteStream(tmp));
419
+ await fsp.rename(tmp, dest);
420
+ }
421
+ /** Extract the `cloudflared` entry from a downloaded .tgz into `destBin`. */
422
+ async function extractArchive(archivePath, destBin, logger) {
423
+ const dir = path.dirname(destBin);
424
+ await new Promise((resolve, reject) => {
425
+ const child = spawn("tar", [
426
+ "-xzf",
427
+ archivePath,
428
+ "-C",
429
+ dir
430
+ ], { stdio: "inherit" });
431
+ child.on("error", reject);
432
+ child.on("exit", (code) => code === 0 ? resolve() : reject(/* @__PURE__ */ new Error(`tar exited ${code}`)));
433
+ });
434
+ if (!fs.existsSync(destBin)) logger.warn("cloudflared archive did not yield expected binary name", {
435
+ meta: { destBin },
436
+ tags: {
437
+ topic: "tunnel",
438
+ phase: "extract"
439
+ }
440
+ });
441
+ await fsp.rm(archivePath, { force: true });
442
+ }
443
+ /**
444
+ * Return a path/command for a runnable `cloudflared`, downloading + caching it
445
+ * on first use. Prefers an existing PATH install; otherwise caches under
446
+ * `binDir`. Throws (with an actionable message) if it cannot be provisioned.
447
+ */
448
+ async function ensureCloudflared(opts) {
449
+ const onPath = await cloudflaredOnPath();
450
+ if (onPath) return onPath;
451
+ const cached = path.join(opts.binDir, os.platform() === "win32" ? "cloudflared.exe" : "cloudflared");
452
+ if (fs.existsSync(cached) && await runsOk(cached)) return cached;
453
+ const version = opts.version ?? "2025.4.2";
454
+ const asset = cloudflaredAsset(os.platform(), os.arch());
455
+ const url = `${RELEASE_BASE}/${version}/${asset.fileName}`;
456
+ await fsp.mkdir(opts.binDir, { recursive: true });
457
+ opts.logger.info("Provisioning cloudflared (not on PATH) — downloading", {
458
+ meta: {
459
+ version,
460
+ asset: asset.fileName,
461
+ dest: cached
462
+ },
463
+ tags: {
464
+ topic: "tunnel",
465
+ phase: "provision"
466
+ }
467
+ });
468
+ if (asset.isArchive) {
469
+ const archive = path.join(opts.binDir, asset.fileName);
470
+ await downloadTo(url, archive);
471
+ await extractArchive(archive, cached, opts.logger);
472
+ } else await downloadTo(url, cached);
473
+ await fsp.chmod(cached, 493);
474
+ if (!await runsOk(cached)) throw new Error(`cloudflared downloaded to ${cached} but is not runnable`);
475
+ opts.logger.info("cloudflared ready", {
476
+ meta: {
477
+ path: cached,
478
+ version
479
+ },
480
+ tags: {
481
+ topic: "tunnel",
482
+ phase: "provision-done"
483
+ }
484
+ });
485
+ return cached;
486
+ }
342
487
  //#endregion
343
488
  //#region src/tunnel/cloudflare-api.ts
344
489
  /**
@@ -665,6 +810,19 @@ var CloudflareTunnelAddon = class extends BaseAddon {
665
810
  }
666
811
  return "127.0.0.1";
667
812
  }
813
+ /**
814
+ * Resolve the `cloudflared` binary path once per addon lifetime (PATH install,
815
+ * or a copy downloaded + cached under `<dataDir>/bin`). Memoized so the
816
+ * (possible) download happens at most once, on the first tunnel start.
817
+ */
818
+ cloudflaredBinPromise;
819
+ resolveCloudflared() {
820
+ this.cloudflaredBinPromise ??= ensureCloudflared({
821
+ binDir: path.join(this.ctx.dataDir, "bin"),
822
+ logger: this.ctx.logger
823
+ });
824
+ return this.cloudflaredBinPromise;
825
+ }
668
826
  async onInitialize() {
669
827
  if (this.config.localPort === 3001) {
670
828
  this.ctx.logger.warn("Resetting stale localPort=3001 (Vite admin-ui dev port) → 0 (auto)", { tags: {
@@ -678,7 +836,7 @@ var CloudflareTunnelAddon = class extends BaseAddon {
678
836
  ...this.config,
679
837
  localPort: this.resolveLocalPort(),
680
838
  localHost
681
- }, this.ctx.logger, this.ctx.eventBus);
839
+ }, this.ctx.logger, this.ctx.eventBus, () => this.resolveCloudflared());
682
840
  this.ctx.logger.info("Cloudflare Tunnel addon initialized", { meta: {
683
841
  mode: this.config.mode,
684
842
  hasCustomToken: !!this.config.customTunnelToken
@@ -734,7 +892,7 @@ var CloudflareTunnelAddon = class extends BaseAddon {
734
892
  ...this.config,
735
893
  localPort: this.resolveLocalPort(),
736
894
  localHost
737
- }, this.ctx.logger, this.ctx.eventBus);
895
+ }, this.ctx.logger, this.ctx.eventBus, () => this.resolveCloudflared());
738
896
  if (this.isRunCapable()) {
739
897
  this.ctx.logger.info("config changed — (re-)spawning tunnel", {
740
898
  meta: {
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_dist = require("../dist-FQAxmoZY.js");
2
+ const require_dist = require("../dist-BaLJK4dE.js");
3
3
  //#region src/turn/cloudflare-turn.ts
4
4
  /**
5
5
  * Cloudflare returns ICE servers in several flavours depending on which
@@ -1,4 +1,4 @@
1
- import { d as literal, f as number, i as defineCustomActions, l as boolean, m as string, o as turnProviderCapability, p as object, r as customAction, t as BaseAddon, u as discriminatedUnion } from "../dist-gS41k7Cx.mjs";
1
+ import { d as literal, f as number, i as defineCustomActions, l as boolean, m as string, o as turnProviderCapability, p as object, r as customAction, t as BaseAddon, u as discriminatedUnion } from "../dist-COIj-4Zk.mjs";
2
2
  //#region src/turn/cloudflare-turn.ts
3
3
  /**
4
4
  * Cloudflare returns ICE servers in several flavours depending on which
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-cloudflare",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Cloudflare bundle — Tunnel (network-access) + TURN relay (turn-provider). Multi-entry npm package shipping 2 addons under a single bundle.",
5
5
  "keywords": [
6
6
  "camstack",