ai-fly 0.3.0 → 0.3.1-alpha.2
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/ai-fly.js +3 -3
- package/dist/{forget-B17zGwj4.mjs → forget-BXL2x4Wv.mjs} +1 -1
- package/dist/{import-tilxdbTH.mjs → import-rpBFh_pa.mjs} +3 -3
- package/dist/{join-DsKUAibH.mjs → join-CaFx5yoF.mjs} +2 -2
- package/dist/{join-hyqiwEek.mjs → join-u1DqTU3U.mjs} +5 -3
- package/dist/{key-CNufbB__.mjs → key-KcUU3yhJ.mjs} +2 -2
- package/dist/{ports-rRppuBgo.mjs → ports-BRXd5UXh.mjs} +4 -2
- package/dist/{run-bbDjqVLr.mjs → run-3pdPsD8e.mjs} +2 -2
- package/dist/{runtime-D_nxUEjd.mjs → runtime-DNs9GtGp.mjs} +21 -2
- package/dist/{secret-SA37VND5.mjs → secret-COdVHzjH.mjs} +1 -1
- package/dist/{status-B-j454u2.mjs → status-Bttb9aPj.mjs} +2 -2
- package/dist/{store-BiTS2FTl.mjs → store-CQShpYar.mjs} +25 -5
- package/dist/{test-Bc0OKBAi.mjs → test-mlaZC1y5.mjs} +2 -2
- package/package.json +1 -1
package/dist/ai-fly.js
CHANGED
|
@@ -61,8 +61,8 @@ async function dispatch(command, rest) {
|
|
|
61
61
|
const lazy = (load) => {
|
|
62
62
|
return (argv, ctx) => load().then((m) => m.run([...argv], ctx));
|
|
63
63
|
};
|
|
64
|
-
return match(command).with("serve", () => lazy(() => import("./serve-DSBlodTF.mjs"))(rest)).with("daemon", () => lazy(() => import("./daemon-ZWUSWAgM.mjs"))(rest)).with("presets", () => lazy(() => import("./presets-C73CAMZ6.mjs"))(rest)).with("secret", () => lazy(() => import("./secret-
|
|
65
|
-
if (rest[0] === "add") return lazy(() => import("./key-
|
|
64
|
+
return match(command).with("serve", () => lazy(() => import("./serve-DSBlodTF.mjs"))(rest)).with("daemon", () => lazy(() => import("./daemon-ZWUSWAgM.mjs"))(rest)).with("presets", () => lazy(() => import("./presets-C73CAMZ6.mjs"))(rest)).with("secret", () => lazy(() => import("./secret-COdVHzjH.mjs"))(rest)).with("settings", async () => (await import("./settings-DU0YqNDt.mjs")).runAsSettings(rest, {})).with("relay", async () => (await import("./settings-DU0YqNDt.mjs")).runAsRelay(rest, {})).with("test", () => lazy(() => import("./test-mlaZC1y5.mjs"))(rest)).with("service", () => lazy(() => import("./service-B5uORhl3.mjs"))(rest)).with("group", () => lazy(() => import("./group-B96IKEDF.mjs"))(rest)).with("share", () => lazy(() => import("./share-Cug0944N.mjs"))(rest)).with("revoke", () => lazy(() => import("./revoke-CkpvK33Q.mjs"))(rest)).with("join", () => lazy(() => import("./join-CaFx5yoF.mjs"))(rest)).with("import", () => lazy(() => import("./import-rpBFh_pa.mjs"))(rest)).with("run", () => lazy(() => import("./run-3pdPsD8e.mjs"))(rest)).with("ports", () => lazy(() => import("./ports-BRXd5UXh.mjs"))(rest)).with("forget", () => lazy(() => import("./forget-BXL2x4Wv.mjs"))(rest)).with("key", () => {
|
|
65
|
+
if (rest[0] === "add") return lazy(() => import("./key-KcUU3yhJ.mjs"))(rest);
|
|
66
66
|
return lazy(() => import("./key-BiEp995L.mjs"))(rest);
|
|
67
67
|
}).with("status", async () => {
|
|
68
68
|
const [scope, ...tail] = rest;
|
|
@@ -71,7 +71,7 @@ async function dispatch(command, rest) {
|
|
|
71
71
|
const argv = scope === "provider" || scope === "consumer" ? tail : rest;
|
|
72
72
|
let code = 0;
|
|
73
73
|
if (wantProvider) code = Math.max(code, await lazy(() => import("./status-SkNsxGnk.mjs"))(argv));
|
|
74
|
-
if (wantConsumer) code = Math.max(code, await lazy(() => import("./status-
|
|
74
|
+
if (wantConsumer) code = Math.max(code, await lazy(() => import("./status-Bttb9aPj.mjs"))(argv));
|
|
75
75
|
if (!wantProvider && !wantConsumer) process.stdout.write("nothing to report: no provider storage and no imported consumers\n");
|
|
76
76
|
return code;
|
|
77
77
|
}).otherwise(() => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { n as UsageError } from "./errors-BGtxPkM7.mjs";
|
|
2
2
|
import { n as parseArgv } from "./args-CSxXW9eD.mjs";
|
|
3
|
-
import { d as removeKeyring, n as consumersRoot } from "./store-
|
|
3
|
+
import { d as removeKeyring, n as consumersRoot } from "./store-CQShpYar.mjs";
|
|
4
4
|
import { n as ctxHomedir, r as ctxOut } from "./common-CgxCXeTt.mjs";
|
|
5
5
|
import { homedir } from "node:os";
|
|
6
6
|
//#region src/cli/commands/consumer/forget.ts
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { n as UsageError } from "./errors-BGtxPkM7.mjs";
|
|
2
2
|
import { n as parseArgv } from "./args-CSxXW9eD.mjs";
|
|
3
|
-
import { n as consumersRoot, s as listKeyrings } from "./store-
|
|
3
|
+
import { n as consumersRoot, s as listKeyrings } from "./store-CQShpYar.mjs";
|
|
4
4
|
import { a as ringsForRun, i as printListeners, n as ctxHomedir, o as waitForSignals, r as ctxOut, t as createSdkFabricFactory } from "./common-CgxCXeTt.mjs";
|
|
5
|
-
import { a as importLink, i as formatLinkPreview, n as decodeShareLink, r as formatKeyringSummary } from "./join-
|
|
6
|
-
import { n as createFabricProviderTransport, t as startEngine } from "./runtime-
|
|
5
|
+
import { a as importLink, i as formatLinkPreview, n as decodeShareLink, r as formatKeyringSummary } from "./join-u1DqTU3U.mjs";
|
|
6
|
+
import { n as createFabricProviderTransport, t as startEngine } from "./runtime-DNs9GtGp.mjs";
|
|
7
7
|
import { homedir } from "node:os";
|
|
8
8
|
//#region src/cli/commands/consumer/import.ts
|
|
9
9
|
async function run(argv, ctx = {}) {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { n as UsageError } from "./errors-BGtxPkM7.mjs";
|
|
2
2
|
import { n as parseArgv } from "./args-CSxXW9eD.mjs";
|
|
3
|
-
import { n as consumersRoot } from "./store-
|
|
3
|
+
import { n as consumersRoot } from "./store-CQShpYar.mjs";
|
|
4
4
|
import { n as ctxHomedir, r as ctxOut, t as createSdkFabricFactory } from "./common-CgxCXeTt.mjs";
|
|
5
|
-
import { o as joinDevice } from "./join-
|
|
5
|
+
import { o as joinDevice } from "./join-u1DqTU3U.mjs";
|
|
6
6
|
import { homedir } from "node:os";
|
|
7
7
|
//#region src/cli/commands/consumer/join.ts
|
|
8
8
|
async function run(argv, ctx = {}) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as CliError } from "./errors-BGtxPkM7.mjs";
|
|
2
2
|
import { o as decodeZ32, s as randomZ32 } from "./rpc-contract-5qyWzT5t.mjs";
|
|
3
3
|
import { l as SERVICE_ENTRY_SCHEMA } from "./frames-BDqvhB2Y.mjs";
|
|
4
|
-
import { a as hasFabricIdentity, c as loadKeyring, f as saveKeyring,
|
|
4
|
+
import { a as hasFabricIdentity, c as loadKeyring, f as saveKeyring, h as upsertKey, i as findKeyringDir, l as mergeImportView, o as keyringDir, r as fabricDir } from "./store-CQShpYar.mjs";
|
|
5
5
|
import { existsSync, mkdirSync, renameSync, rmSync } from "node:fs";
|
|
6
6
|
import { join } from "node:path";
|
|
7
7
|
import { z } from "zod";
|
|
@@ -91,7 +91,8 @@ async function joinDevice(token, root, deps) {
|
|
|
91
91
|
relayUrls: [],
|
|
92
92
|
keys: [],
|
|
93
93
|
services: [],
|
|
94
|
-
ports: {}
|
|
94
|
+
ports: {},
|
|
95
|
+
actualPorts: {}
|
|
95
96
|
};
|
|
96
97
|
if (existing === void 0) saveKeyring(root, ring);
|
|
97
98
|
return {
|
|
@@ -112,7 +113,8 @@ async function joinDevice(token, root, deps) {
|
|
|
112
113
|
relayUrls: [],
|
|
113
114
|
keys: [],
|
|
114
115
|
services: [],
|
|
115
|
-
ports: {}
|
|
116
|
+
ports: {},
|
|
117
|
+
actualPorts: {}
|
|
116
118
|
};
|
|
117
119
|
const existing = loadKeyring(root, provider.endpointId);
|
|
118
120
|
saveKeyring(root, existing ?? ring);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { n as UsageError } from "./errors-BGtxPkM7.mjs";
|
|
2
2
|
import { n as parseArgv } from "./args-CSxXW9eD.mjs";
|
|
3
|
-
import { n as consumersRoot } from "./store-
|
|
3
|
+
import { n as consumersRoot } from "./store-CQShpYar.mjs";
|
|
4
4
|
import { n as ctxHomedir, r as ctxOut } from "./common-CgxCXeTt.mjs";
|
|
5
|
-
import { r as formatKeyringSummary, t as addKey } from "./join-
|
|
5
|
+
import { r as formatKeyringSummary, t as addKey } from "./join-u1DqTU3U.mjs";
|
|
6
6
|
import { homedir } from "node:os";
|
|
7
7
|
//#region src/cli/commands/consumer/key.ts
|
|
8
8
|
async function run(argv, ctx = {}) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { n as UsageError } from "./errors-BGtxPkM7.mjs";
|
|
2
2
|
import { n as parseArgv } from "./args-CSxXW9eD.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { m as setPort, n as consumersRoot, s as listKeyrings } from "./store-CQShpYar.mjs";
|
|
4
4
|
import { n as ctxHomedir, r as ctxOut } from "./common-CgxCXeTt.mjs";
|
|
5
5
|
import { i as validatePortArg, n as desiredPortFor } from "./ports-pI59MtmI.mjs";
|
|
6
6
|
import { homedir } from "node:os";
|
|
@@ -28,7 +28,9 @@ async function run(argv, ctx = {}) {
|
|
|
28
28
|
if (ring.services.length === 0) out(" (no services known yet - run the gateway to fetch the catalog)");
|
|
29
29
|
for (const s of ring.services) {
|
|
30
30
|
const pinned = ring.ports[s.serviceId] !== void 0;
|
|
31
|
-
|
|
31
|
+
const actual = ring.actualPorts[s.serviceId];
|
|
32
|
+
const note = actual !== void 0 && actual !== desiredPortFor(s, ring.ports) ? ` [LIVE: ${actual} - auto-shifted by running gateway]` : "";
|
|
33
|
+
out(` ${s.serviceId} ${s.name} port ${desiredPortFor(s, ring.ports)} [${pinned ? "pinned" : `default ${s.defaultPort}`}]${note}`);
|
|
32
34
|
}
|
|
33
35
|
}
|
|
34
36
|
return 0;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { n as UsageError, r as reportCliError, t as CliError } from "./errors-BGtxPkM7.mjs";
|
|
2
2
|
import { n as parseArgv } from "./args-CSxXW9eD.mjs";
|
|
3
|
-
import { n as consumersRoot, s as listKeyrings } from "./store-
|
|
3
|
+
import { n as consumersRoot, s as listKeyrings } from "./store-CQShpYar.mjs";
|
|
4
4
|
import { a as ringsForRun, i as printListeners, n as ctxHomedir, o as waitForSignals, r as ctxOut, t as createSdkFabricFactory } from "./common-CgxCXeTt.mjs";
|
|
5
|
-
import { n as createFabricProviderTransport, t as startEngine } from "./runtime-
|
|
5
|
+
import { n as createFabricProviderTransport, t as startEngine } from "./runtime-DNs9GtGp.mjs";
|
|
6
6
|
import { a as killHard, c as terminateGracefully, i as isAlive, l as writeLastStart, n as daemonDir, o as readLastStart, r as ensureDaemonDir, s as readPid, t as clearPid, u as writePid } from "./daemon-state-CYkiW3SA.mjs";
|
|
7
7
|
import { openSync, readFileSync } from "node:fs";
|
|
8
8
|
import { resolve } from "node:path";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { i as FRAME_TYPE, o as HTTP_METHODS } from "./frames-BDqvhB2Y.mjs";
|
|
2
|
-
import { c as loadKeyring, f as saveKeyring, t as applyCatalog, u as reconcileKeyMetadata } from "./store-
|
|
2
|
+
import { c as loadKeyring, f as saveKeyring, p as setActualPorts, t as applyCatalog, u as reconcileKeyMetadata } from "./store-CQShpYar.mjs";
|
|
3
3
|
import { i as splitBody, n as FabricWireAdapter, t as WireSession } from "./mux-CS1zhF4W.mjs";
|
|
4
4
|
import { n as desiredPortFor, r as listenWithFallback, t as LOOPBACK_HOST } from "./ports-pI59MtmI.mjs";
|
|
5
5
|
import { Hono } from "hono";
|
|
@@ -371,7 +371,12 @@ var ProviderConnection = class {
|
|
|
371
371
|
seen.add(s.serviceId);
|
|
372
372
|
services.push(s);
|
|
373
373
|
}
|
|
374
|
-
|
|
374
|
+
const persisted = loadKeyring(this.root, this.ring.endpointId);
|
|
375
|
+
const base = persisted === void 0 ? this.ring : {
|
|
376
|
+
...this.ring,
|
|
377
|
+
actualPorts: persisted.actualPorts
|
|
378
|
+
};
|
|
379
|
+
let ring = applyCatalog(base, {
|
|
375
380
|
alias: header.alias,
|
|
376
381
|
relayUrls: header.relayUrls,
|
|
377
382
|
services
|
|
@@ -1358,6 +1363,20 @@ async function startEngine(opts) {
|
|
|
1358
1363
|
});
|
|
1359
1364
|
managerRef.current = manager;
|
|
1360
1365
|
for (const ring of opts.rings) if (ring.services.length > 0) await gateway.syncProviderServices(ring.endpointId, ring.alias, ring.services, ring.ports);
|
|
1366
|
+
const live = new Map(gateway.listenerInfo().map((l) => [l.serviceId, l.port]));
|
|
1367
|
+
for (const ring of opts.rings) {
|
|
1368
|
+
const actual = {};
|
|
1369
|
+
for (const s of ring.services) {
|
|
1370
|
+
const port = live.get(s.serviceId);
|
|
1371
|
+
if (port !== void 0) actual[s.serviceId] = port;
|
|
1372
|
+
}
|
|
1373
|
+
if (Object.keys(actual).length === 0) continue;
|
|
1374
|
+
try {
|
|
1375
|
+
setActualPorts(opts.consumersRoot, ring.endpointId, actual);
|
|
1376
|
+
} catch (err) {
|
|
1377
|
+
opts.onNotice?.(`warning: could not persist actual ports for '${ring.alias}': ${err.message}`);
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1361
1380
|
manager.start();
|
|
1362
1381
|
return {
|
|
1363
1382
|
manager,
|
|
@@ -62,7 +62,7 @@ async function set(options, positionals, home) {
|
|
|
62
62
|
value = (await readHidden(`value for '${name}': `)).trim();
|
|
63
63
|
}
|
|
64
64
|
if (value === "") throw new UsageError("error: secret value must not be empty");
|
|
65
|
-
const entry = SecretsStore.open(resolveDataDir(
|
|
65
|
+
const entry = SecretsStore.open(resolveDataDir(str(options.data), home)).set(name, value, { bearerPrefix: options["no-bearer"] !== true });
|
|
66
66
|
process.stdout.write(`secret set: ${entry.name} (bearer prefix ${entry.bearerPrefix ? "on" : "off"}) - inject with service add --secret ${entry.name}\n`);
|
|
67
67
|
return 0;
|
|
68
68
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { n as parseArgv } from "./args-CSxXW9eD.mjs";
|
|
2
|
-
import { n as consumersRoot, s as listKeyrings } from "./store-
|
|
2
|
+
import { n as consumersRoot, s as listKeyrings } from "./store-CQShpYar.mjs";
|
|
3
3
|
import { a as ringsForRun, n as ctxHomedir, r as ctxOut, t as createSdkFabricFactory } from "./common-CgxCXeTt.mjs";
|
|
4
|
-
import { n as createFabricProviderTransport, t as startEngine } from "./runtime-
|
|
4
|
+
import { n as createFabricProviderTransport, t as startEngine } from "./runtime-DNs9GtGp.mjs";
|
|
5
5
|
import { homedir } from "node:os";
|
|
6
6
|
//#region src/cli/commands/consumer/status.ts
|
|
7
7
|
/** 状态显示名(spec 六态)。 */
|
|
@@ -18,7 +18,8 @@ const KEYRING_SCHEMA = z.object({
|
|
|
18
18
|
group: z.string().max(256)
|
|
19
19
|
})),
|
|
20
20
|
services: z.array(SERVICE_ENTRY_SCHEMA),
|
|
21
|
-
ports: z.record(z.string(), z.number().int().min(0).max(65535))
|
|
21
|
+
ports: z.record(z.string(), z.number().int().min(0).max(65535)),
|
|
22
|
+
actualPorts: z.record(z.string(), z.number().int().min(0).max(65535)).default({})
|
|
22
23
|
});
|
|
23
24
|
/** 消费侧数据根:默认 ~/.aifly/consumers;--data <dir> 时以 <dir> 为根(CLI 语义)。 */
|
|
24
25
|
function consumersRoot(dataDir, base = homedir()) {
|
|
@@ -178,7 +179,8 @@ function mergeImportView(base, payload, key) {
|
|
|
178
179
|
relayUrls: [],
|
|
179
180
|
keys: [],
|
|
180
181
|
services: [],
|
|
181
|
-
ports: {}
|
|
182
|
+
ports: {},
|
|
183
|
+
actualPorts: {}
|
|
182
184
|
};
|
|
183
185
|
const merged = new Map(ring.services.map((s) => [s.serviceId, s]));
|
|
184
186
|
for (const s of payload.services) merged.set(s.serviceId, s);
|
|
@@ -201,11 +203,14 @@ function applyCatalog(ring, patch) {
|
|
|
201
203
|
const alive = new Set(patch.services.map((s) => s.serviceId));
|
|
202
204
|
const ports = {};
|
|
203
205
|
for (const [serviceId, port] of Object.entries(ring.ports)) if (alive.has(serviceId)) ports[serviceId] = port;
|
|
206
|
+
const actualPorts = {};
|
|
207
|
+
for (const [serviceId, port] of Object.entries(ring.actualPorts ?? {})) if (alive.has(serviceId)) actualPorts[serviceId] = port;
|
|
204
208
|
const next = {
|
|
205
209
|
...ring,
|
|
206
210
|
relayUrls: [...patch.relayUrls],
|
|
207
211
|
services: [...patch.services],
|
|
208
|
-
ports
|
|
212
|
+
ports,
|
|
213
|
+
actualPorts
|
|
209
214
|
};
|
|
210
215
|
if (patch.alias !== void 0 && patch.alias !== "") next.alias = patch.alias;
|
|
211
216
|
return next;
|
|
@@ -246,6 +251,20 @@ function setPort(root, endpointId, serviceId, port) {
|
|
|
246
251
|
saveKeyring(root, next);
|
|
247
252
|
return next;
|
|
248
253
|
}
|
|
254
|
+
/** 网关实际监听端口回写(引擎启动路径;整体替换并修剪到存活服务)。 */
|
|
255
|
+
function setActualPorts(root, endpointId, actual) {
|
|
256
|
+
const existing = loadKeyring(root, endpointId);
|
|
257
|
+
if (existing === void 0) throw new CliError(`error: provider '${endpointId}' not found`);
|
|
258
|
+
const alive = new Set(existing.services.map((s) => s.serviceId));
|
|
259
|
+
const actualPorts = {};
|
|
260
|
+
for (const [serviceId, port] of Object.entries(actual)) if (alive.has(serviceId)) actualPorts[serviceId] = port;
|
|
261
|
+
const next = {
|
|
262
|
+
...existing,
|
|
263
|
+
actualPorts
|
|
264
|
+
};
|
|
265
|
+
saveKeyring(root, next);
|
|
266
|
+
return next;
|
|
267
|
+
}
|
|
249
268
|
/** forget:整环删除(钥环 + fabric 身份目录;提供方侧撤销需提供方操作)。 */
|
|
250
269
|
function removeKeyring(root, ref) {
|
|
251
270
|
const dir = findKeyringDir(root, ref);
|
|
@@ -269,9 +288,10 @@ function removeKeyring(root, ref) {
|
|
|
269
288
|
relayUrls: [],
|
|
270
289
|
keys: [],
|
|
271
290
|
services: [],
|
|
272
|
-
ports: {}
|
|
291
|
+
ports: {},
|
|
292
|
+
actualPorts: {}
|
|
273
293
|
}
|
|
274
294
|
};
|
|
275
295
|
}
|
|
276
296
|
//#endregion
|
|
277
|
-
export { hasFabricIdentity as a, loadKeyring as c, removeKeyring as d, saveKeyring as f, findKeyringDir as i, mergeImportView as l,
|
|
297
|
+
export { hasFabricIdentity as a, loadKeyring as c, removeKeyring as d, saveKeyring as f, upsertKey as h, findKeyringDir as i, mergeImportView as l, setPort as m, consumersRoot as n, keyringDir as o, setActualPorts as p, fabricDir as r, listKeyrings as s, applyCatalog as t, reconcileKeyMetadata as u };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { n as UsageError, r as reportCliError } from "./errors-BGtxPkM7.mjs";
|
|
2
2
|
import { n as parseArgv } from "./args-CSxXW9eD.mjs";
|
|
3
|
-
import { n as consumersRoot, s as listKeyrings } from "./store-
|
|
3
|
+
import { n as consumersRoot, s as listKeyrings } from "./store-CQShpYar.mjs";
|
|
4
4
|
import { n as desiredPortFor } from "./ports-pI59MtmI.mjs";
|
|
5
5
|
import { n as testLocalService } from "./local-test-C3HGjGY3.mjs";
|
|
6
6
|
import { homedir } from "node:os";
|
|
@@ -39,7 +39,7 @@ async function run(argv, ctx = {}) {
|
|
|
39
39
|
const serviceName = options.service === void 0 ? void 0 : String(options.service);
|
|
40
40
|
const target = serviceName === void 0 ? flat[0] : flat.find(({ svc }) => svc.name === serviceName || svc.serviceId === serviceName);
|
|
41
41
|
if (target === void 0) throw new UsageError(`error: service '${serviceName}' not found (imported: ${flat.map((f) => f.svc.name).join(", ")})`);
|
|
42
|
-
const port = desiredPortFor(target.svc, target.ring.ports);
|
|
42
|
+
const port = target.ring.actualPorts[target.svc.serviceId] ?? desiredPortFor(target.svc, target.ring.ports);
|
|
43
43
|
const result = await testLocalService({
|
|
44
44
|
port,
|
|
45
45
|
form,
|