@tomflow/proflow-dev-tunnel 0.1.16 → 0.1.18

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @tomflow/proflow-dev-tunnel
2
2
 
3
+ ## 0.1.18
4
+
5
+ ### Patch Changes
6
+
7
+ - Harden Deployment Closeout: fail closed on partial installs and package-manager conflicts, normalize Git workspace isolation, make Dev Tunnel status fast and resumable without fabricating login failures, and align Browser Extension setup/conformance behavior.
8
+
9
+ ## 0.1.17
10
+
11
+ ### Patch Changes
12
+
13
+ - Add professional setup orchestration, Provider FAST/THINK input, recoverable managed Dev Tunnel provisioning, Browser installation guidance, and precise status diagnostics.
14
+
3
15
  ## 0.1.16
4
16
 
5
17
  ### Patch Changes
package/DOCS.md CHANGED
@@ -27,7 +27,7 @@
27
27
 
28
28
  ## 使用方式
29
29
 
30
- 运行 `platform setup --module dev-tunnel`;除登录失效时的 GitHub 浏览器授权外,不需要输入 Tunnel ID、端口或 URL。
30
+ 运行 `platform setup`;除登录失效时的 GitHub 浏览器授权外,不需要输入 Tunnel ID、端口或 URL。
31
31
 
32
32
  ## 职责边界与限制
33
33
 
package/SETUP.md CHANGED
@@ -4,9 +4,9 @@
4
4
 
5
5
  Responsible: AI
6
6
 
7
- Interactive executable: `platform setup --module dev-tunnel`
7
+ Interactive executable: `platform setup`
8
8
 
9
- Non-interactive executable: `pnpm exec -- proflow-dev-tunnel setup --workspace <path>`
9
+ Non-interactive executable: `platform setup --workspace <path>`
10
10
 
11
11
  Required inputs: none
12
12
 
@@ -25,6 +25,6 @@ Automatic flow:
25
25
  → 持久化并发布 tunnelId/publicBaseUrl
26
26
  ```
27
27
 
28
- Verify: `pnpm exec -- proflow-dev-tunnel verify`
28
+ Verify: `platform status`
29
29
 
30
30
  Success condition: `dev-tunnel.setupStatus=READY`.
@@ -1,9 +1,13 @@
1
1
  import { type ModuleCommandContext } from "@tomflow/proflow-module-contract";
2
2
  import { type DevTunnelAutomation, type DevTunnelRuntime } from "../src/resource-adapter.ts";
3
+ type SetupPhase = "PENDING_CREATED" | "PORT_READY" | "HOST_READY" | "READY";
3
4
  type SetupState = {
4
- contract: "proflow.dev-tunnel-setup.v1";
5
+ contract: "proflow.dev-tunnel-setup.v2";
5
6
  tunnelId: string;
6
- publicBaseUrl: string;
7
+ phase: SetupPhase;
8
+ gatewayPort?: number;
9
+ publicBaseUrl?: string;
10
+ cliPath?: string;
7
11
  };
8
12
  type RuntimeFactory = (input: {
9
13
  command?: string;
@@ -15,6 +19,7 @@ export declare function createDevTunnelBehaviorAdapter(dependencies?: {
15
19
  automation?: DevTunnelAutomation;
16
20
  createRuntime?: RuntimeFactory;
17
21
  verifyPublicBaseUrl?: (publicBaseUrl: string) => Promise<void>;
22
+ resolveCli?: (workspaceRoot: string) => Promise<string>;
18
23
  }): {
19
24
  readonly install: (context: ModuleCommandContext) => Promise<{
20
25
  result: {
@@ -22,7 +27,7 @@ export declare function createDevTunnelBehaviorAdapter(dependencies?: {
22
27
  readonly ok: true;
23
28
  readonly status: "SUCCEEDED";
24
29
  readonly moduleRef: "dev-tunnel";
25
- readonly moduleVersion: "0.1.16";
30
+ readonly moduleVersion: "0.1.17";
26
31
  };
27
32
  observedEffects: never[];
28
33
  }>;
@@ -32,14 +37,14 @@ export declare function createDevTunnelBehaviorAdapter(dependencies?: {
32
37
  readonly ok: true;
33
38
  readonly status: "SUCCEEDED";
34
39
  readonly moduleRef: "dev-tunnel";
35
- readonly moduleVersion: "0.1.16";
40
+ readonly moduleVersion: "0.1.17";
36
41
  };
37
42
  observedEffects: string[];
38
43
  } | {
39
44
  result: {
40
45
  contract: "deployment.result.v1";
41
46
  moduleRef: "dev-tunnel";
42
- moduleVersion: "0.1.16";
47
+ moduleVersion: "0.1.17";
43
48
  ok: false;
44
49
  status: "FAILED";
45
50
  error: {
@@ -56,7 +61,7 @@ export declare function createDevTunnelBehaviorAdapter(dependencies?: {
56
61
  ok: true;
57
62
  status: "SUCCEEDED";
58
63
  moduleRef: "dev-tunnel";
59
- moduleVersion: "0.1.16";
64
+ moduleVersion: "0.1.17";
60
65
  data: {
61
66
  setupStatus: "ACTION_REQUIRED" | "READY";
62
67
  runtimeStatus: "FAILED" | "RUNNING" | "STOPPED";
@@ -83,7 +88,7 @@ export declare function createDevTunnelBehaviorAdapter(dependencies?: {
83
88
  ok: true;
84
89
  status: "SUCCEEDED";
85
90
  moduleRef: "dev-tunnel";
86
- moduleVersion: "0.1.16";
91
+ moduleVersion: "0.1.17";
87
92
  data: {
88
93
  docs: string;
89
94
  };
@@ -94,7 +99,7 @@ export declare function createDevTunnelBehaviorAdapter(dependencies?: {
94
99
  result: {
95
100
  contract: "deployment.result.v1";
96
101
  moduleRef: "dev-tunnel";
97
- moduleVersion: "0.1.16";
102
+ moduleVersion: "0.1.17";
98
103
  ok: false;
99
104
  status: "FAILED";
100
105
  error: {
@@ -110,7 +115,7 @@ export declare function createDevTunnelBehaviorAdapter(dependencies?: {
110
115
  ok: true;
111
116
  status: "SUCCEEDED";
112
117
  moduleRef: "dev-tunnel";
113
- moduleVersion: "0.1.16";
118
+ moduleVersion: "0.1.17";
114
119
  data: import("../src/resource-adapter.ts").DevTunnelObservation;
115
120
  };
116
121
  observedEffects: string[];
@@ -121,14 +126,14 @@ export declare function createDevTunnelBehaviorAdapter(dependencies?: {
121
126
  readonly ok: true;
122
127
  readonly status: "SUCCEEDED";
123
128
  readonly moduleRef: "dev-tunnel";
124
- readonly moduleVersion: "0.1.16";
129
+ readonly moduleVersion: "0.1.17";
125
130
  };
126
131
  observedEffects: string[];
127
132
  } | {
128
133
  result: {
129
134
  contract: "deployment.result.v1";
130
135
  moduleRef: "dev-tunnel";
131
- moduleVersion: "0.1.16";
136
+ moduleVersion: "0.1.17";
132
137
  ok: false;
133
138
  status: "FAILED";
134
139
  error: {
@@ -143,7 +148,7 @@ export declare function createDevTunnelBehaviorAdapter(dependencies?: {
143
148
  result: {
144
149
  contract: "deployment.result.v1";
145
150
  moduleRef: "dev-tunnel";
146
- moduleVersion: "0.1.16";
151
+ moduleVersion: "0.1.17";
147
152
  ok: false;
148
153
  status: "FAILED";
149
154
  error: {
@@ -159,7 +164,7 @@ export declare function createDevTunnelBehaviorAdapter(dependencies?: {
159
164
  ok: true;
160
165
  status: "SUCCEEDED";
161
166
  moduleRef: "dev-tunnel";
162
- moduleVersion: "0.1.16";
167
+ moduleVersion: "0.1.17";
163
168
  data: SetupState;
164
169
  };
165
170
  observedEffects: string[];
@@ -172,7 +177,7 @@ export declare const behaviorAdapter: {
172
177
  readonly ok: true;
173
178
  readonly status: "SUCCEEDED";
174
179
  readonly moduleRef: "dev-tunnel";
175
- readonly moduleVersion: "0.1.16";
180
+ readonly moduleVersion: "0.1.17";
176
181
  };
177
182
  observedEffects: never[];
178
183
  }>;
@@ -182,14 +187,14 @@ export declare const behaviorAdapter: {
182
187
  readonly ok: true;
183
188
  readonly status: "SUCCEEDED";
184
189
  readonly moduleRef: "dev-tunnel";
185
- readonly moduleVersion: "0.1.16";
190
+ readonly moduleVersion: "0.1.17";
186
191
  };
187
192
  observedEffects: string[];
188
193
  } | {
189
194
  result: {
190
195
  contract: "deployment.result.v1";
191
196
  moduleRef: "dev-tunnel";
192
- moduleVersion: "0.1.16";
197
+ moduleVersion: "0.1.17";
193
198
  ok: false;
194
199
  status: "FAILED";
195
200
  error: {
@@ -206,7 +211,7 @@ export declare const behaviorAdapter: {
206
211
  ok: true;
207
212
  status: "SUCCEEDED";
208
213
  moduleRef: "dev-tunnel";
209
- moduleVersion: "0.1.16";
214
+ moduleVersion: "0.1.17";
210
215
  data: {
211
216
  setupStatus: "ACTION_REQUIRED" | "READY";
212
217
  runtimeStatus: "FAILED" | "RUNNING" | "STOPPED";
@@ -233,7 +238,7 @@ export declare const behaviorAdapter: {
233
238
  ok: true;
234
239
  status: "SUCCEEDED";
235
240
  moduleRef: "dev-tunnel";
236
- moduleVersion: "0.1.16";
241
+ moduleVersion: "0.1.17";
237
242
  data: {
238
243
  docs: string;
239
244
  };
@@ -244,7 +249,7 @@ export declare const behaviorAdapter: {
244
249
  result: {
245
250
  contract: "deployment.result.v1";
246
251
  moduleRef: "dev-tunnel";
247
- moduleVersion: "0.1.16";
252
+ moduleVersion: "0.1.17";
248
253
  ok: false;
249
254
  status: "FAILED";
250
255
  error: {
@@ -260,7 +265,7 @@ export declare const behaviorAdapter: {
260
265
  ok: true;
261
266
  status: "SUCCEEDED";
262
267
  moduleRef: "dev-tunnel";
263
- moduleVersion: "0.1.16";
268
+ moduleVersion: "0.1.17";
264
269
  data: import("../src/resource-adapter.ts").DevTunnelObservation;
265
270
  };
266
271
  observedEffects: string[];
@@ -271,14 +276,14 @@ export declare const behaviorAdapter: {
271
276
  readonly ok: true;
272
277
  readonly status: "SUCCEEDED";
273
278
  readonly moduleRef: "dev-tunnel";
274
- readonly moduleVersion: "0.1.16";
279
+ readonly moduleVersion: "0.1.17";
275
280
  };
276
281
  observedEffects: string[];
277
282
  } | {
278
283
  result: {
279
284
  contract: "deployment.result.v1";
280
285
  moduleRef: "dev-tunnel";
281
- moduleVersion: "0.1.16";
286
+ moduleVersion: "0.1.17";
282
287
  ok: false;
283
288
  status: "FAILED";
284
289
  error: {
@@ -293,7 +298,7 @@ export declare const behaviorAdapter: {
293
298
  result: {
294
299
  contract: "deployment.result.v1";
295
300
  moduleRef: "dev-tunnel";
296
- moduleVersion: "0.1.16";
301
+ moduleVersion: "0.1.17";
297
302
  ok: false;
298
303
  status: "FAILED";
299
304
  error: {
@@ -309,7 +314,7 @@ export declare const behaviorAdapter: {
309
314
  ok: true;
310
315
  status: "SUCCEEDED";
311
316
  moduleRef: "dev-tunnel";
312
- moduleVersion: "0.1.16";
317
+ moduleVersion: "0.1.17";
313
318
  data: SetupState;
314
319
  };
315
320
  observedEffects: string[];
@@ -1,6 +1,7 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
3
3
  import { join, resolve } from "node:path";
4
+ import { resolveDevTunnelCli } from "@tomflow/proflow-devtunnel-cli";
4
5
  import { readModuleSharedFacts, writeModuleSharedFacts, } from "@tomflow/proflow-module-contract";
5
6
  import { createDevTunnelAutomation, createDevTunnelRuntime, verifyProvisionedPublicBaseUrl, } from "../src/resource-adapter.js";
6
7
  import { descriptor } from "./descriptor.js";
@@ -18,15 +19,50 @@ const processFile = (context) => join(stateDir(context), "process.json");
18
19
  async function readState(context) {
19
20
  try {
20
21
  const raw = JSON.parse(await readFile(stateFile(context), "utf8"));
21
- if (raw.contract !== "proflow.dev-tunnel-setup.v1" ||
22
- typeof raw.tunnelId !== "string" ||
23
- !raw.tunnelId ||
24
- typeof raw.publicBaseUrl !== "string")
22
+ if (typeof raw.tunnelId !== "string" || !raw.tunnelId)
25
23
  return undefined;
26
- const url = new URL(raw.publicBaseUrl);
27
- if (url.protocol !== "https:")
24
+ if (raw.contract === "proflow.dev-tunnel-setup.v1" &&
25
+ typeof raw.publicBaseUrl === "string") {
26
+ const url = new URL(raw.publicBaseUrl);
27
+ if (url.protocol !== "https:")
28
+ return undefined;
29
+ return {
30
+ contract: "proflow.dev-tunnel-setup.v2",
31
+ tunnelId: raw.tunnelId,
32
+ phase: "READY",
33
+ publicBaseUrl: url.href,
34
+ };
35
+ }
36
+ if (raw.contract !== "proflow.dev-tunnel-setup.v2")
37
+ return undefined;
38
+ if (raw.phase !== "PENDING_CREATED" &&
39
+ raw.phase !== "PORT_READY" &&
40
+ raw.phase !== "HOST_READY" &&
41
+ raw.phase !== "READY")
28
42
  return undefined;
29
- return raw;
43
+ const gatewayPort = raw.gatewayPort;
44
+ if (gatewayPort !== undefined &&
45
+ (!Number.isInteger(gatewayPort) ||
46
+ Number(gatewayPort) < 1 ||
47
+ Number(gatewayPort) > 65_535))
48
+ return undefined;
49
+ const publicBaseUrl = raw.publicBaseUrl;
50
+ const cliPath = raw.cliPath;
51
+ if (raw.phase === "READY") {
52
+ if (typeof publicBaseUrl !== "string")
53
+ return undefined;
54
+ const url = new URL(publicBaseUrl);
55
+ if (url.protocol !== "https:")
56
+ return undefined;
57
+ }
58
+ return {
59
+ contract: "proflow.dev-tunnel-setup.v2",
60
+ tunnelId: raw.tunnelId,
61
+ phase: raw.phase,
62
+ ...(typeof gatewayPort === "number" ? { gatewayPort } : {}),
63
+ ...(typeof publicBaseUrl === "string" ? { publicBaseUrl } : {}),
64
+ ...(typeof cliPath === "string" ? { cliPath } : {}),
65
+ };
30
66
  }
31
67
  catch {
32
68
  return undefined;
@@ -41,6 +77,7 @@ async function writeState(context, state) {
41
77
  }
42
78
  function runtime(context, state) {
43
79
  return createDevTunnelRuntime({
80
+ ...(state?.cliPath ? { command: state.cliPath } : {}),
44
81
  ...(state
45
82
  ? { tunnelId: state.tunnelId, publicBaseUrl: state.publicBaseUrl }
46
83
  : {}),
@@ -51,7 +88,7 @@ const baseBehaviorAdapter = {
51
88
  install: async (context) => {
52
89
  await mkdir(stateDir(context), { recursive: true, mode: 0o700 });
53
90
  const state = await readState(context);
54
- if (state)
91
+ if (state?.phase === "READY" && state.publicBaseUrl)
55
92
  await writeModuleSharedFacts(context, descriptor.moduleRef, {
56
93
  tunnelId: state.tunnelId,
57
94
  publicBaseUrl: state.publicBaseUrl,
@@ -113,7 +150,7 @@ const baseBehaviorAdapter = {
113
150
  code: "TUNNEL_SETUP_REQUIRED",
114
151
  message: "尚未完成持久 Tunnel 自动配置",
115
152
  relatedModuleRefs: [],
116
- nextCommand: "platform setup --module dev-tunnel",
153
+ nextCommand: "platform setup",
117
154
  },
118
155
  ],
119
156
  },
@@ -122,9 +159,8 @@ const baseBehaviorAdapter = {
122
159
  };
123
160
  }
124
161
  const rt = runtime(context, state);
125
- const login = await rt.loginStatus();
126
162
  const observed = await rt.status();
127
- const configured = state !== undefined && login === "LOGGED_IN";
163
+ const configured = state.phase === "READY" && typeof state.publicBaseUrl === "string";
128
164
  const runtimeStatus = observed.state === "RUNNING"
129
165
  ? "RUNNING"
130
166
  : observed.state === "STOPPED"
@@ -147,10 +183,10 @@ const baseBehaviorAdapter = {
147
183
  ? [
148
184
  {
149
185
  scope: "SETUP",
150
- code: "TUNNEL_LOGIN_REQUIRED",
151
- message: "Dev Tunnel CLI 尚未登录或配置未保存",
186
+ code: "TUNNEL_SETUP_INCOMPLETE",
187
+ message: `远程连接配置已保存,当前阶段 ${state.phase};重新运行 Platform setup 将从此处恢复`,
152
188
  relatedModuleRefs: [],
153
- nextCommand: "platform setup --module dev-tunnel",
189
+ nextCommand: "platform setup",
154
190
  },
155
191
  ]
156
192
  : []),
@@ -161,7 +197,7 @@ const baseBehaviorAdapter = {
161
197
  code: "TUNNEL_RUNTIME_FAILED",
162
198
  message: "Tunnel 运行状态检查失败",
163
199
  relatedModuleRefs: [],
164
- nextCommand: "pnpm exec -- proflow-dev-tunnel verify",
200
+ nextCommand: "platform status",
165
201
  },
166
202
  ]
167
203
  : []),
@@ -184,7 +220,7 @@ const baseBehaviorAdapter = {
184
220
  }),
185
221
  start: async (context) => {
186
222
  const state = await readState(context);
187
- if (!state)
223
+ if (state?.phase !== "READY" || !state.publicBaseUrl)
188
224
  return {
189
225
  result: {
190
226
  ...base,
@@ -321,18 +357,23 @@ const setupFailed = (error) => ({
321
357
  observedEffects: [],
322
358
  });
323
359
  export function createDevTunnelBehaviorAdapter(dependencies) {
324
- const automation = dependencies?.automation ??
325
- createDevTunnelAutomation({ command: "devtunnel" });
326
360
  const createRuntime = dependencies?.createRuntime ?? createDevTunnelRuntime;
327
361
  const verifyPublicBaseUrl = dependencies?.verifyPublicBaseUrl ?? verifyProvisionedPublicBaseUrl;
328
362
  return {
329
363
  ...baseBehaviorAdapter,
330
364
  setup: async (context) => {
331
365
  try {
366
+ const previous = await readState(context);
367
+ const cliPath = previous?.cliPath ??
368
+ (dependencies?.automation
369
+ ? "devtunnel"
370
+ : await (dependencies?.resolveCli ??
371
+ (async (workspaceRoot) => (await resolveDevTunnelCli({ workspaceRoot })).command))(context.workspaceRoot));
372
+ const automation = dependencies?.automation ??
373
+ createDevTunnelAutomation({ command: cliPath });
332
374
  await mkdir(stateDir(context), { recursive: true, mode: 0o700 });
333
375
  const port = gatewayPort(await readModuleSharedFacts(context, "agent-gateway"));
334
376
  await automation.ensureLogin();
335
- const previous = await readState(context);
336
377
  let tunnelId;
337
378
  let safeToStartNewHost = false;
338
379
  const createAndVerifyTunnel = async () => {
@@ -340,6 +381,13 @@ export function createDevTunnelBehaviorAdapter(dependencies) {
340
381
  const created = await automation.inspectTunnel(createdTunnelId);
341
382
  if (created.state !== "EXISTS")
342
383
  throw new Error("new Dev Tunnel could not be verified by show --json");
384
+ await writeState(context, {
385
+ contract: "proflow.dev-tunnel-setup.v2",
386
+ tunnelId: createdTunnelId,
387
+ phase: "PENDING_CREATED",
388
+ gatewayPort: port,
389
+ cliPath,
390
+ });
343
391
  return createdTunnelId;
344
392
  };
345
393
  if (previous) {
@@ -360,6 +408,13 @@ export function createDevTunnelBehaviorAdapter(dependencies) {
360
408
  safeToStartNewHost = true;
361
409
  }
362
410
  await automation.ensurePort(tunnelId, port);
411
+ await writeState(context, {
412
+ contract: "proflow.dev-tunnel-setup.v2",
413
+ tunnelId,
414
+ phase: "PORT_READY",
415
+ gatewayPort: port,
416
+ cliPath,
417
+ });
363
418
  const host = createRuntime({
364
419
  tunnelId,
365
420
  processStateFile: processFile(context),
@@ -374,15 +429,25 @@ export function createDevTunnelBehaviorAdapter(dependencies) {
374
429
  throw new Error("Dev Tunnel host did not reach RUNNING");
375
430
  started = true;
376
431
  }
432
+ await writeState(context, {
433
+ contract: "proflow.dev-tunnel-setup.v2",
434
+ tunnelId,
435
+ phase: "HOST_READY",
436
+ gatewayPort: port,
437
+ cliPath,
438
+ });
377
439
  const publicBaseUrl = await automation.discoverPublicBaseUrl(tunnelId, port);
378
440
  const url = new URL(publicBaseUrl);
379
441
  if (url.protocol !== "https:")
380
442
  throw new Error("discovered publicBaseUrl must be HTTPS");
381
443
  await verifyPublicBaseUrl(url.href);
382
444
  const state = {
383
- contract: "proflow.dev-tunnel-setup.v1",
445
+ contract: "proflow.dev-tunnel-setup.v2",
384
446
  tunnelId,
447
+ phase: "READY",
448
+ gatewayPort: port,
385
449
  publicBaseUrl: url.href,
450
+ cliPath,
386
451
  };
387
452
  await writeState(context, state);
388
453
  await writeModuleSharedFacts(context, descriptor.moduleRef, {
@@ -3,7 +3,7 @@ export declare const descriptor: {
3
3
  readonly contractVersion: "1.0.0";
4
4
  readonly moduleRef: "dev-tunnel";
5
5
  readonly packageName: "@tomflow/proflow-dev-tunnel";
6
- readonly moduleVersion: "0.1.16";
6
+ readonly moduleVersion: "0.1.17";
7
7
  readonly kind: "external-resource";
8
8
  readonly templateVersion: "1.0.0";
9
9
  readonly platformCompatibility: ">=1.0.0 <2.0.0";
@@ -3,7 +3,7 @@ export const descriptor = {
3
3
  contractVersion: "1.0.0",
4
4
  moduleRef: "dev-tunnel",
5
5
  packageName: "@tomflow/proflow-dev-tunnel",
6
- moduleVersion: "0.1.16",
6
+ moduleVersion: "0.1.17",
7
7
  kind: "external-resource",
8
8
  templateVersion: "1.0.0",
9
9
  platformCompatibility: ">=1.0.0 <2.0.0",
@@ -10,7 +10,17 @@ const START_CONFIRM_MS = 500;
10
10
  function defaultCommandRunner(command, args, options) {
11
11
  if (options?.interactive) {
12
12
  return new Promise((resolve) => {
13
- const child = spawn(command, args, { stdio: "inherit" });
13
+ const child = spawn(command, args, {
14
+ stdio: ["ignore", "pipe", "pipe"],
15
+ });
16
+ let stdout = "";
17
+ let stderr = "";
18
+ child.stdout?.on("data", (chunk) => {
19
+ stdout += chunk.toString();
20
+ });
21
+ child.stderr?.on("data", (chunk) => {
22
+ stderr += chunk.toString();
23
+ });
14
24
  let timedOut = false;
15
25
  const timer = setTimeout(() => {
16
26
  timedOut = true;
@@ -18,14 +28,14 @@ function defaultCommandRunner(command, args, options) {
18
28
  }, options.timeoutMs ?? 600_000);
19
29
  child.once("error", (error) => {
20
30
  clearTimeout(timer);
21
- resolve({ exitCode: null, stdout: "", stderr: error.message });
31
+ resolve({ exitCode: null, stdout, stderr: error.message });
22
32
  });
23
33
  child.once("exit", (code) => {
24
34
  clearTimeout(timer);
25
35
  resolve({
26
36
  exitCode: timedOut ? null : code,
27
- stdout: "",
28
- stderr: timedOut ? "command timed out" : "",
37
+ stdout,
38
+ stderr: timedOut ? "command timed out" : stderr,
29
39
  });
30
40
  });
31
41
  });
@@ -94,6 +104,9 @@ function parsePorts(input) {
94
104
  if (Array.isArray(input))
95
105
  return z.array(tunnelPortSchema).parse(input);
96
106
  if (typeof input === "object" && input !== null) {
107
+ const warning = Reflect.get(input, "warning");
108
+ if (typeof warning === "string" && /\bno ports found\b/i.test(warning))
109
+ return [];
97
110
  const ports = Reflect.get(input, "ports");
98
111
  if (Array.isArray(ports))
99
112
  return z.array(tunnelPortSchema).parse(ports);
@@ -391,14 +404,11 @@ export function createDevTunnelRuntime(input) {
391
404
  return "UNKNOWN";
392
405
  }
393
406
  };
394
- const observe = async (state) => {
395
- const login = await observeLogin();
396
- return {
397
- state,
398
- login,
399
- ...(publicBaseUrl === undefined ? {} : { publicBaseUrl }),
400
- };
401
- };
407
+ const observation = (state, login = "UNKNOWN") => ({
408
+ state,
409
+ login,
410
+ ...(publicBaseUrl === undefined ? {} : { publicBaseUrl }),
411
+ });
402
412
  const ownedPersistedPid = async () => {
403
413
  if (!processStateFile || !tunnelId)
404
414
  return undefined;
@@ -414,24 +424,24 @@ export function createDevTunnelRuntime(input) {
414
424
  command,
415
425
  async status() {
416
426
  if (await ownedPersistedPid())
417
- return observe("RUNNING");
427
+ return observation("RUNNING");
418
428
  if (child?.pid && processAlive(child.pid))
419
- return observe("RUNNING");
420
- return observe("UNKNOWN");
429
+ return observation("RUNNING");
430
+ return observation("UNKNOWN");
421
431
  },
422
432
  loginStatus: () => observeLogin(),
423
433
  publicBaseUrl: () => publicBaseUrl,
424
434
  async start() {
425
435
  const login = await observeLogin();
426
436
  if (login !== "LOGGED_IN")
427
- return observe("UNKNOWN");
437
+ return observation("UNKNOWN", login);
428
438
  if (tunnelId === undefined) {
429
439
  throw new TypeError("tunnelId is required to host the configured persistent tunnel");
430
440
  }
431
441
  if (await ownedPersistedPid())
432
- return observe("RUNNING");
442
+ return observation("RUNNING", login);
433
443
  if (child?.pid && processAlive(child.pid))
434
- return observe("RUNNING");
444
+ return observation("RUNNING", login);
435
445
  const spawned = spawn(command, ["host", tunnelId], {
436
446
  stdio: "ignore",
437
447
  detached: true,
@@ -480,7 +490,7 @@ export function createDevTunnelRuntime(input) {
480
490
  }
481
491
  }
482
492
  spawned.unref();
483
- return observe("RUNNING");
493
+ return observation("RUNNING", login);
484
494
  },
485
495
  async stop() {
486
496
  const persistedPid = await ownedPersistedPid();
@@ -489,20 +499,20 @@ export function createDevTunnelRuntime(input) {
489
499
  if (processStateFile)
490
500
  await rm(processStateFile, { force: true });
491
501
  child = undefined;
492
- return observe("STOPPED");
502
+ return observation("STOPPED");
493
503
  }
494
504
  try {
495
505
  process.kill(pid, "SIGTERM");
496
506
  }
497
507
  catch {
498
- return observe("UNKNOWN");
508
+ return observation("UNKNOWN");
499
509
  }
500
510
  if (!(await waitForProcessExit(pid)))
501
- return observe("UNKNOWN");
511
+ return observation("UNKNOWN");
502
512
  if (processStateFile)
503
513
  await rm(processStateFile, { force: true });
504
514
  child = undefined;
505
- return observe("STOPPED");
515
+ return observation("STOPPED");
506
516
  },
507
517
  async restart() {
508
518
  const stopped = await this.stop();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tomflow/proflow-dev-tunnel",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -23,10 +23,11 @@
23
23
  ],
24
24
  "dependencies": {
25
25
  "zod": "4.1.12",
26
+ "@tomflow/proflow-devtunnel-cli": "^0.1.0",
26
27
  "@tomflow/proflow-module-contract": "^0.1.13"
27
28
  },
28
29
  "devDependencies": {
29
- "@tomflow/proflow-deployment-conformance": "^0.1.12"
30
+ "@tomflow/proflow-deployment-conformance": "^0.1.13"
30
31
  },
31
32
  "description": "Governs the Microsoft Dev Tunnel public HTTPS ingress resource and its managed local host process.",
32
33
  "keywords": [
@@ -3,7 +3,7 @@
3
3
  "contractVersion": "1.0.0",
4
4
  "moduleRef": "dev-tunnel",
5
5
  "packageName": "@tomflow/proflow-dev-tunnel",
6
- "moduleVersion": "0.1.16",
6
+ "moduleVersion": "0.1.18",
7
7
  "kind": "external-resource",
8
8
  "templateVersion": "1.0.0",
9
9
  "platformCompatibility": ">=1.0.0 <2.0.0",