@frontmcp/utils 1.10.16 → 1.11.0-rc.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/README.md +1 -0
- package/browser/async-context/browser-async-context.mjs +177 -0
- package/browser/async-context/index.mjs +15 -0
- package/browser/async-context/overlap-error.mjs +10 -0
- package/browser/class-brand/class-brand.mjs +27 -0
- package/browser/class-brand/index.mjs +6 -0
- package/browser/content/content.mjs +151 -0
- package/browser/content/index.mjs +8 -0
- package/browser/crypto/browser.mjs +103 -0
- package/browser/crypto/encrypted-blob.mjs +119 -0
- package/browser/crypto/hmac-signing.mjs +70 -0
- package/browser/crypto/index.mjs +281 -0
- package/browser/crypto/jwt-alg.mjs +40 -0
- package/browser/crypto/key-persistence/factory.mjs +134 -0
- package/browser/crypto/key-persistence/index.mjs +26 -0
- package/browser/crypto/key-persistence/key-persistence.mjs +185 -0
- package/browser/crypto/key-persistence/schemas.mjs +82 -0
- package/browser/crypto/pkce/index.mjs +25 -0
- package/browser/crypto/pkce/pkce.mjs +59 -0
- package/browser/crypto/runtime.mjs +17 -0
- package/browser/crypto/secret-persistence/index.mjs +29 -0
- package/browser/crypto/secret-persistence/persistence.mjs +181 -0
- package/browser/crypto/secret-persistence/schema.mjs +40 -0
- package/browser/crypto/web-rsa.mjs +36 -0
- package/browser/env/browser-env.mjs +56 -0
- package/browser/env/browser-runtime-context.mjs +85 -0
- package/browser/env/build-target.mjs +25 -0
- package/browser/env/edge-scope.mjs +17 -0
- package/browser/env/index.mjs +47 -0
- package/browser/env/provider.mjs +46 -0
- package/browser/escape/escape.mjs +43 -0
- package/browser/escape/index.mjs +9 -0
- package/browser/event-emitter/browser-event-emitter.mjs +49 -0
- package/browser/event-emitter/index.mjs +5 -0
- package/browser/fs/fs.mjs +227 -0
- package/browser/fs/index.mjs +65 -0
- package/browser/http/cookie.mjs +85 -0
- package/browser/http/http.mjs +18 -0
- package/browser/http/index.mjs +11 -0
- package/browser/index.mjs +526 -0
- package/browser/llm/index.mjs +5 -0
- package/browser/llm/llm-tool-handlers.mjs +55 -0
- package/browser/machine-id/index.mjs +7 -0
- package/browser/machine-id/machine-id.mjs +108 -0
- package/browser/naming/index.mjs +10 -0
- package/browser/naming/naming.mjs +68 -0
- package/browser/net/index.mjs +10 -0
- package/browser/net/ip-address.mjs +128 -0
- package/browser/net/redirect.mjs +7 -0
- package/browser/path/browser-path.mjs +47 -0
- package/browser/path/index.mjs +24 -0
- package/browser/path/path.mjs +13 -0
- package/browser/process/child-process.mjs +23 -0
- package/browser/process/index.mjs +15 -0
- package/browser/process/owned-process.mjs +175 -0
- package/browser/process/process-tree.mjs +149 -0
- package/browser/process/process-watch.mjs +48 -0
- package/browser/redaction/index.mjs +6 -0
- package/browser/redaction/redaction.mjs +74 -0
- package/browser/regex/index.mjs +3 -0
- package/browser/regex/patterns.mjs +167 -0
- package/browser/regex/safe-regex.mjs +93 -0
- package/browser/serialization/index.mjs +5 -0
- package/browser/serialization/serialization.mjs +22 -0
- package/browser/stdio-bridge/errors.mjs +27 -0
- package/browser/stdio-bridge/index.mjs +24 -0
- package/browser/stdio-bridge/list-changed.mjs +21 -0
- package/browser/stdio-bridge/state-machine.mjs +211 -0
- package/browser/stdio-bridge/stdio-framer.mjs +91 -0
- package/browser/stdio-bridge/upstream-client.mjs +189 -0
- package/browser/storage/adapters/base.mjs +139 -0
- package/browser/storage/adapters/cloudflare-kv.mjs +149 -0
- package/browser/storage/adapters/filesystem.mjs +279 -0
- package/browser/storage/adapters/index.mjs +23 -0
- package/browser/storage/adapters/indexeddb.mjs +198 -0
- package/browser/storage/adapters/localstorage.mjs +222 -0
- package/browser/storage/adapters/memory.mjs +350 -0
- package/browser/storage/adapters/redis.mjs +497 -0
- package/browser/storage/adapters/upstash.mjs +299 -0
- package/browser/storage/adapters/vercel-kv.mjs +226 -0
- package/browser/storage/change-feed.mjs +189 -0
- package/browser/storage/encrypted-typed-storage.mjs +329 -0
- package/browser/storage/errors.mjs +92 -0
- package/browser/storage/factory.mjs +155 -0
- package/browser/storage/index.mjs +96 -0
- package/browser/storage/namespace.mjs +186 -0
- package/browser/storage/redis-error-listener.mjs +32 -0
- package/browser/storage/redis-url.mjs +66 -0
- package/browser/storage/typed-storage.mjs +171 -0
- package/browser/storage/utils/compare-and-delete.mjs +9 -0
- package/browser/storage/utils/index.mjs +24 -0
- package/browser/storage/utils/pattern.mjs +68 -0
- package/browser/storage/utils/ttl.mjs +48 -0
- package/browser/uri/index.mjs +19 -0
- package/browser/uri/uri-template.mjs +86 -0
- package/browser/uri/uri-validation.mjs +18 -0
- package/crypto/browser.d.ts +1 -13
- package/crypto/browser.js +1 -1
- package/crypto/node.js +1 -1
- package/crypto/web-rsa.d.ts +13 -0
- package/env/browser-runtime-context.js +1 -1
- package/env/runtime-context.js +1 -1
- package/esm/crypto/browser.mjs +1 -1
- package/esm/crypto/node.mjs +1 -1
- package/esm/env/browser-runtime-context.mjs +1 -1
- package/esm/env/runtime-context.mjs +1 -1
- package/esm/index.mjs +1 -1
- package/fs/fs.d.ts +20 -4
- package/fs/index.d.ts +1 -1
- package/index.d.ts +9 -3
- package/index.js +1 -1
- package/package.json +24 -8
- package/process/child-process.d.ts +2 -0
- package/process/index.d.ts +6 -0
- package/process/owned-process.d.ts +59 -0
- package/process/process-tree.d.ts +78 -0
- package/process/process-watch.d.ts +41 -0
- package/redaction/index.d.ts +2 -0
- package/redaction/redaction.d.ts +24 -0
- package/stdio-bridge/errors.d.ts +23 -0
- package/stdio-bridge/index.d.ts +9 -0
- package/stdio-bridge/list-changed.d.ts +3 -0
- package/stdio-bridge/state-machine.d.ts +61 -0
- package/stdio-bridge/stdio-framer.d.ts +49 -0
- package/stdio-bridge/types.d.ts +10 -0
- package/stdio-bridge/upstream-client.d.ts +66 -0
- package/storage/adapters/base.d.ts +2 -0
- package/storage/adapters/memory.d.ts +1 -0
- package/storage/adapters/redis.d.ts +3 -0
- package/storage/change-feed.d.ts +47 -0
- package/storage/factory.d.ts +6 -0
- package/storage/index.d.ts +3 -1
- package/storage/namespace.d.ts +2 -0
- package/storage/types.d.ts +46 -1
- package/storage/utils/compare-and-delete.d.ts +2 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// libs/utils/src/naming/naming.ts
|
|
2
|
+
import { trimBoth } from "../regex/index.mjs";
|
|
3
|
+
function splitWords(input) {
|
|
4
|
+
const parts = [];
|
|
5
|
+
let buff = "";
|
|
6
|
+
for (let i = 0; i < input.length; i++) {
|
|
7
|
+
const ch = input[i];
|
|
8
|
+
const isAlphaNum = /[A-Za-z0-9]/.test(ch);
|
|
9
|
+
if (!isAlphaNum) {
|
|
10
|
+
if (buff) {
|
|
11
|
+
parts.push(buff);
|
|
12
|
+
buff = "";
|
|
13
|
+
}
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
if (buff && /[a-z]/.test(buff[buff.length - 1]) && /[A-Z]/.test(ch)) {
|
|
17
|
+
parts.push(buff);
|
|
18
|
+
buff = ch;
|
|
19
|
+
} else {
|
|
20
|
+
buff += ch;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
if (buff) parts.push(buff);
|
|
24
|
+
return parts;
|
|
25
|
+
}
|
|
26
|
+
function toCase(words, kind) {
|
|
27
|
+
const safe = words.filter(Boolean);
|
|
28
|
+
switch (kind) {
|
|
29
|
+
case "snake":
|
|
30
|
+
return safe.map((w) => w.toLowerCase()).join("_");
|
|
31
|
+
case "kebab":
|
|
32
|
+
return safe.map((w) => w.toLowerCase()).join("-");
|
|
33
|
+
case "dot":
|
|
34
|
+
return safe.map((w) => w.toLowerCase()).join(".");
|
|
35
|
+
case "camel":
|
|
36
|
+
if (safe.length === 0) return "";
|
|
37
|
+
return safe[0].toLowerCase() + safe.slice(1).map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join("");
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function sepFor(kind) {
|
|
41
|
+
return kind === "snake" ? "_" : kind === "kebab" ? "-" : kind === "dot" ? "." : "";
|
|
42
|
+
}
|
|
43
|
+
function shortHash(s) {
|
|
44
|
+
let h = 5381;
|
|
45
|
+
for (let i = 0; i < s.length; i++) h = (h << 5) + h + s.charCodeAt(i);
|
|
46
|
+
return (h >>> 0).toString(16).slice(-6).padStart(6, "0");
|
|
47
|
+
}
|
|
48
|
+
function ensureMaxLen(name, max) {
|
|
49
|
+
if (name.length <= max) return name;
|
|
50
|
+
const hash = shortHash(name);
|
|
51
|
+
const lastSep = Math.max(name.lastIndexOf("_"), name.lastIndexOf("-"), name.lastIndexOf("."), name.lastIndexOf("/"));
|
|
52
|
+
const tail = lastSep > 0 ? name.slice(lastSep + 1) : name.slice(-Math.max(3, Math.min(16, Math.floor(max / 4))));
|
|
53
|
+
const budget = Math.max(1, max - (1 + hash.length + 1 + tail.length));
|
|
54
|
+
const prefix = name.slice(0, budget);
|
|
55
|
+
return `${prefix}-${hash}-${tail}`.slice(0, max);
|
|
56
|
+
}
|
|
57
|
+
function idFromString(name) {
|
|
58
|
+
const cleaned = name.replace(/[^A-Za-z0-9_-]+/g, "-");
|
|
59
|
+
return trimBoth(cleaned, "-").slice(0, 64);
|
|
60
|
+
}
|
|
61
|
+
export {
|
|
62
|
+
ensureMaxLen,
|
|
63
|
+
idFromString,
|
|
64
|
+
sepFor,
|
|
65
|
+
shortHash,
|
|
66
|
+
splitWords,
|
|
67
|
+
toCase
|
|
68
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// libs/utils/src/net/index.ts
|
|
2
|
+
import { classifyIpAddress, expandIpv6, extractEmbeddedIpv4, parseIpv4 } from "./ip-address.mjs";
|
|
3
|
+
import { isRedirectResponse } from "./redirect.mjs";
|
|
4
|
+
export {
|
|
5
|
+
classifyIpAddress,
|
|
6
|
+
expandIpv6,
|
|
7
|
+
extractEmbeddedIpv4,
|
|
8
|
+
isRedirectResponse,
|
|
9
|
+
parseIpv4
|
|
10
|
+
};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// libs/utils/src/net/ip-address.ts
|
|
2
|
+
var IPV4_OCTET_PATTERN = /^(0|[1-9]\d{0,2})$/;
|
|
3
|
+
var IPV6_GROUP_PATTERN = /^[0-9a-f]{1,4}$/;
|
|
4
|
+
var IPV4_BLOCKS = compileBlocks(
|
|
5
|
+
[
|
|
6
|
+
["0.0.0.0/8", "this-network"],
|
|
7
|
+
["10.0.0.0/8", "private"],
|
|
8
|
+
["100.64.0.0/10", "carrier-grade-nat"],
|
|
9
|
+
["127.0.0.0/8", "loopback"],
|
|
10
|
+
["169.254.0.0/16", "link-local"],
|
|
11
|
+
["172.16.0.0/12", "private"],
|
|
12
|
+
["192.0.0.0/24", "ietf-protocol"],
|
|
13
|
+
["192.168.0.0/16", "private"],
|
|
14
|
+
["198.18.0.0/15", "benchmarking"],
|
|
15
|
+
["224.0.0.0/4", "multicast"],
|
|
16
|
+
["255.255.255.255/32", "reserved"],
|
|
17
|
+
["240.0.0.0/4", "reserved"]
|
|
18
|
+
],
|
|
19
|
+
parseIpv4
|
|
20
|
+
);
|
|
21
|
+
var IPV6_BLOCKS = compileBlocks(
|
|
22
|
+
[
|
|
23
|
+
["::/128", "unspecified"],
|
|
24
|
+
["::1/128", "loopback"],
|
|
25
|
+
["fd00:ec2::254/128", "cloud-metadata"],
|
|
26
|
+
["fc00::/7", "private"],
|
|
27
|
+
["fe80::/10", "link-local"],
|
|
28
|
+
["fec0::/10", "site-local"],
|
|
29
|
+
// RFC 8215 local-use NAT64: where the IPv4 address sits depends on the operator's prefix length.
|
|
30
|
+
["64:ff9b:1::/48", "local-use-nat64"],
|
|
31
|
+
["ff00::/8", "multicast"]
|
|
32
|
+
],
|
|
33
|
+
expandIpv6
|
|
34
|
+
);
|
|
35
|
+
var IPV4_EMBEDDINGS = compileBlocks(
|
|
36
|
+
[
|
|
37
|
+
["::ffff:0:0/96", 6],
|
|
38
|
+
["::ffff:0:0:0/96", 6],
|
|
39
|
+
["::/96", 6],
|
|
40
|
+
["64:ff9b::/96", 6],
|
|
41
|
+
["2002::/16", 1]
|
|
42
|
+
],
|
|
43
|
+
expandIpv6
|
|
44
|
+
);
|
|
45
|
+
function parseIpv4(address) {
|
|
46
|
+
const parts = address.split(".");
|
|
47
|
+
if (parts.length !== 4 || !parts.every((part) => IPV4_OCTET_PATTERN.test(part))) return void 0;
|
|
48
|
+
const octets = parts.map(Number);
|
|
49
|
+
return octets.every((octet) => octet <= 255) ? octets : void 0;
|
|
50
|
+
}
|
|
51
|
+
function expandIpv6(address) {
|
|
52
|
+
const zoneIndex = address.indexOf("%");
|
|
53
|
+
let text = (zoneIndex === -1 ? address : address.slice(0, zoneIndex)).toLowerCase();
|
|
54
|
+
const lastColonIndex = text.lastIndexOf(":");
|
|
55
|
+
const trailingSegment = text.slice(lastColonIndex + 1);
|
|
56
|
+
if (lastColonIndex !== -1 && trailingSegment.includes(".")) {
|
|
57
|
+
const octets = parseIpv4(trailingSegment);
|
|
58
|
+
if (!octets) return void 0;
|
|
59
|
+
const highGroup = (octets[0] << 8 | octets[1]).toString(16);
|
|
60
|
+
const lowGroup = (octets[2] << 8 | octets[3]).toString(16);
|
|
61
|
+
text = `${text.slice(0, lastColonIndex + 1)}${highGroup}:${lowGroup}`;
|
|
62
|
+
}
|
|
63
|
+
const halves = text.split("::");
|
|
64
|
+
if (halves.length > 2) return void 0;
|
|
65
|
+
const head = parseIpv6Groups(halves[0]);
|
|
66
|
+
if (!head) return void 0;
|
|
67
|
+
if (halves.length === 1) return head.length === 8 ? head : void 0;
|
|
68
|
+
const tail = parseIpv6Groups(halves[1]);
|
|
69
|
+
if (!tail || head.length + tail.length > 7) return void 0;
|
|
70
|
+
return [...head, ...new Array(8 - head.length - tail.length).fill(0), ...tail];
|
|
71
|
+
}
|
|
72
|
+
function extractEmbeddedIpv4(groups) {
|
|
73
|
+
const octets = findEmbeddedIpv4Octets(groups);
|
|
74
|
+
return octets ? octets.join(".") : void 0;
|
|
75
|
+
}
|
|
76
|
+
function classifyIpAddress(address) {
|
|
77
|
+
const isBracketed = address.startsWith("[") && address.endsWith("]");
|
|
78
|
+
const octets = isBracketed ? void 0 : parseIpv4(address);
|
|
79
|
+
if (octets) return classifyIpv4(octets);
|
|
80
|
+
const groups = expandIpv6(isBracketed ? address.slice(1, -1) : address);
|
|
81
|
+
if (!groups) return void 0;
|
|
82
|
+
const block = IPV6_BLOCKS.find((candidate) => matchesBlock(groups, candidate, 16));
|
|
83
|
+
if (block) return { family: 6, range: block.value, cidr: block.cidr };
|
|
84
|
+
const embeddedOctets = findEmbeddedIpv4Octets(groups);
|
|
85
|
+
if (!embeddedOctets) return { family: 6, range: "public" };
|
|
86
|
+
return { ...classifyIpv4(embeddedOctets), family: 6, embeddedIpv4: embeddedOctets.join(".") };
|
|
87
|
+
}
|
|
88
|
+
function classifyIpv4(octets) {
|
|
89
|
+
const block = IPV4_BLOCKS.find((candidate) => matchesBlock(octets, candidate, 8));
|
|
90
|
+
return block ? { family: 4, range: block.value, cidr: block.cidr } : { family: 4, range: "public" };
|
|
91
|
+
}
|
|
92
|
+
function findEmbeddedIpv4Octets(groups) {
|
|
93
|
+
const embedding = IPV4_EMBEDDINGS.find((block) => matchesBlock(groups, block, 16));
|
|
94
|
+
if (!embedding) return void 0;
|
|
95
|
+
const highGroup = groups[embedding.value];
|
|
96
|
+
const lowGroup = groups[embedding.value + 1];
|
|
97
|
+
return [highGroup >> 8, highGroup & 255, lowGroup >> 8, lowGroup & 255];
|
|
98
|
+
}
|
|
99
|
+
function parseIpv6Groups(part) {
|
|
100
|
+
if (part === "") return [];
|
|
101
|
+
const groups = part.split(":");
|
|
102
|
+
if (!groups.every((group) => IPV6_GROUP_PATTERN.test(group))) return void 0;
|
|
103
|
+
return groups.map((group) => parseInt(group, 16));
|
|
104
|
+
}
|
|
105
|
+
function matchesBlock(words, block, bitsPerWord) {
|
|
106
|
+
let remainingBits = block.prefixLength;
|
|
107
|
+
for (let index = 0; remainingBits > 0; index++) {
|
|
108
|
+
const comparedBits = Math.min(bitsPerWord, remainingBits);
|
|
109
|
+
const ignoredBits = bitsPerWord - comparedBits;
|
|
110
|
+
if (words[index] >> ignoredBits !== block.network[index] >> ignoredBits) return false;
|
|
111
|
+
remainingBits -= comparedBits;
|
|
112
|
+
}
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
function compileBlocks(entries, parseNetwork) {
|
|
116
|
+
return entries.map(([cidr, value]) => {
|
|
117
|
+
const [networkText, prefixText] = cidr.split("/");
|
|
118
|
+
const network = parseNetwork(networkText);
|
|
119
|
+
if (!network) throw new Error(`Invalid network in address block "${cidr}"`);
|
|
120
|
+
return { cidr, network, prefixLength: Number(prefixText), value };
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
export {
|
|
124
|
+
classifyIpAddress,
|
|
125
|
+
expandIpv6,
|
|
126
|
+
extractEmbeddedIpv4,
|
|
127
|
+
parseIpv4
|
|
128
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// libs/utils/src/path/browser-path.ts
|
|
2
|
+
function notAvailable(name) {
|
|
3
|
+
throw new Error(`path.${name}() is not available in browser environments`);
|
|
4
|
+
}
|
|
5
|
+
function resolve(..._segments) {
|
|
6
|
+
return notAvailable("resolve");
|
|
7
|
+
}
|
|
8
|
+
function join(..._segments) {
|
|
9
|
+
return notAvailable("join");
|
|
10
|
+
}
|
|
11
|
+
function basename(p, ext) {
|
|
12
|
+
const trimmed = p.replace(/[/\\]+$/, "");
|
|
13
|
+
const lastSep = Math.max(trimmed.lastIndexOf("/"), trimmed.lastIndexOf("\\"));
|
|
14
|
+
let base = lastSep >= 0 ? trimmed.slice(lastSep + 1) : trimmed;
|
|
15
|
+
if (ext && base.endsWith(ext)) {
|
|
16
|
+
base = base.slice(0, -ext.length);
|
|
17
|
+
}
|
|
18
|
+
return base;
|
|
19
|
+
}
|
|
20
|
+
function dirname(_p) {
|
|
21
|
+
return notAvailable("dirname");
|
|
22
|
+
}
|
|
23
|
+
function extname(p) {
|
|
24
|
+
const base = basename(p);
|
|
25
|
+
const dotIndex = base.lastIndexOf(".");
|
|
26
|
+
return dotIndex > 0 ? base.slice(dotIndex) : "";
|
|
27
|
+
}
|
|
28
|
+
function isAbsolute(p) {
|
|
29
|
+
if (!p) return false;
|
|
30
|
+
if (p.startsWith("/") || p.startsWith("\\")) return true;
|
|
31
|
+
if (/^[A-Za-z]:[\\/]/.test(p)) return true;
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
function pathToFileURL(_p) {
|
|
35
|
+
return notAvailable("pathToFileURL");
|
|
36
|
+
}
|
|
37
|
+
var sep = "/";
|
|
38
|
+
export {
|
|
39
|
+
basename,
|
|
40
|
+
dirname,
|
|
41
|
+
extname,
|
|
42
|
+
isAbsolute,
|
|
43
|
+
join,
|
|
44
|
+
pathToFileURL,
|
|
45
|
+
resolve,
|
|
46
|
+
sep
|
|
47
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// libs/utils/src/path/index.ts
|
|
2
|
+
import { trimSlashes, joinPath } from "./path.mjs";
|
|
3
|
+
import {
|
|
4
|
+
resolve,
|
|
5
|
+
join,
|
|
6
|
+
basename,
|
|
7
|
+
dirname,
|
|
8
|
+
extname,
|
|
9
|
+
isAbsolute,
|
|
10
|
+
pathToFileURL,
|
|
11
|
+
sep
|
|
12
|
+
} from "./browser-path.mjs";
|
|
13
|
+
export {
|
|
14
|
+
basename,
|
|
15
|
+
dirname,
|
|
16
|
+
extname,
|
|
17
|
+
isAbsolute,
|
|
18
|
+
joinPath,
|
|
19
|
+
join as pathJoin,
|
|
20
|
+
resolve as pathResolve,
|
|
21
|
+
pathToFileURL,
|
|
22
|
+
sep,
|
|
23
|
+
trimSlashes
|
|
24
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// libs/utils/src/path/path.ts
|
|
2
|
+
import { trimBoth } from "../regex/index.mjs";
|
|
3
|
+
function trimSlashes(s) {
|
|
4
|
+
return trimBoth(s ?? "", "/");
|
|
5
|
+
}
|
|
6
|
+
function joinPath(...parts) {
|
|
7
|
+
const cleaned = parts.map((p) => trimSlashes(p)).filter(Boolean);
|
|
8
|
+
return cleaned.length ? `/${cleaned.join("/")}` : "";
|
|
9
|
+
}
|
|
10
|
+
export {
|
|
11
|
+
joinPath,
|
|
12
|
+
trimSlashes
|
|
13
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
var __frontmcpModule = typeof process !== "undefined" && typeof process.getBuiltinModule === "function" ? process.getBuiltinModule("module") : undefined;
|
|
2
|
+
const require = __frontmcpModule ? __frontmcpModule.createRequire(import.meta.url || 'file:///') : undefined;
|
|
3
|
+
// __frontmcpCreateRequire
|
|
4
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
5
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
6
|
+
}) : x)(function(x) {
|
|
7
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
8
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
// libs/utils/src/process/child-process.ts
|
|
12
|
+
import { assertNode } from "../crypto/runtime.mjs";
|
|
13
|
+
var childProcess = null;
|
|
14
|
+
function getChildProcess() {
|
|
15
|
+
if (!childProcess) {
|
|
16
|
+
assertNode("Process operations");
|
|
17
|
+
childProcess = __require("child_process");
|
|
18
|
+
}
|
|
19
|
+
return childProcess;
|
|
20
|
+
}
|
|
21
|
+
export {
|
|
22
|
+
getChildProcess
|
|
23
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// libs/utils/src/process/index.ts
|
|
2
|
+
import { processTreeSpawnOptions, signalProcessTree, isProcessTreeAlive, stopProcessTree } from "./process-tree.mjs";
|
|
3
|
+
import { spawnOwned, reapOrphans, OwnedProcessError } from "./owned-process.mjs";
|
|
4
|
+
import { watchParent, watchPath } from "./process-watch.mjs";
|
|
5
|
+
export {
|
|
6
|
+
OwnedProcessError,
|
|
7
|
+
isProcessTreeAlive,
|
|
8
|
+
processTreeSpawnOptions,
|
|
9
|
+
reapOrphans,
|
|
10
|
+
signalProcessTree,
|
|
11
|
+
spawnOwned,
|
|
12
|
+
stopProcessTree,
|
|
13
|
+
watchParent,
|
|
14
|
+
watchPath
|
|
15
|
+
};
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
// libs/utils/src/process/owned-process.ts
|
|
2
|
+
import { getChildProcess } from "./child-process.mjs";
|
|
3
|
+
import { isPidRunning, sleep, stopProcessTree } from "./process-tree.mjs";
|
|
4
|
+
var OWNER_ENV_VAR = "FRONTMCP_OWNER";
|
|
5
|
+
var OWNER_ARG_PREFIX = "--frontmcp-owner=";
|
|
6
|
+
var MARKER_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
7
|
+
var DEFAULT_REAP_GRACE_MS = 2e3;
|
|
8
|
+
var KILL_WAIT_MS = 1e3;
|
|
9
|
+
var OwnedProcessError = class extends Error {
|
|
10
|
+
constructor(message) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = "OwnedProcessError";
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
function assertMarker(marker) {
|
|
16
|
+
if (!MARKER_PATTERN.test(marker)) {
|
|
17
|
+
throw new OwnedProcessError(
|
|
18
|
+
`Invalid process marker "${marker}": use up to 64 letters, digits, ".", "_" and "-", starting with a letter or digit`
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function spawnOwned(command, args, options) {
|
|
23
|
+
const { marker, ...spawnOptions } = options;
|
|
24
|
+
assertMarker(marker);
|
|
25
|
+
const owner = `${marker}:${process.pid}`;
|
|
26
|
+
const child = getChildProcess().spawn(command, [...args, `${OWNER_ARG_PREFIX}${owner}`], {
|
|
27
|
+
...spawnOptions,
|
|
28
|
+
env: { ...spawnOptions.env ?? process.env, [OWNER_ENV_VAR]: owner }
|
|
29
|
+
});
|
|
30
|
+
const stopsTree = spawnOptions.detached === true || process.platform === "win32";
|
|
31
|
+
return {
|
|
32
|
+
child,
|
|
33
|
+
stop: (signal = "SIGTERM", graceMs = 5e3) => stopsTree ? stopProcessTree(child, signal, graceMs) : stopChild(child, signal, graceMs)
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function exitsWithin(child, timeoutMs) {
|
|
37
|
+
if (child.exitCode !== null || child.signalCode !== null) return Promise.resolve(true);
|
|
38
|
+
return new Promise((resolve) => {
|
|
39
|
+
const onExit = () => {
|
|
40
|
+
clearTimeout(timer);
|
|
41
|
+
resolve(true);
|
|
42
|
+
};
|
|
43
|
+
const timer = setTimeout(() => {
|
|
44
|
+
child.removeListener("exit", onExit);
|
|
45
|
+
resolve(false);
|
|
46
|
+
}, timeoutMs);
|
|
47
|
+
child.once("exit", onExit);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
async function stopChild(child, signal, graceMs) {
|
|
51
|
+
if (child.pid === void 0) return true;
|
|
52
|
+
child.kill(signal);
|
|
53
|
+
if (await exitsWithin(child, graceMs)) return true;
|
|
54
|
+
child.kill("SIGKILL");
|
|
55
|
+
return exitsWithin(child, KILL_WAIT_MS);
|
|
56
|
+
}
|
|
57
|
+
function parseProcessList(output) {
|
|
58
|
+
const processes = [];
|
|
59
|
+
for (const line of output.split("\n")) {
|
|
60
|
+
const match = /^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(.*)$/.exec(line);
|
|
61
|
+
if (match) {
|
|
62
|
+
processes.push({
|
|
63
|
+
pid: Number(match[1]),
|
|
64
|
+
ppid: Number(match[2]),
|
|
65
|
+
pgid: Number(match[3]),
|
|
66
|
+
zombie: match[4].startsWith("Z"),
|
|
67
|
+
command: match[5]
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return processes;
|
|
72
|
+
}
|
|
73
|
+
function listProcesses() {
|
|
74
|
+
const result = getChildProcess().spawnSync("ps", ["-A", "-ww", "-o", "pid=,ppid=,pgid=,stat=,command="], {
|
|
75
|
+
encoding: "utf8"
|
|
76
|
+
});
|
|
77
|
+
if (result.error || result.status !== 0) {
|
|
78
|
+
const reason = result.error?.message ?? (String(result.stderr).trim() || `exit code ${result.status}`);
|
|
79
|
+
throw new OwnedProcessError(`Could not list processes with ps: ${reason}`);
|
|
80
|
+
}
|
|
81
|
+
return parseProcessList(String(result.stdout));
|
|
82
|
+
}
|
|
83
|
+
function ownerPidOf(command, marker) {
|
|
84
|
+
const prefix = `${OWNER_ARG_PREFIX}${marker}:`;
|
|
85
|
+
const tag = command.split(/\s+/).find((token) => token.startsWith(prefix));
|
|
86
|
+
const pid = tag?.slice(prefix.length);
|
|
87
|
+
return pid && /^\d+$/.test(pid) ? Number(pid) : void 0;
|
|
88
|
+
}
|
|
89
|
+
function ownLineage(processes) {
|
|
90
|
+
const byPid = new Map(processes.map((info) => [info.pid, info]));
|
|
91
|
+
const lineage = /* @__PURE__ */ new Set([process.pid]);
|
|
92
|
+
const self = byPid.get(process.pid);
|
|
93
|
+
let ancestor = self && byPid.get(self.ppid);
|
|
94
|
+
while (ancestor && !lineage.has(ancestor.pid)) {
|
|
95
|
+
lineage.add(ancestor.pid);
|
|
96
|
+
ancestor = byPid.get(ancestor.ppid);
|
|
97
|
+
}
|
|
98
|
+
if (self) lineage.add(self.pgid);
|
|
99
|
+
return lineage;
|
|
100
|
+
}
|
|
101
|
+
function treesOf(orphans, processes, spared) {
|
|
102
|
+
const members = /* @__PURE__ */ new Set();
|
|
103
|
+
const pending = orphans.map((orphan) => orphan.pid);
|
|
104
|
+
for (const orphan of orphans) {
|
|
105
|
+
if (orphan.pgid === orphan.pid) {
|
|
106
|
+
for (const info of processes) if (info.pgid === orphan.pid) pending.push(info.pid);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
while (pending.length > 0) {
|
|
110
|
+
const pid = pending.pop();
|
|
111
|
+
if (members.has(pid) || spared.has(pid)) continue;
|
|
112
|
+
members.add(pid);
|
|
113
|
+
for (const info of processes) if (info.ppid === pid) pending.push(info.pid);
|
|
114
|
+
}
|
|
115
|
+
return members;
|
|
116
|
+
}
|
|
117
|
+
function signalEach(pids, signal, d) {
|
|
118
|
+
for (const pid of pids) {
|
|
119
|
+
try {
|
|
120
|
+
d.kill(pid, signal);
|
|
121
|
+
} catch {
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function stillRunning(pids, d) {
|
|
126
|
+
const signalled = pids.filter((pid) => isPidRunning(pid, d));
|
|
127
|
+
if (signalled.length === 0) return signalled;
|
|
128
|
+
const zombies = new Set(
|
|
129
|
+
d.listProcesses().filter((info) => info.zombie).map((info) => info.pid)
|
|
130
|
+
);
|
|
131
|
+
return signalled.filter((pid) => !zombies.has(pid));
|
|
132
|
+
}
|
|
133
|
+
async function waitForExit(pids, timeoutMs, d) {
|
|
134
|
+
const deadline = Date.now() + timeoutMs;
|
|
135
|
+
let running = stillRunning(pids, d);
|
|
136
|
+
while (running.length > 0 && Date.now() < deadline) {
|
|
137
|
+
await d.sleep(50);
|
|
138
|
+
running = stillRunning(running, d);
|
|
139
|
+
}
|
|
140
|
+
return running;
|
|
141
|
+
}
|
|
142
|
+
async function reapOrphans(marker, options = {}, deps) {
|
|
143
|
+
assertMarker(marker);
|
|
144
|
+
const d = {
|
|
145
|
+
platform: process.platform,
|
|
146
|
+
kill: (pid, signal) => process.kill(pid, signal),
|
|
147
|
+
sleep,
|
|
148
|
+
listProcesses,
|
|
149
|
+
...deps
|
|
150
|
+
};
|
|
151
|
+
if (d.platform === "win32") return [];
|
|
152
|
+
const processes = d.listProcesses();
|
|
153
|
+
const lineage = ownLineage(processes);
|
|
154
|
+
const orphans = processes.filter((info) => {
|
|
155
|
+
const owner = ownerPidOf(info.command, marker);
|
|
156
|
+
return owner !== void 0 && !lineage.has(info.pid) && !isPidRunning(owner, d);
|
|
157
|
+
});
|
|
158
|
+
if (orphans.length === 0) return [];
|
|
159
|
+
const tree = [...treesOf(orphans, processes, lineage)];
|
|
160
|
+
signalEach(tree, "SIGTERM", d);
|
|
161
|
+
const survivors = await waitForExit(tree, options.graceMs ?? DEFAULT_REAP_GRACE_MS, d);
|
|
162
|
+
if (survivors.length > 0) {
|
|
163
|
+
signalEach(survivors, "SIGKILL", d);
|
|
164
|
+
await waitForExit(survivors, KILL_WAIT_MS, d);
|
|
165
|
+
}
|
|
166
|
+
const orphanPids = orphans.map((orphan) => orphan.pid);
|
|
167
|
+
const left = new Set(stillRunning(orphanPids, d));
|
|
168
|
+
return orphanPids.filter((pid) => !left.has(pid));
|
|
169
|
+
}
|
|
170
|
+
export {
|
|
171
|
+
OwnedProcessError,
|
|
172
|
+
parseProcessList,
|
|
173
|
+
reapOrphans,
|
|
174
|
+
spawnOwned
|
|
175
|
+
};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// libs/utils/src/process/process-tree.ts
|
|
2
|
+
import { getChildProcess } from "./child-process.mjs";
|
|
3
|
+
function runTaskkill(pid, force) {
|
|
4
|
+
const result = getChildProcess().spawnSync("taskkill", ["/pid", String(pid), "/T", ...force ? ["/F"] : []], {
|
|
5
|
+
stdio: "ignore",
|
|
6
|
+
windowsHide: true
|
|
7
|
+
});
|
|
8
|
+
return !result.error && result.status === 0;
|
|
9
|
+
}
|
|
10
|
+
function queryChildPids(pid) {
|
|
11
|
+
if (!Number.isInteger(pid) || pid <= 0) return [];
|
|
12
|
+
const result = getChildProcess().spawnSync(
|
|
13
|
+
"powershell.exe",
|
|
14
|
+
[
|
|
15
|
+
"-NoProfile",
|
|
16
|
+
"-NonInteractive",
|
|
17
|
+
"-Command",
|
|
18
|
+
// Single quotes: a double quote inside an argument would need Windows command-line escaping
|
|
19
|
+
`Get-CimInstance Win32_Process -Filter 'ParentProcessId=${pid}' | ForEach-Object { $_.ProcessId }`
|
|
20
|
+
],
|
|
21
|
+
{ encoding: "utf8", windowsHide: true, timeout: 15e3 }
|
|
22
|
+
);
|
|
23
|
+
if (result.error || result.status !== 0) return [];
|
|
24
|
+
return parsePidList(String(result.stdout ?? ""));
|
|
25
|
+
}
|
|
26
|
+
function parsePidList(output) {
|
|
27
|
+
return output.split(/\r?\n/).map((line) => line.trim()).filter((line) => /^\d+$/.test(line)).map(Number).filter((pid) => pid > 0);
|
|
28
|
+
}
|
|
29
|
+
function sleep(ms) {
|
|
30
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
31
|
+
}
|
|
32
|
+
function withDefaults(deps) {
|
|
33
|
+
return {
|
|
34
|
+
platform: process.platform,
|
|
35
|
+
kill: (pid, signal) => process.kill(pid, signal),
|
|
36
|
+
taskkill: runTaskkill,
|
|
37
|
+
childPids: queryChildPids,
|
|
38
|
+
sleep,
|
|
39
|
+
...deps
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function processTreeSpawnOptions(platform = process.platform) {
|
|
43
|
+
return platform === "win32" ? {} : { detached: true };
|
|
44
|
+
}
|
|
45
|
+
function hasExited(child) {
|
|
46
|
+
return child.exitCode !== null || child.signalCode !== null;
|
|
47
|
+
}
|
|
48
|
+
function isPidRunning(pid, d) {
|
|
49
|
+
try {
|
|
50
|
+
d.kill(pid, 0);
|
|
51
|
+
return true;
|
|
52
|
+
} catch (err) {
|
|
53
|
+
return err.code === "EPERM";
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function windowsOrphans(pid, d) {
|
|
57
|
+
if (isPidRunning(pid, d)) return [];
|
|
58
|
+
return d.childPids(pid);
|
|
59
|
+
}
|
|
60
|
+
function taskkillTree(child, pid, force, d) {
|
|
61
|
+
const roots = hasExited(child) ? windowsOrphans(pid, d) : [pid];
|
|
62
|
+
let delivered = false;
|
|
63
|
+
for (const root of roots) {
|
|
64
|
+
if (d.taskkill(root, force)) delivered = true;
|
|
65
|
+
}
|
|
66
|
+
return delivered;
|
|
67
|
+
}
|
|
68
|
+
function signalProcessTree(child, signal, deps) {
|
|
69
|
+
const d = withDefaults(deps);
|
|
70
|
+
const pid = child?.pid;
|
|
71
|
+
if (!child || pid === void 0) return false;
|
|
72
|
+
if (d.platform === "win32") return taskkillTree(child, pid, signal === "SIGKILL", d);
|
|
73
|
+
try {
|
|
74
|
+
d.kill(-pid, signal);
|
|
75
|
+
return true;
|
|
76
|
+
} catch {
|
|
77
|
+
}
|
|
78
|
+
if (hasExited(child)) return false;
|
|
79
|
+
try {
|
|
80
|
+
return child.kill(signal);
|
|
81
|
+
} catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function isProcessTreeAlive(child, deps) {
|
|
86
|
+
const d = withDefaults(deps);
|
|
87
|
+
const pid = child?.pid;
|
|
88
|
+
if (!child || pid === void 0) return false;
|
|
89
|
+
if (d.platform === "win32") return !hasExited(child) || windowsOrphans(pid, d).length > 0;
|
|
90
|
+
try {
|
|
91
|
+
d.kill(-pid, 0);
|
|
92
|
+
return true;
|
|
93
|
+
} catch (err) {
|
|
94
|
+
return err.code === "EPERM";
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async function stopProcessTree(child, signal, graceMs, deps) {
|
|
98
|
+
const d = withDefaults(deps);
|
|
99
|
+
if (!child) return true;
|
|
100
|
+
if (d.platform === "win32") return stopWindowsTree(child, signal, graceMs, d);
|
|
101
|
+
signalProcessTree(child, signal, d);
|
|
102
|
+
if (await waitForProcessTreeExit(child, graceMs, d)) return true;
|
|
103
|
+
signalProcessTree(child, "SIGKILL", d);
|
|
104
|
+
return waitForProcessTreeExit(child, 1e3, d);
|
|
105
|
+
}
|
|
106
|
+
async function waitForProcessTreeExit(child, timeoutMs, d) {
|
|
107
|
+
const deadline = Date.now() + timeoutMs;
|
|
108
|
+
while (isProcessTreeAlive(child, d)) {
|
|
109
|
+
if (Date.now() >= deadline) return false;
|
|
110
|
+
await d.sleep(50);
|
|
111
|
+
}
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
async function stopWindowsTree(child, signal, graceMs, d) {
|
|
115
|
+
const pid = child.pid;
|
|
116
|
+
if (pid === void 0) return true;
|
|
117
|
+
if (hasExited(child) && windowsOrphans(pid, d).length === 0) return true;
|
|
118
|
+
if (signal !== "SIGKILL") {
|
|
119
|
+
const asked = taskkillTree(child, pid, false, d);
|
|
120
|
+
if (asked && await waitForWindowsTreeExit(child, pid, graceMs, d)) return true;
|
|
121
|
+
}
|
|
122
|
+
const rootWasRunning = !hasExited(child);
|
|
123
|
+
const forced = taskkillTree(child, pid, true, d);
|
|
124
|
+
return waitForWindowsTreeExit(child, pid, 1e3, d, !(forced && rootWasRunning));
|
|
125
|
+
}
|
|
126
|
+
async function waitForWindowsTreeExit(child, pid, timeoutMs, d, checkOrphans = true) {
|
|
127
|
+
const deadline = Date.now() + timeoutMs;
|
|
128
|
+
while (!hasExited(child)) {
|
|
129
|
+
if (Date.now() >= deadline) return false;
|
|
130
|
+
await d.sleep(50);
|
|
131
|
+
}
|
|
132
|
+
if (!checkOrphans) return true;
|
|
133
|
+
for (; ; ) {
|
|
134
|
+
if (windowsOrphans(pid, d).length === 0) return true;
|
|
135
|
+
if (Date.now() >= deadline) return false;
|
|
136
|
+
await d.sleep(250);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
export {
|
|
140
|
+
isPidRunning,
|
|
141
|
+
isProcessTreeAlive,
|
|
142
|
+
parsePidList,
|
|
143
|
+
processTreeSpawnOptions,
|
|
144
|
+
queryChildPids,
|
|
145
|
+
runTaskkill,
|
|
146
|
+
signalProcessTree,
|
|
147
|
+
sleep,
|
|
148
|
+
stopProcessTree
|
|
149
|
+
};
|