@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 +12 -0
- package/DOCS.md +1 -1
- package/SETUP.md +3 -3
- package/dist/deployment/adapter.d.ts +29 -24
- package/dist/deployment/adapter.js +85 -20
- package/dist/deployment/descriptor.d.ts +1 -1
- package/dist/deployment/descriptor.js +1 -1
- package/dist/src/resource-adapter.js +33 -23
- package/package.json +3 -2
- package/proflow.module.json +1 -1
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
package/SETUP.md
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
Responsible: AI
|
|
6
6
|
|
|
7
|
-
Interactive executable: `platform setup
|
|
7
|
+
Interactive executable: `platform setup`
|
|
8
8
|
|
|
9
|
-
Non-interactive executable: `
|
|
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: `
|
|
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.
|
|
5
|
+
contract: "proflow.dev-tunnel-setup.v2";
|
|
5
6
|
tunnelId: string;
|
|
6
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
27
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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: "
|
|
151
|
-
message:
|
|
186
|
+
code: "TUNNEL_SETUP_INCOMPLETE",
|
|
187
|
+
message: `远程连接配置已保存,当前阶段 ${state.phase};重新运行 Platform setup 将从此处恢复`,
|
|
152
188
|
relatedModuleRefs: [],
|
|
153
|
-
nextCommand: "platform setup
|
|
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: "
|
|
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.
|
|
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.
|
|
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.
|
|
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, {
|
|
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
|
|
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
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
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
|
|
427
|
+
return observation("RUNNING");
|
|
418
428
|
if (child?.pid && processAlive(child.pid))
|
|
419
|
-
return
|
|
420
|
-
return
|
|
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
|
|
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
|
|
442
|
+
return observation("RUNNING", login);
|
|
433
443
|
if (child?.pid && processAlive(child.pid))
|
|
434
|
-
return
|
|
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
|
|
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
|
|
502
|
+
return observation("STOPPED");
|
|
493
503
|
}
|
|
494
504
|
try {
|
|
495
505
|
process.kill(pid, "SIGTERM");
|
|
496
506
|
}
|
|
497
507
|
catch {
|
|
498
|
-
return
|
|
508
|
+
return observation("UNKNOWN");
|
|
499
509
|
}
|
|
500
510
|
if (!(await waitForProcessExit(pid)))
|
|
501
|
-
return
|
|
511
|
+
return observation("UNKNOWN");
|
|
502
512
|
if (processStateFile)
|
|
503
513
|
await rm(processStateFile, { force: true });
|
|
504
514
|
child = undefined;
|
|
505
|
-
return
|
|
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.
|
|
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.
|
|
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": [
|
package/proflow.module.json
CHANGED
|
@@ -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.
|
|
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",
|