@appforge-ci/core 0.2.3 → 0.3.1
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 +240 -1
- package/dist/agent-readiness.d.ts.map +1 -1
- package/dist/agent-readiness.js +304 -1
- package/dist/agent-readiness.js.map +1 -1
- package/dist/agent-readiness.test.js +277 -19
- package/dist/agent-readiness.test.js.map +1 -1
- package/dist/api-client.d.ts +80 -3
- package/dist/api-client.d.ts.map +1 -1
- package/dist/api-client.js +79 -0
- package/dist/api-client.js.map +1 -1
- package/dist/api-client.test.js +22 -0
- package/dist/api-client.test.js.map +1 -1
- package/dist/appforge-verify.test.d.ts +2 -0
- package/dist/appforge-verify.test.d.ts.map +1 -0
- package/dist/appforge-verify.test.js +958 -0
- package/dist/appforge-verify.test.js.map +1 -0
- package/dist/attestation-keys.d.ts +20 -0
- package/dist/attestation-keys.d.ts.map +1 -0
- package/dist/attestation-keys.js +27 -0
- package/dist/attestation-keys.js.map +1 -0
- package/dist/attestation-keys.test.d.ts +2 -0
- package/dist/attestation-keys.test.d.ts.map +1 -0
- package/dist/attestation-keys.test.js +45 -0
- package/dist/attestation-keys.test.js.map +1 -0
- package/dist/attestation-signing.d.ts +226 -0
- package/dist/attestation-signing.d.ts.map +1 -0
- package/dist/attestation-signing.js +276 -0
- package/dist/attestation-signing.js.map +1 -0
- package/dist/attestation-signing.test.d.ts +2 -0
- package/dist/attestation-signing.test.d.ts.map +1 -0
- package/dist/attestation-signing.test.js +250 -0
- package/dist/attestation-signing.test.js.map +1 -0
- package/dist/attestation-workerd.test.d.ts +2 -0
- package/dist/attestation-workerd.test.d.ts.map +1 -0
- package/dist/attestation-workerd.test.js +183 -0
- package/dist/attestation-workerd.test.js.map +1 -0
- package/dist/attestation.d.ts +412 -0
- package/dist/attestation.d.ts.map +1 -0
- package/dist/attestation.js +353 -0
- package/dist/attestation.js.map +1 -0
- package/dist/attestation.test.d.ts +2 -0
- package/dist/attestation.test.d.ts.map +1 -0
- package/dist/attestation.test.js +218 -0
- package/dist/attestation.test.js.map +1 -0
- package/dist/build-vpn.d.ts +20 -0
- package/dist/build-vpn.d.ts.map +1 -0
- package/dist/build-vpn.js +50 -0
- package/dist/build-vpn.js.map +1 -0
- package/dist/fleet-vpn.d.ts +64 -0
- package/dist/fleet-vpn.d.ts.map +1 -0
- package/dist/fleet-vpn.js +88 -0
- package/dist/fleet-vpn.js.map +1 -0
- package/dist/fleet-vpn.test.d.ts +2 -0
- package/dist/fleet-vpn.test.d.ts.map +1 -0
- package/dist/fleet-vpn.test.js +69 -0
- package/dist/fleet-vpn.test.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -1
- package/dist/schemas-repo-url.test.d.ts +2 -0
- package/dist/schemas-repo-url.test.d.ts.map +1 -0
- package/dist/schemas-repo-url.test.js +67 -0
- package/dist/schemas-repo-url.test.js.map +1 -0
- package/dist/schemas.d.ts +360 -7
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +148 -12
- package/dist/schemas.js.map +1 -1
- package/dist/ssh-deploy-key.d.ts +59 -0
- package/dist/ssh-deploy-key.d.ts.map +1 -0
- package/dist/ssh-deploy-key.js +124 -0
- package/dist/ssh-deploy-key.js.map +1 -0
- package/dist/ssh-deploy-key.test.d.ts +2 -0
- package/dist/ssh-deploy-key.test.d.ts.map +1 -0
- package/dist/ssh-deploy-key.test.js +117 -0
- package/dist/ssh-deploy-key.test.js.map +1 -0
- package/dist/types.d.ts +204 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -5
- package/dist/types.js.map +1 -1
- package/dist/vpn-attestation-workerd.test.d.ts +2 -0
- package/dist/vpn-attestation-workerd.test.d.ts.map +1 -0
- package/dist/vpn-attestation-workerd.test.js +184 -0
- package/dist/vpn-attestation-workerd.test.js.map +1 -0
- package/dist/vpn-attestation.d.ts +1015 -0
- package/dist/vpn-attestation.d.ts.map +1 -0
- package/dist/vpn-attestation.js +963 -0
- package/dist/vpn-attestation.js.map +1 -0
- package/dist/vpn-attestation.test.d.ts +2 -0
- package/dist/vpn-attestation.test.d.ts.map +1 -0
- package/dist/vpn-attestation.test.js +613 -0
- package/dist/vpn-attestation.test.js.map +1 -0
- package/dist/vpn-egress-presets.d.ts +142 -0
- package/dist/vpn-egress-presets.d.ts.map +1 -0
- package/dist/vpn-egress-presets.js +318 -0
- package/dist/vpn-egress-presets.js.map +1 -0
- package/dist/vpn-egress-presets.test.d.ts +2 -0
- package/dist/vpn-egress-presets.test.d.ts.map +1 -0
- package/dist/vpn-egress-presets.test.js +210 -0
- package/dist/vpn-egress-presets.test.js.map +1 -0
- package/dist/vpn-egress.d.ts +57 -0
- package/dist/vpn-egress.d.ts.map +1 -0
- package/dist/vpn-egress.js +126 -0
- package/dist/vpn-egress.js.map +1 -0
- package/dist/vpn-egress.test.d.ts +2 -0
- package/dist/vpn-egress.test.d.ts.map +1 -0
- package/dist/vpn-egress.test.js +133 -0
- package/dist/vpn-egress.test.js.map +1 -0
- package/dist/vpn-sandbox.d.ts +67 -0
- package/dist/vpn-sandbox.d.ts.map +1 -0
- package/dist/vpn-sandbox.js +22 -0
- package/dist/vpn-sandbox.js.map +1 -0
- package/dist/vpn-sandbox.test.d.ts +2 -0
- package/dist/vpn-sandbox.test.d.ts.map +1 -0
- package/dist/vpn-sandbox.test.js +34 -0
- package/dist/vpn-sandbox.test.js.map +1 -0
- package/dist/vpn-ssh.d.ts +138 -0
- package/dist/vpn-ssh.d.ts.map +1 -0
- package/dist/vpn-ssh.js +320 -0
- package/dist/vpn-ssh.js.map +1 -0
- package/dist/vpn-ssh.test.d.ts +2 -0
- package/dist/vpn-ssh.test.d.ts.map +1 -0
- package/dist/vpn-ssh.test.js +274 -0
- package/dist/vpn-ssh.test.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { BuildAttestationStatus, BuildVpnStatus } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* One-line, human-readable summary of `Build.vpn`, shared by the dashboard's
|
|
4
|
+
* Network panel and `appforge build show` so they never disagree.
|
|
5
|
+
* "Ran through VPN — Lab test gateway · connected"
|
|
6
|
+
* "VPN tunnel failed: <detail>"
|
|
7
|
+
* "No VPN"
|
|
8
|
+
*/
|
|
9
|
+
export declare function describeBuildVpn(vpn: BuildVpnStatus | undefined): string;
|
|
10
|
+
/**
|
|
11
|
+
* One-line, human-readable state of a build's attestation, shared by the dashboard and `appforge build show`. Says plainly when it
|
|
12
|
+
* is not (yet) evidence: pending, degraded, unsigned, or recording a violation.
|
|
13
|
+
* "Evidence: complete, signed, all controls enforced"
|
|
14
|
+
* "Evidence: complete, signed, 2 control violations recorded"
|
|
15
|
+
* "Evidence: DEGRADED (the agent did not report), signed: server-observed facts only"
|
|
16
|
+
* "Evidence: complete, NOT SIGNED (the platform had no signing key)"
|
|
17
|
+
* "Evidence: pending (the build's agent has not reported yet)"
|
|
18
|
+
*/
|
|
19
|
+
export declare function describeBuildAttestation(a: BuildAttestationStatus | undefined): string | null;
|
|
20
|
+
//# sourceMappingURL=build-vpn.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-vpn.d.ts","sourceRoot":"","sources":["../src/build-vpn.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEzE;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,cAAc,GAAG,SAAS,GAAG,MAAM,CAUxE;AAED;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,EAAE,sBAAsB,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAe7F"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One-line, human-readable summary of `Build.vpn`, shared by the dashboard's
|
|
3
|
+
* Network panel and `appforge build show` so they never disagree.
|
|
4
|
+
* "Ran through VPN — Lab test gateway · connected"
|
|
5
|
+
* "VPN tunnel failed: <detail>"
|
|
6
|
+
* "No VPN"
|
|
7
|
+
*/
|
|
8
|
+
export function describeBuildVpn(vpn) {
|
|
9
|
+
if (!vpn)
|
|
10
|
+
return "VPN status not available";
|
|
11
|
+
const name = vpn.tunnelName ?? "tunnel";
|
|
12
|
+
if (vpn.used) {
|
|
13
|
+
if (vpn.outcome === "connected")
|
|
14
|
+
return `Ran through VPN — ${name} · connected`;
|
|
15
|
+
if (vpn.outcome === "failed")
|
|
16
|
+
return `VPN tunnel failed: ${vpn.detail ?? "the handshake did not complete"}`;
|
|
17
|
+
return `VPN reserved — ${name} · no completed handshake recorded`;
|
|
18
|
+
}
|
|
19
|
+
if (vpn.outcome === "unknown")
|
|
20
|
+
return "VPN status unknown: no VPN events were recorded for this build";
|
|
21
|
+
return "No VPN";
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* One-line, human-readable state of a build's attestation, shared by the dashboard and `appforge build show`. Says plainly when it
|
|
25
|
+
* is not (yet) evidence: pending, degraded, unsigned, or recording a violation.
|
|
26
|
+
* "Evidence: complete, signed, all controls enforced"
|
|
27
|
+
* "Evidence: complete, signed, 2 control violations recorded"
|
|
28
|
+
* "Evidence: DEGRADED (the agent did not report), signed: server-observed facts only"
|
|
29
|
+
* "Evidence: complete, NOT SIGNED (the platform had no signing key)"
|
|
30
|
+
* "Evidence: pending (the build's agent has not reported yet)"
|
|
31
|
+
*/
|
|
32
|
+
export function describeBuildAttestation(a) {
|
|
33
|
+
if (!a)
|
|
34
|
+
return null;
|
|
35
|
+
if (a.state === "pending")
|
|
36
|
+
return "Evidence: pending (the build's agent has not reported yet)";
|
|
37
|
+
const signed = a.signed ? "signed" : "NOT SIGNED (the platform had no signing key)";
|
|
38
|
+
if (a.state === "degraded_server_observed")
|
|
39
|
+
return `Evidence: DEGRADED (the agent did not report), ${signed}: server-observed facts only`;
|
|
40
|
+
const verdict = a.violations > 0
|
|
41
|
+
? `${a.violations} control violation${a.violations === 1 ? "" : "s"} recorded`
|
|
42
|
+
: a.controlsVerdict === "all_enforced"
|
|
43
|
+
? "all controls enforced"
|
|
44
|
+
: a.controlsVerdict === "network_policy_checked"
|
|
45
|
+
? "network policy checked"
|
|
46
|
+
: "no control verdict";
|
|
47
|
+
const partial = a.completeness === "partial" ? ", partial" : "";
|
|
48
|
+
return `Evidence: complete${partial}, ${signed}, ${verdict}`;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=build-vpn.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-vpn.js","sourceRoot":"","sources":["../src/build-vpn.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAA+B;IAC9D,IAAI,CAAC,GAAG;QAAE,OAAO,0BAA0B,CAAC;IAC5C,MAAM,IAAI,GAAG,GAAG,CAAC,UAAU,IAAI,QAAQ,CAAC;IACxC,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;QACb,IAAI,GAAG,CAAC,OAAO,KAAK,WAAW;YAAE,OAAO,qBAAqB,IAAI,cAAc,CAAC;QAChF,IAAI,GAAG,CAAC,OAAO,KAAK,QAAQ;YAAE,OAAO,sBAAsB,GAAG,CAAC,MAAM,IAAI,gCAAgC,EAAE,CAAC;QAC5G,OAAO,kBAAkB,IAAI,oCAAoC,CAAC;IACpE,CAAC;IACD,IAAI,GAAG,CAAC,OAAO,KAAK,SAAS;QAAE,OAAO,gEAAgE,CAAC;IACvG,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,wBAAwB,CAAC,CAAqC;IAC5E,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACpB,IAAI,CAAC,CAAC,KAAK,KAAK,SAAS;QAAE,OAAO,4DAA4D,CAAC;IAC/F,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,8CAA8C,CAAC;IACpF,IAAI,CAAC,CAAC,KAAK,KAAK,0BAA0B;QAAE,OAAO,kDAAkD,MAAM,8BAA8B,CAAC;IAC1I,MAAM,OAAO,GACX,CAAC,CAAC,UAAU,GAAG,CAAC;QACd,CAAC,CAAC,GAAG,CAAC,CAAC,UAAU,qBAAqB,CAAC,CAAC,UAAU,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,WAAW;QAC9E,CAAC,CAAC,CAAC,CAAC,eAAe,KAAK,cAAc;YACpC,CAAC,CAAC,uBAAuB;YACzB,CAAC,CAAC,CAAC,CAAC,eAAe,KAAK,wBAAwB;gBAC9C,CAAC,CAAC,wBAAwB;gBAC1B,CAAC,CAAC,oBAAoB,CAAC;IAC/B,MAAM,OAAO,GAAG,CAAC,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC;IAChE,OAAO,qBAAqB,OAAO,KAAK,MAAM,KAAK,OAAO,EAAE,CAAC;AAC/D,CAAC"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { type EnforcementLevel } from "./agent-readiness.js";
|
|
2
|
+
import type { AgentVpnPin } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* How the fleet shows an agent's VPN facts, shared by the dashboard's Fleet page
|
|
5
|
+
* and `appforge-admin agents list` so the two surfaces never disagree about what
|
|
6
|
+
* a feature id means or what "eligible" is. Pure functions and constants only.
|
|
7
|
+
*
|
|
8
|
+
* Honesty rule: every sentence here describes what the CONTROL PLANE knows, i.e.
|
|
9
|
+
* what the Mac reported at its last heartbeat. A feature chip is a claim the
|
|
10
|
+
* Mac made, not a proof; the scheduler only trusts it while the heartbeat is
|
|
11
|
+
* fresh (`agentVpnCapabilitiesCurrent`), and the page says so.
|
|
12
|
+
*/
|
|
13
|
+
/** What each VPN feature id means, as a one-sentence legend. */
|
|
14
|
+
export declare const VPN_FEATURE_LEGEND: Readonly<Record<string, string>>;
|
|
15
|
+
/** Legend sentence for a feature id; a short fallback for an id this build does not know. */
|
|
16
|
+
export declare function describeVpnFeature(id: string): string;
|
|
17
|
+
/** What each enforcement level means. */
|
|
18
|
+
export declare const ENFORCEMENT_LEVEL_LEGEND: Readonly<Record<EnforcementLevel, string>>;
|
|
19
|
+
export declare function describeEnforcementLevel(level: unknown): string;
|
|
20
|
+
/**
|
|
21
|
+
* The VPN eligibility as the scheduler applies it in `/agents/lease-job`: a Mac
|
|
22
|
+
* serves VPN builds only when it reports the verified helper (`vpnCapable`), a
|
|
23
|
+
* platform admin approved it (`vpnApproved`), AND the scheduler currently trusts
|
|
24
|
+
* the helper report (`agentVpnCapabilitiesCurrent`: a VPN-capable agent version
|
|
25
|
+
* with a recent heartbeat). `vpnCapable` is sticky between heartbeats, so an
|
|
26
|
+
* offline or too-old Mac can still carry helper=1 + approved=1 yet is never
|
|
27
|
+
* leased a VPN build; `stale` is true for exactly that case.
|
|
28
|
+
*
|
|
29
|
+
* `agentVersion` and `lastSeenAt` are optional only for callers that cannot
|
|
30
|
+
* supply them (an older API omits them): when either is absent freshness is
|
|
31
|
+
* unknown and only the helper and approval halves are judged.
|
|
32
|
+
*/
|
|
33
|
+
export declare function vpnEligibility(a: {
|
|
34
|
+
vpnCapable?: number;
|
|
35
|
+
vpnApproved?: number;
|
|
36
|
+
agentVersion?: string | null;
|
|
37
|
+
lastSeenAt?: string;
|
|
38
|
+
}, now?: Date): {
|
|
39
|
+
eligible: boolean;
|
|
40
|
+
stale: boolean;
|
|
41
|
+
label: string;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Whether the reported features / enforcement level / runner flag are still
|
|
45
|
+
* trusted by the scheduler: a VPN-capable agent version with a recent heartbeat.
|
|
46
|
+
* When false they are the Mac's LAST report and may be out of date (it is
|
|
47
|
+
* offline, or too old to be leased VPN builds at all).
|
|
48
|
+
*/
|
|
49
|
+
export declare function agentVpnCapabilitiesCurrent(a: {
|
|
50
|
+
agentVersion: string | null;
|
|
51
|
+
lastSeenAt: string;
|
|
52
|
+
}, now?: Date): boolean;
|
|
53
|
+
/** The co-hosted GitHub Actions runner flag in words: 1 yes, 0 no, anything else unreported. */
|
|
54
|
+
export declare function describeCoHostedRunner(coHostedRunner: number | null | undefined): "runner co-hosted" | "no runner" | "runner unreported";
|
|
55
|
+
/** Replace C0/C1 control characters (ESC, CSI, newline...) so tenant-chosen text cannot drive a terminal. */
|
|
56
|
+
export declare function stripControlChars(text: string): string;
|
|
57
|
+
/**
|
|
58
|
+
* An org as a person reads it: its name when known (with the id for disambiguation), else the id.
|
|
59
|
+
* The name is tenant-controlled (an org owner sets it), so control characters are neutralised.
|
|
60
|
+
*/
|
|
61
|
+
export declare function orgDisplay(orgId: string, orgName: string | null | undefined): string;
|
|
62
|
+
/** One pin as a short phrase: "Acme (org_1) [dedicated]". */
|
|
63
|
+
export declare function describeVpnPin(pin: AgentVpnPin): string;
|
|
64
|
+
//# sourceMappingURL=fleet-vpn.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fleet-vpn.d.ts","sourceRoot":"","sources":["../src/fleet-vpn.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoD,KAAK,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC/G,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;;;;;;GASG;AAEH,gEAAgE;AAChE,eAAO,MAAM,kBAAkB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAS/D,CAAC;AAEF,6FAA6F;AAC7F,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,yCAAyC;AACzC,eAAO,MAAM,wBAAwB,EAAE,QAAQ,CAAC,MAAM,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAI/E,CAAC;AAEF,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAE/D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAC5B,CAAC,EAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,EACnG,GAAG,GAAE,IAAiB,GACrB;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAStD;AAED;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,CAAC,EAAE;IAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,EAAE,GAAG,GAAE,IAAiB,GAAG,OAAO,CAEnI;AAED,gGAAgG;AAChG,wBAAgB,sBAAsB,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,kBAAkB,GAAG,WAAW,GAAG,mBAAmB,CAIxI;AAED,6GAA6G;AAC7G,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAEpF;AAED,6DAA6D;AAC7D,wBAAgB,cAAc,CAAC,GAAG,EAAE,WAAW,GAAG,MAAM,CAEvD"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { agentProvesVpnEnforcement, parseEnforcementLevel } from "./agent-readiness.js";
|
|
2
|
+
/**
|
|
3
|
+
* How the fleet shows an agent's VPN facts, shared by the dashboard's Fleet page
|
|
4
|
+
* and `appforge-admin agents list` so the two surfaces never disagree about what
|
|
5
|
+
* a feature id means or what "eligible" is. Pure functions and constants only.
|
|
6
|
+
*
|
|
7
|
+
* Honesty rule: every sentence here describes what the CONTROL PLANE knows, i.e.
|
|
8
|
+
* what the Mac reported at its last heartbeat. A feature chip is a claim the
|
|
9
|
+
* Mac made, not a proof; the scheduler only trusts it while the heartbeat is
|
|
10
|
+
* fresh (`agentVpnCapabilitiesCurrent`), and the page says so.
|
|
11
|
+
*/
|
|
12
|
+
/** What each VPN feature id means, as a one-sentence legend. */
|
|
13
|
+
export const VPN_FEATURE_LEGEND = {
|
|
14
|
+
"egress-policy-v1": "The verified VPN helper enforces a strict egress allow-list for builds (it refuses everything outside the tunnel's policy). Reported only while a helper of a new enough version is installed and verified.",
|
|
15
|
+
"ssh-v1": "Git and ssh traffic can be routed through the tunnel with pinned host keys (the verified helper has the connect subcommand). Reported only while that helper is installed and verified.",
|
|
16
|
+
"sandbox-v1": "Build processes run under a kernel (Seatbelt) sandbox. Reported only while the on-host sandbox self-test has passed recently; a failed or stale self-test withdraws it.",
|
|
17
|
+
"evidence-v1": "Per-build connection evidence: the verified helper streams per-connection records and the agent reports them after each strict-egress or regulated build, so the control plane can issue a signed attestation. Reported only while a helper with per-build credentials and the accounting pipe is installed and verified.",
|
|
18
|
+
};
|
|
19
|
+
/** Legend sentence for a feature id; a short fallback for an id this build does not know. */
|
|
20
|
+
export function describeVpnFeature(id) {
|
|
21
|
+
return VPN_FEATURE_LEGEND[id] ?? "A feature id this dashboard does not know (reported by the agent; no description).";
|
|
22
|
+
}
|
|
23
|
+
/** What each enforcement level means. */
|
|
24
|
+
export const ENFORCEMENT_LEVEL_LEGEND = {
|
|
25
|
+
none: "No kernel-enforced isolation of build processes.",
|
|
26
|
+
sandbox: "Builds run under a kernel (Seatbelt) sandbox profile whose self-test passed on this Mac. Not a dedicated OS user.",
|
|
27
|
+
sandbox_uid: "The sandbox plus a dedicated unprivileged OS user for builds.",
|
|
28
|
+
};
|
|
29
|
+
export function describeEnforcementLevel(level) {
|
|
30
|
+
return ENFORCEMENT_LEVEL_LEGEND[parseEnforcementLevel(level)];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The VPN eligibility as the scheduler applies it in `/agents/lease-job`: a Mac
|
|
34
|
+
* serves VPN builds only when it reports the verified helper (`vpnCapable`), a
|
|
35
|
+
* platform admin approved it (`vpnApproved`), AND the scheduler currently trusts
|
|
36
|
+
* the helper report (`agentVpnCapabilitiesCurrent`: a VPN-capable agent version
|
|
37
|
+
* with a recent heartbeat). `vpnCapable` is sticky between heartbeats, so an
|
|
38
|
+
* offline or too-old Mac can still carry helper=1 + approved=1 yet is never
|
|
39
|
+
* leased a VPN build; `stale` is true for exactly that case.
|
|
40
|
+
*
|
|
41
|
+
* `agentVersion` and `lastSeenAt` are optional only for callers that cannot
|
|
42
|
+
* supply them (an older API omits them): when either is absent freshness is
|
|
43
|
+
* unknown and only the helper and approval halves are judged.
|
|
44
|
+
*/
|
|
45
|
+
export function vpnEligibility(a, now = new Date()) {
|
|
46
|
+
const helper = a.vpnCapable === 1;
|
|
47
|
+
const approved = a.vpnApproved === 1;
|
|
48
|
+
if (helper && approved) {
|
|
49
|
+
const stale = a.agentVersion !== undefined && a.lastSeenAt !== undefined && !agentVpnCapabilitiesCurrent({ agentVersion: a.agentVersion, lastSeenAt: a.lastSeenAt }, now);
|
|
50
|
+
if (stale)
|
|
51
|
+
return { eligible: false, stale: true, label: "not eligible now, helper report stale (offline or old agent)" };
|
|
52
|
+
return { eligible: true, stale: false, label: "eligible" };
|
|
53
|
+
}
|
|
54
|
+
return { eligible: false, stale: false, label: `${helper ? "helper ready" : "no helper"}, ${approved ? "approved" : "not approved"}` };
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Whether the reported features / enforcement level / runner flag are still
|
|
58
|
+
* trusted by the scheduler: a VPN-capable agent version with a recent heartbeat.
|
|
59
|
+
* When false they are the Mac's LAST report and may be out of date (it is
|
|
60
|
+
* offline, or too old to be leased VPN builds at all).
|
|
61
|
+
*/
|
|
62
|
+
export function agentVpnCapabilitiesCurrent(a, now = new Date()) {
|
|
63
|
+
return agentProvesVpnEnforcement(a, now);
|
|
64
|
+
}
|
|
65
|
+
/** The co-hosted GitHub Actions runner flag in words: 1 yes, 0 no, anything else unreported. */
|
|
66
|
+
export function describeCoHostedRunner(coHostedRunner) {
|
|
67
|
+
if (coHostedRunner === 1)
|
|
68
|
+
return "runner co-hosted";
|
|
69
|
+
if (coHostedRunner === 0)
|
|
70
|
+
return "no runner";
|
|
71
|
+
return "runner unreported";
|
|
72
|
+
}
|
|
73
|
+
/** Replace C0/C1 control characters (ESC, CSI, newline...) so tenant-chosen text cannot drive a terminal. */
|
|
74
|
+
export function stripControlChars(text) {
|
|
75
|
+
return text.replace(/[\x00-\x1f\x7f-\x9f]/g, "\uFFFD");
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* An org as a person reads it: its name when known (with the id for disambiguation), else the id.
|
|
79
|
+
* The name is tenant-controlled (an org owner sets it), so control characters are neutralised.
|
|
80
|
+
*/
|
|
81
|
+
export function orgDisplay(orgId, orgName) {
|
|
82
|
+
return orgName ? `${stripControlChars(orgName)} (${orgId})` : orgId;
|
|
83
|
+
}
|
|
84
|
+
/** One pin as a short phrase: "Acme (org_1) [dedicated]". */
|
|
85
|
+
export function describeVpnPin(pin) {
|
|
86
|
+
return `${orgDisplay(pin.orgId, pin.orgName)} [${pin.hostPolicy}]`;
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=fleet-vpn.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fleet-vpn.js","sourceRoot":"","sources":["../src/fleet-vpn.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fleet-vpn.test.d.ts","sourceRoot":"","sources":["../src/fleet-vpn.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { ENFORCEMENT_LEVELS, KNOWN_VPN_FEATURES, MIN_VPN_AGENT_VERSION, VPN_CAPABILITY_MAX_AGE_SECONDS, VPN_FEATURE_LEGEND, agentVpnCapabilitiesCurrent, describeCoHostedRunner, describeEnforcementLevel, describeVpnFeature, describeVpnPin, orgDisplay, vpnEligibility, } from "./index.js";
|
|
4
|
+
test("every feature id the program introduces has a legend sentence, and an unknown id gets an honest fallback", () => {
|
|
5
|
+
for (const id of KNOWN_VPN_FEATURES) {
|
|
6
|
+
assert.ok(VPN_FEATURE_LEGEND[id] && VPN_FEATURE_LEGEND[id].length > 20, `no legend for ${id}`);
|
|
7
|
+
assert.equal(describeVpnFeature(id), VPN_FEATURE_LEGEND[id]);
|
|
8
|
+
}
|
|
9
|
+
assert.match(describeVpnFeature("future-thing-v9"), /does not know/);
|
|
10
|
+
});
|
|
11
|
+
test("the legend does not claim compliance, certification or attestation properties the code does not give", () => {
|
|
12
|
+
const all = [...Object.values(VPN_FEATURE_LEGEND), ...ENFORCEMENT_LEVELS.map(describeEnforcementLevel)].join("\n");
|
|
13
|
+
assert.ok(!/SOC ?2|FIPS|complian|certif|bank-grade|secure|guarantee/i.test(all), all);
|
|
14
|
+
});
|
|
15
|
+
test("every enforcement level has a description; an unrecognised level reads as none", () => {
|
|
16
|
+
for (const level of ENFORCEMENT_LEVELS)
|
|
17
|
+
assert.ok(describeEnforcementLevel(level).length > 10);
|
|
18
|
+
assert.equal(describeEnforcementLevel("root"), describeEnforcementLevel("none"));
|
|
19
|
+
assert.equal(describeEnforcementLevel(undefined), describeEnforcementLevel("none"));
|
|
20
|
+
});
|
|
21
|
+
test("vpnEligibility is eligible only with BOTH the helper and the admin approval, and spells out the missing half otherwise", () => {
|
|
22
|
+
assert.deepEqual(vpnEligibility({ vpnCapable: 1, vpnApproved: 1 }), { eligible: true, stale: false, label: "eligible" });
|
|
23
|
+
assert.deepEqual(vpnEligibility({ vpnCapable: 1, vpnApproved: 0 }), { eligible: false, stale: false, label: "helper ready, not approved" });
|
|
24
|
+
assert.deepEqual(vpnEligibility({ vpnCapable: 0, vpnApproved: 1 }), { eligible: false, stale: false, label: "no helper, approved" });
|
|
25
|
+
assert.deepEqual(vpnEligibility({}), { eligible: false, stale: false, label: "no helper, not approved" });
|
|
26
|
+
});
|
|
27
|
+
test("vpnEligibility is not eligible (stale) for an approved helper Mac that is offline or too old, as the scheduler refuses it", () => {
|
|
28
|
+
const now = new Date("2026-10-03T12:00:00Z");
|
|
29
|
+
const seen = (secondsAgo) => new Date(now.getTime() - secondsAgo * 1000).toISOString();
|
|
30
|
+
const base = { vpnCapable: 1, vpnApproved: 1 };
|
|
31
|
+
assert.equal(vpnEligibility({ ...base, agentVersion: "0.3.0", lastSeenAt: seen(5) }, now).eligible, true);
|
|
32
|
+
for (const r of [
|
|
33
|
+
vpnEligibility({ ...base, agentVersion: "0.3.0", lastSeenAt: seen(VPN_CAPABILITY_MAX_AGE_SECONDS + 5) }, now),
|
|
34
|
+
vpnEligibility({ ...base, agentVersion: "0.1.9", lastSeenAt: seen(5) }, now),
|
|
35
|
+
vpnEligibility({ ...base, agentVersion: null, lastSeenAt: seen(5) }, now),
|
|
36
|
+
]) {
|
|
37
|
+
assert.equal(r.eligible, false);
|
|
38
|
+
assert.equal(r.stale, true);
|
|
39
|
+
assert.match(r.label, /stale/);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
test("agentVpnCapabilitiesCurrent follows the scheduler's proof: a VPN-capable version and a recent heartbeat", () => {
|
|
43
|
+
const now = new Date("2026-10-03T12:00:00Z");
|
|
44
|
+
const seen = (secondsAgo) => new Date(now.getTime() - secondsAgo * 1000).toISOString();
|
|
45
|
+
assert.equal(agentVpnCapabilitiesCurrent({ agentVersion: "0.3.0", lastSeenAt: seen(5) }, now), true);
|
|
46
|
+
assert.equal(agentVpnCapabilitiesCurrent({ agentVersion: "0.3.0", lastSeenAt: seen(VPN_CAPABILITY_MAX_AGE_SECONDS + 5) }, now), false);
|
|
47
|
+
assert.equal(agentVpnCapabilitiesCurrent({ agentVersion: null, lastSeenAt: seen(5) }, now), false);
|
|
48
|
+
assert.equal(agentVpnCapabilitiesCurrent({ agentVersion: "0.1.9", lastSeenAt: seen(5) }, now), false);
|
|
49
|
+
assert.equal(agentVpnCapabilitiesCurrent({ agentVersion: MIN_VPN_AGENT_VERSION, lastSeenAt: seen(5) }, now), true);
|
|
50
|
+
});
|
|
51
|
+
test("the co-hosted runner flag reads yes / no / unreported, and null or undefined are never 'no runner'", () => {
|
|
52
|
+
assert.equal(describeCoHostedRunner(1), "runner co-hosted");
|
|
53
|
+
assert.equal(describeCoHostedRunner(0), "no runner");
|
|
54
|
+
assert.equal(describeCoHostedRunner(null), "runner unreported");
|
|
55
|
+
assert.equal(describeCoHostedRunner(undefined), "runner unreported");
|
|
56
|
+
});
|
|
57
|
+
test("orgDisplay neutralises control characters in a tenant-chosen org name", () => {
|
|
58
|
+
const shown = orgDisplay("org_1", "Evil\u001b[2K\u001b]0;pwned\u0007\nName");
|
|
59
|
+
assert.ok(!/[\x00-\x1f\x7f-\x9f]/.test(shown), JSON.stringify(shown));
|
|
60
|
+
assert.match(shown, /^Evil.*Name \(org_1\)$/);
|
|
61
|
+
assert.ok(!/[\x00-\x1f\x7f-\x9f]/.test(describeVpnPin({ orgId: "o", orgName: "a\u001bb", hostPolicy: "pinned" })));
|
|
62
|
+
});
|
|
63
|
+
test("orgDisplay and describeVpnPin show the name with its id, or the bare id when the name does not resolve", () => {
|
|
64
|
+
assert.equal(orgDisplay("org_1", "Acme"), "Acme (org_1)");
|
|
65
|
+
assert.equal(orgDisplay("org_1", null), "org_1");
|
|
66
|
+
assert.equal(describeVpnPin({ orgId: "org_1", orgName: "Acme", hostPolicy: "dedicated" }), "Acme (org_1) [dedicated]");
|
|
67
|
+
assert.equal(describeVpnPin({ orgId: "org_1", orgName: null, hostPolicy: "pinned" }), "org_1 [pinned]");
|
|
68
|
+
});
|
|
69
|
+
//# sourceMappingURL=fleet-vpn.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fleet-vpn.test.js","sourceRoot":"","sources":["../src/fleet-vpn.test.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,9 +2,20 @@ export * from "./types.js";
|
|
|
2
2
|
export * from "./schemas.js";
|
|
3
3
|
export * from "./api-client.js";
|
|
4
4
|
export * from "./agent-readiness.js";
|
|
5
|
+
export * from "./fleet-vpn.js";
|
|
5
6
|
export * from "./ota-rollout.js";
|
|
6
7
|
export * from "./ota-bundle-hash.js";
|
|
7
8
|
export * from "./ota-artifact.js";
|
|
8
9
|
export * from "./vpn-network.js";
|
|
10
|
+
export * from "./vpn-egress.js";
|
|
11
|
+
export * from "./vpn-ssh.js";
|
|
12
|
+
export * from "./vpn-sandbox.js";
|
|
13
|
+
export * from "./ssh-deploy-key.js";
|
|
14
|
+
export * from "./vpn-egress-presets.js";
|
|
9
15
|
export * from "./artifact-upload.js";
|
|
16
|
+
export * from "./build-vpn.js";
|
|
17
|
+
export * from "./attestation.js";
|
|
18
|
+
export * from "./attestation-signing.js";
|
|
19
|
+
export * from "./attestation-keys.js";
|
|
20
|
+
export * from "./vpn-attestation.js";
|
|
10
21
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,sBAAsB,CAAC;AACrC,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,sBAAsB,CAAC;AACrC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,0BAA0B,CAAC;AACzC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -2,9 +2,20 @@ export * from "./types.js";
|
|
|
2
2
|
export * from "./schemas.js";
|
|
3
3
|
export * from "./api-client.js";
|
|
4
4
|
export * from "./agent-readiness.js";
|
|
5
|
+
export * from "./fleet-vpn.js";
|
|
5
6
|
export * from "./ota-rollout.js";
|
|
6
7
|
export * from "./ota-bundle-hash.js";
|
|
7
8
|
export * from "./ota-artifact.js";
|
|
8
9
|
export * from "./vpn-network.js";
|
|
10
|
+
export * from "./vpn-egress.js";
|
|
11
|
+
export * from "./vpn-ssh.js";
|
|
12
|
+
export * from "./vpn-sandbox.js";
|
|
13
|
+
export * from "./ssh-deploy-key.js";
|
|
14
|
+
export * from "./vpn-egress-presets.js";
|
|
9
15
|
export * from "./artifact-upload.js";
|
|
16
|
+
export * from "./build-vpn.js";
|
|
17
|
+
export * from "./attestation.js";
|
|
18
|
+
export * from "./attestation-signing.js";
|
|
19
|
+
export * from "./attestation-keys.js";
|
|
20
|
+
export * from "./vpn-attestation.js";
|
|
10
21
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,sBAAsB,CAAC;AACrC,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,sBAAsB,CAAC;AACrC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,0BAA0B,CAAC;AACzC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schemas-repo-url.test.d.ts","sourceRoot":"","sources":["../src/schemas-repo-url.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { createAppSchema, setVpnSshHostsSchema, updateAppSchema } from "./schemas.js";
|
|
4
|
+
const create = (repoUrl) => createAppSchema.safeParse({ name: "a", platform: "flutter", repoUrl });
|
|
5
|
+
const update = (repoUrl) => updateAppSchema.safeParse({ repoUrl });
|
|
6
|
+
// The transport matrix: https as always; ssh syntactically valid (the routes decide whether THIS app may use it).
|
|
7
|
+
const ACCEPTED = [
|
|
8
|
+
"https://github.com/org/repo",
|
|
9
|
+
"https://github.com/org/repo.git",
|
|
10
|
+
"https://git.corp.test:8443/org/repo.git",
|
|
11
|
+
"ssh://git@git.corp.test/org/repo.git",
|
|
12
|
+
"ssh://git@10.20.0.5:2222/srv/git/repo.git",
|
|
13
|
+
"git@git.corp.test:org/repo.git",
|
|
14
|
+
"git@git.corp.test:/srv/git/repo.git",
|
|
15
|
+
];
|
|
16
|
+
const REJECTED = [
|
|
17
|
+
"http://github.com/org/repo",
|
|
18
|
+
"git://github.com/org/repo",
|
|
19
|
+
"file:///etc/passwd",
|
|
20
|
+
"/local/path",
|
|
21
|
+
"ext::sh -c id",
|
|
22
|
+
"ssh://git.corp.test/org/repo.git", // no user
|
|
23
|
+
"ssh://git@-oProxyCommand=id/x",
|
|
24
|
+
"ssh://git@git.corp.test/org/repo.git;id",
|
|
25
|
+
"git@git.corp.test:org/$(id)",
|
|
26
|
+
"git@git.corp.test:../x",
|
|
27
|
+
"git.corp.test:org/repo.git",
|
|
28
|
+
"git+ssh://git@git.corp.test/org/repo.git",
|
|
29
|
+
"not a url",
|
|
30
|
+
"",
|
|
31
|
+
];
|
|
32
|
+
test("repoUrl: https and strictly-formed ssh remotes are accepted by create and update", () => {
|
|
33
|
+
for (const u of ACCEPTED) {
|
|
34
|
+
assert.equal(create(u).success, true, `create ${u}`);
|
|
35
|
+
assert.equal(update(u).success, true, `update ${u}`);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
test("repoUrl: every other transport and every malformed ssh remote is refused, with a message", () => {
|
|
39
|
+
for (const u of REJECTED) {
|
|
40
|
+
const c = create(u);
|
|
41
|
+
assert.equal(c.success, false, `create ${u}`);
|
|
42
|
+
assert.equal(update(u).success, false, `update ${u}`);
|
|
43
|
+
if (!c.success)
|
|
44
|
+
assert.ok(c.error.issues[0].message.length > 0);
|
|
45
|
+
}
|
|
46
|
+
assert.equal(create(42).success, false);
|
|
47
|
+
assert.equal(create(undefined).success, false);
|
|
48
|
+
});
|
|
49
|
+
test("repoUrl: the https-only wording is kept for an unsupported scheme and mentions the ssh exception", () => {
|
|
50
|
+
const r = create("git://github.com/org/repo");
|
|
51
|
+
assert.equal(r.success, false);
|
|
52
|
+
if (!r.success)
|
|
53
|
+
assert.match(r.error.issues[0].message, /https:\/\/ URL, or an ssh remote/);
|
|
54
|
+
});
|
|
55
|
+
test("createAppSchema takes an optional useVpn", () => {
|
|
56
|
+
assert.equal(createAppSchema.safeParse({ name: "a", platform: "ios", repoUrl: "https://github.com/o/r", useVpn: true }).success, true);
|
|
57
|
+
assert.equal(createAppSchema.safeParse({ name: "a", platform: "ios", repoUrl: "https://github.com/o/r", useVpn: "yes" }).success, false);
|
|
58
|
+
});
|
|
59
|
+
test("setVpnSshHostsSchema bounds the shape only", () => {
|
|
60
|
+
assert.equal(setVpnSshHostsSchema.safeParse({ knownHosts: [] }).success, true);
|
|
61
|
+
assert.equal(setVpnSshHostsSchema.safeParse({ knownHosts: ["a b c"] }).success, true);
|
|
62
|
+
assert.equal(setVpnSshHostsSchema.safeParse({ knownHosts: "text" }).success, false);
|
|
63
|
+
assert.equal(setVpnSshHostsSchema.safeParse({}).success, false);
|
|
64
|
+
assert.equal(setVpnSshHostsSchema.safeParse({ knownHosts: Array(513).fill("x") }).success, false);
|
|
65
|
+
assert.equal(setVpnSshHostsSchema.safeParse({ knownHosts: ["x".repeat(5000)] }).success, false);
|
|
66
|
+
});
|
|
67
|
+
//# sourceMappingURL=schemas-repo-url.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schemas-repo-url.test.js","sourceRoot":"","sources":["../src/schemas-repo-url.test.ts"],"names":[],"mappings":"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"}
|