laxy-verify 1.3.0 → 1.3.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 +486 -474
- package/dist/ai-analysis.d.ts +28 -0
- package/dist/ai-analysis.js +32 -0
- package/dist/audit/broken-links.d.ts +25 -25
- package/dist/audit/broken-links.js +97 -97
- package/dist/badge.d.ts +2 -2
- package/dist/badge.js +18 -18
- package/dist/cli.js +1242 -1250
- package/dist/compare-env.d.ts +23 -0
- package/dist/compare-env.js +55 -0
- package/dist/config.d.ts +102 -102
- package/dist/config.js +360 -360
- package/dist/entitlement.d.ts +15 -15
- package/dist/entitlement.js +98 -98
- package/dist/init-analysis.d.ts +6 -0
- package/dist/init-analysis.js +302 -0
- package/dist/init.js +132 -132
- package/dist/lighthouse.d.ts +37 -37
- package/dist/lighthouse.js +231 -231
- package/dist/report-markdown.d.ts +53 -53
- package/dist/report-markdown.js +407 -407
- package/dist/route-discovery.d.ts +7 -0
- package/dist/route-discovery.js +108 -0
- package/dist/security-audit.d.ts +17 -17
- package/dist/security-audit.js +127 -127
- package/dist/serve.d.ts +1 -0
- package/dist/serve.js +36 -0
- package/dist/verification-core/report.js +526 -526
- package/dist/verification-core/types.d.ts +164 -164
- package/dist/visual-diff.d.ts +33 -33
- package/dist/visual-diff.js +213 -213
- package/package.json +1 -1
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface RuntimeRouteDiscoveryResult {
|
|
2
|
+
routes: string[];
|
|
3
|
+
scriptUrls: string[];
|
|
4
|
+
}
|
|
5
|
+
export declare function extractScriptUrlsFromHtml(html: string, baseUrl: string): string[];
|
|
6
|
+
export declare function extractRoutesFromText(content: string): string[];
|
|
7
|
+
export declare function discoverRuntimeRoutes(baseUrl: string): Promise<RuntimeRouteDiscoveryResult>;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.extractScriptUrlsFromHtml = extractScriptUrlsFromHtml;
|
|
4
|
+
exports.extractRoutesFromText = extractRoutesFromText;
|
|
5
|
+
exports.discoverRuntimeRoutes = discoverRuntimeRoutes;
|
|
6
|
+
const SCRIPT_SRC_REGEX = /<script[^>]+src=["']([^"'#?]+(?:\?[^"'#]*)?)["']/gi;
|
|
7
|
+
const HTML_ROUTE_REGEX = /(?:href|data-href)=["'](\/[^"'#? ]*)/gi;
|
|
8
|
+
const ROUTE_SNIPPET_REGEXES = [
|
|
9
|
+
/(?:path|pathname|href|to|route|router\.push|navigate)\s*[:=(]\s*["'`]((?:\/(?!\/)[^"'`?#]+))["'`]/g,
|
|
10
|
+
/["'`]((?:\/(?!\/)[a-z0-9][^"'`?#]*))["'`]/gi,
|
|
11
|
+
];
|
|
12
|
+
function normalizeRoute(candidate) {
|
|
13
|
+
const decoded = candidate
|
|
14
|
+
.replace(/\\u002F/gi, "/")
|
|
15
|
+
.replace(/\\\//g, "/")
|
|
16
|
+
.trim();
|
|
17
|
+
if (!decoded.startsWith("/"))
|
|
18
|
+
return null;
|
|
19
|
+
const normalized = decoded
|
|
20
|
+
.split("?")[0]
|
|
21
|
+
?.split("#")[0]
|
|
22
|
+
?.replace(/\/+/g, "/")
|
|
23
|
+
?.replace(/\/$/, "") || "/";
|
|
24
|
+
if (normalized === "/" || normalized.length < 2)
|
|
25
|
+
return null;
|
|
26
|
+
if (normalized.startsWith("/_next/") || normalized.startsWith("/api/"))
|
|
27
|
+
return null;
|
|
28
|
+
if (/[.*:[\]]/.test(normalized))
|
|
29
|
+
return null;
|
|
30
|
+
if (/\.[a-z0-9]{2,8}$/i.test(normalized))
|
|
31
|
+
return null;
|
|
32
|
+
if (/\s/.test(normalized))
|
|
33
|
+
return null;
|
|
34
|
+
if (!/^\/[a-z0-9/_-]+$/i.test(normalized))
|
|
35
|
+
return null;
|
|
36
|
+
return normalized;
|
|
37
|
+
}
|
|
38
|
+
function extractScriptUrlsFromHtml(html, baseUrl) {
|
|
39
|
+
const urls = [];
|
|
40
|
+
const seen = new Set();
|
|
41
|
+
for (const match of html.matchAll(SCRIPT_SRC_REGEX)) {
|
|
42
|
+
const raw = match[1];
|
|
43
|
+
if (!raw)
|
|
44
|
+
continue;
|
|
45
|
+
try {
|
|
46
|
+
const scriptUrl = new URL(raw, baseUrl);
|
|
47
|
+
if (scriptUrl.origin !== new URL(baseUrl).origin)
|
|
48
|
+
continue;
|
|
49
|
+
const href = scriptUrl.href;
|
|
50
|
+
if (!seen.has(href)) {
|
|
51
|
+
seen.add(href);
|
|
52
|
+
urls.push(href);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// Ignore malformed URLs.
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return urls;
|
|
60
|
+
}
|
|
61
|
+
function extractRoutesFromText(content) {
|
|
62
|
+
const routes = [];
|
|
63
|
+
const seen = new Set();
|
|
64
|
+
for (const regex of ROUTE_SNIPPET_REGEXES) {
|
|
65
|
+
for (const match of content.matchAll(regex)) {
|
|
66
|
+
const route = normalizeRoute(match[1] ?? "");
|
|
67
|
+
if (!route || seen.has(route))
|
|
68
|
+
continue;
|
|
69
|
+
seen.add(route);
|
|
70
|
+
routes.push(route);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return routes;
|
|
74
|
+
}
|
|
75
|
+
async function discoverRuntimeRoutes(baseUrl) {
|
|
76
|
+
const htmlRes = await fetch(baseUrl, {
|
|
77
|
+
signal: AbortSignal.timeout(8000),
|
|
78
|
+
headers: { accept: "text/html,application/xhtml+xml" },
|
|
79
|
+
});
|
|
80
|
+
const html = await htmlRes.text();
|
|
81
|
+
const routes = new Set(extractRoutesFromText(html));
|
|
82
|
+
for (const match of html.matchAll(HTML_ROUTE_REGEX)) {
|
|
83
|
+
const route = normalizeRoute(match[1] ?? "");
|
|
84
|
+
if (route)
|
|
85
|
+
routes.add(route);
|
|
86
|
+
}
|
|
87
|
+
const scriptUrls = extractScriptUrlsFromHtml(html, baseUrl)
|
|
88
|
+
.filter((url) => /\/_next\/static\/chunks\/|assets\/|static\/|build\//i.test(url))
|
|
89
|
+
.slice(0, 10);
|
|
90
|
+
await Promise.all(scriptUrls.map(async (scriptUrl) => {
|
|
91
|
+
try {
|
|
92
|
+
const res = await fetch(scriptUrl, { signal: AbortSignal.timeout(5000) });
|
|
93
|
+
if (!res.ok)
|
|
94
|
+
return;
|
|
95
|
+
const content = await res.text();
|
|
96
|
+
for (const route of extractRoutesFromText(content)) {
|
|
97
|
+
routes.add(route);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// Skip chunk fetch failures. This is best-effort coverage expansion.
|
|
102
|
+
}
|
|
103
|
+
}));
|
|
104
|
+
return {
|
|
105
|
+
routes: Array.from(routes).sort((a, b) => a.localeCompare(b)),
|
|
106
|
+
scriptUrls,
|
|
107
|
+
};
|
|
108
|
+
}
|
package/dist/security-audit.d.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
export interface SecurityAuditResult {
|
|
2
|
-
totalVulnerabilities: number;
|
|
3
|
-
critical: number;
|
|
4
|
-
high: number;
|
|
5
|
-
moderate: number;
|
|
6
|
-
low: number;
|
|
7
|
-
summary: string;
|
|
8
|
-
missingHeaders: string[];
|
|
9
|
-
headerCheckUrl?: string;
|
|
10
|
-
headerCheckError?: string;
|
|
11
|
-
}
|
|
12
|
-
export declare function auditSecurityHeaders(url: string): Promise<{
|
|
13
|
-
missingHeaders: string[];
|
|
14
|
-
checkedUrl: string;
|
|
15
|
-
error?: string;
|
|
16
|
-
}>;
|
|
17
|
-
export declare function runSecurityAudit(cwd: string, appUrl?: string, timeoutMs?: number): Promise<SecurityAuditResult>;
|
|
1
|
+
export interface SecurityAuditResult {
|
|
2
|
+
totalVulnerabilities: number;
|
|
3
|
+
critical: number;
|
|
4
|
+
high: number;
|
|
5
|
+
moderate: number;
|
|
6
|
+
low: number;
|
|
7
|
+
summary: string;
|
|
8
|
+
missingHeaders: string[];
|
|
9
|
+
headerCheckUrl?: string;
|
|
10
|
+
headerCheckError?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function auditSecurityHeaders(url: string): Promise<{
|
|
13
|
+
missingHeaders: string[];
|
|
14
|
+
checkedUrl: string;
|
|
15
|
+
error?: string;
|
|
16
|
+
}>;
|
|
17
|
+
export declare function runSecurityAudit(cwd: string, appUrl?: string, timeoutMs?: number): Promise<SecurityAuditResult>;
|
package/dist/security-audit.js
CHANGED
|
@@ -1,127 +1,127 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.auditSecurityHeaders = auditSecurityHeaders;
|
|
4
|
-
exports.runSecurityAudit = runSecurityAudit;
|
|
5
|
-
/**
|
|
6
|
-
* npm audit wrapper plus runtime security-header checks.
|
|
7
|
-
*
|
|
8
|
-
* The package audit catches known dependency vulnerabilities.
|
|
9
|
-
* The header audit adds shallow runtime coverage for common missing
|
|
10
|
-
* browser-enforced protections on the running app.
|
|
11
|
-
*/
|
|
12
|
-
const node_child_process_1 = require("node:child_process");
|
|
13
|
-
async function runNpmAudit(cwd, timeoutMs) {
|
|
14
|
-
return new Promise((resolve) => {
|
|
15
|
-
const chunks = [];
|
|
16
|
-
const proc = process.platform === "win32"
|
|
17
|
-
? (0, node_child_process_1.spawn)(process.env.ComSpec || "cmd.exe", ["/d", "/c", "npm audit --json"], {
|
|
18
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
19
|
-
cwd,
|
|
20
|
-
})
|
|
21
|
-
: (0, node_child_process_1.spawn)("npm", ["audit", "--json"], {
|
|
22
|
-
shell: true,
|
|
23
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
24
|
-
cwd,
|
|
25
|
-
});
|
|
26
|
-
const timer = setTimeout(() => {
|
|
27
|
-
try {
|
|
28
|
-
proc.kill();
|
|
29
|
-
}
|
|
30
|
-
catch { }
|
|
31
|
-
resolve({ totalVulnerabilities: 0, critical: 0, high: 0, moderate: 0, low: 0 });
|
|
32
|
-
}, timeoutMs);
|
|
33
|
-
proc.stdout?.on("data", (chunk) => chunks.push(chunk.toString()));
|
|
34
|
-
proc.stderr?.on("data", () => { });
|
|
35
|
-
proc.on("exit", () => {
|
|
36
|
-
clearTimeout(timer);
|
|
37
|
-
try {
|
|
38
|
-
const json = JSON.parse(chunks.join(""));
|
|
39
|
-
const meta = json.metadata?.vulnerabilities ?? json.vulnerabilities ?? {};
|
|
40
|
-
const critical = meta.critical ?? 0;
|
|
41
|
-
const high = meta.high ?? 0;
|
|
42
|
-
const moderate = meta.moderate ?? 0;
|
|
43
|
-
const low = meta.low ?? 0;
|
|
44
|
-
resolve({
|
|
45
|
-
totalVulnerabilities: critical + high + moderate + low,
|
|
46
|
-
critical,
|
|
47
|
-
high,
|
|
48
|
-
moderate,
|
|
49
|
-
low,
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
catch {
|
|
53
|
-
resolve({ totalVulnerabilities: 0, critical: 0, high: 0, moderate: 0, low: 0 });
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
async function auditSecurityHeaders(url) {
|
|
59
|
-
const requiredHeaders = ["X-Frame-Options", "Content-Security-Policy"];
|
|
60
|
-
const urlObj = new URL(url);
|
|
61
|
-
if (urlObj.protocol === "https:") {
|
|
62
|
-
requiredHeaders.push("Strict-Transport-Security");
|
|
63
|
-
}
|
|
64
|
-
let response;
|
|
65
|
-
try {
|
|
66
|
-
response = await fetch(url, {
|
|
67
|
-
method: "HEAD",
|
|
68
|
-
redirect: "follow",
|
|
69
|
-
signal: AbortSignal.timeout(5000),
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
catch {
|
|
73
|
-
try {
|
|
74
|
-
response = await fetch(url, {
|
|
75
|
-
method: "GET",
|
|
76
|
-
redirect: "follow",
|
|
77
|
-
signal: AbortSignal.timeout(5000),
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
catch (error) {
|
|
81
|
-
return {
|
|
82
|
-
missingHeaders: [],
|
|
83
|
-
checkedUrl: url,
|
|
84
|
-
error: error instanceof Error ? error.message : String(error),
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
const missingHeaders = requiredHeaders.filter((headerName) => !response.headers.get(headerName));
|
|
89
|
-
return {
|
|
90
|
-
missingHeaders,
|
|
91
|
-
checkedUrl: response.url || url,
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
async function runSecurityAudit(cwd, appUrl, timeoutMs = 30000) {
|
|
95
|
-
console.log(" Running security audit (npm audit + runtime headers)...");
|
|
96
|
-
const [npmAudit, headerAudit] = await Promise.all([
|
|
97
|
-
runNpmAudit(cwd, timeoutMs),
|
|
98
|
-
appUrl ? auditSecurityHeaders(appUrl) : Promise.resolve(null),
|
|
99
|
-
]);
|
|
100
|
-
const parts = [];
|
|
101
|
-
if (npmAudit.critical > 0)
|
|
102
|
-
parts.push(`${npmAudit.critical} critical`);
|
|
103
|
-
if (npmAudit.high > 0)
|
|
104
|
-
parts.push(`${npmAudit.high} high`);
|
|
105
|
-
if (npmAudit.moderate > 0)
|
|
106
|
-
parts.push(`${npmAudit.moderate} moderate`);
|
|
107
|
-
if (npmAudit.low > 0)
|
|
108
|
-
parts.push(`${npmAudit.low} low`);
|
|
109
|
-
const missingHeaders = headerAudit?.missingHeaders ?? [];
|
|
110
|
-
if (missingHeaders.length > 0) {
|
|
111
|
-
parts.push(`missing headers: ${missingHeaders.join(", ")}`);
|
|
112
|
-
}
|
|
113
|
-
if (headerAudit?.error) {
|
|
114
|
-
parts.push(`header check skipped: ${headerAudit.error}`);
|
|
115
|
-
}
|
|
116
|
-
const summary = parts.length > 0
|
|
117
|
-
? parts.join(" | ")
|
|
118
|
-
: "No known vulnerabilities or missing security headers";
|
|
119
|
-
console.log(` Security: ${summary}`);
|
|
120
|
-
return {
|
|
121
|
-
...npmAudit,
|
|
122
|
-
summary,
|
|
123
|
-
missingHeaders,
|
|
124
|
-
headerCheckUrl: headerAudit?.checkedUrl,
|
|
125
|
-
headerCheckError: headerAudit?.error,
|
|
126
|
-
};
|
|
127
|
-
}
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.auditSecurityHeaders = auditSecurityHeaders;
|
|
4
|
+
exports.runSecurityAudit = runSecurityAudit;
|
|
5
|
+
/**
|
|
6
|
+
* npm audit wrapper plus runtime security-header checks.
|
|
7
|
+
*
|
|
8
|
+
* The package audit catches known dependency vulnerabilities.
|
|
9
|
+
* The header audit adds shallow runtime coverage for common missing
|
|
10
|
+
* browser-enforced protections on the running app.
|
|
11
|
+
*/
|
|
12
|
+
const node_child_process_1 = require("node:child_process");
|
|
13
|
+
async function runNpmAudit(cwd, timeoutMs) {
|
|
14
|
+
return new Promise((resolve) => {
|
|
15
|
+
const chunks = [];
|
|
16
|
+
const proc = process.platform === "win32"
|
|
17
|
+
? (0, node_child_process_1.spawn)(process.env.ComSpec || "cmd.exe", ["/d", "/c", "npm audit --json"], {
|
|
18
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
19
|
+
cwd,
|
|
20
|
+
})
|
|
21
|
+
: (0, node_child_process_1.spawn)("npm", ["audit", "--json"], {
|
|
22
|
+
shell: true,
|
|
23
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
24
|
+
cwd,
|
|
25
|
+
});
|
|
26
|
+
const timer = setTimeout(() => {
|
|
27
|
+
try {
|
|
28
|
+
proc.kill();
|
|
29
|
+
}
|
|
30
|
+
catch { }
|
|
31
|
+
resolve({ totalVulnerabilities: 0, critical: 0, high: 0, moderate: 0, low: 0 });
|
|
32
|
+
}, timeoutMs);
|
|
33
|
+
proc.stdout?.on("data", (chunk) => chunks.push(chunk.toString()));
|
|
34
|
+
proc.stderr?.on("data", () => { });
|
|
35
|
+
proc.on("exit", () => {
|
|
36
|
+
clearTimeout(timer);
|
|
37
|
+
try {
|
|
38
|
+
const json = JSON.parse(chunks.join(""));
|
|
39
|
+
const meta = json.metadata?.vulnerabilities ?? json.vulnerabilities ?? {};
|
|
40
|
+
const critical = meta.critical ?? 0;
|
|
41
|
+
const high = meta.high ?? 0;
|
|
42
|
+
const moderate = meta.moderate ?? 0;
|
|
43
|
+
const low = meta.low ?? 0;
|
|
44
|
+
resolve({
|
|
45
|
+
totalVulnerabilities: critical + high + moderate + low,
|
|
46
|
+
critical,
|
|
47
|
+
high,
|
|
48
|
+
moderate,
|
|
49
|
+
low,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
resolve({ totalVulnerabilities: 0, critical: 0, high: 0, moderate: 0, low: 0 });
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
async function auditSecurityHeaders(url) {
|
|
59
|
+
const requiredHeaders = ["X-Frame-Options", "Content-Security-Policy"];
|
|
60
|
+
const urlObj = new URL(url);
|
|
61
|
+
if (urlObj.protocol === "https:") {
|
|
62
|
+
requiredHeaders.push("Strict-Transport-Security");
|
|
63
|
+
}
|
|
64
|
+
let response;
|
|
65
|
+
try {
|
|
66
|
+
response = await fetch(url, {
|
|
67
|
+
method: "HEAD",
|
|
68
|
+
redirect: "follow",
|
|
69
|
+
signal: AbortSignal.timeout(5000),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
try {
|
|
74
|
+
response = await fetch(url, {
|
|
75
|
+
method: "GET",
|
|
76
|
+
redirect: "follow",
|
|
77
|
+
signal: AbortSignal.timeout(5000),
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
return {
|
|
82
|
+
missingHeaders: [],
|
|
83
|
+
checkedUrl: url,
|
|
84
|
+
error: error instanceof Error ? error.message : String(error),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const missingHeaders = requiredHeaders.filter((headerName) => !response.headers.get(headerName));
|
|
89
|
+
return {
|
|
90
|
+
missingHeaders,
|
|
91
|
+
checkedUrl: response.url || url,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
async function runSecurityAudit(cwd, appUrl, timeoutMs = 30000) {
|
|
95
|
+
console.log(" Running security audit (npm audit + runtime headers)...");
|
|
96
|
+
const [npmAudit, headerAudit] = await Promise.all([
|
|
97
|
+
runNpmAudit(cwd, timeoutMs),
|
|
98
|
+
appUrl ? auditSecurityHeaders(appUrl) : Promise.resolve(null),
|
|
99
|
+
]);
|
|
100
|
+
const parts = [];
|
|
101
|
+
if (npmAudit.critical > 0)
|
|
102
|
+
parts.push(`${npmAudit.critical} critical`);
|
|
103
|
+
if (npmAudit.high > 0)
|
|
104
|
+
parts.push(`${npmAudit.high} high`);
|
|
105
|
+
if (npmAudit.moderate > 0)
|
|
106
|
+
parts.push(`${npmAudit.moderate} moderate`);
|
|
107
|
+
if (npmAudit.low > 0)
|
|
108
|
+
parts.push(`${npmAudit.low} low`);
|
|
109
|
+
const missingHeaders = headerAudit?.missingHeaders ?? [];
|
|
110
|
+
if (missingHeaders.length > 0) {
|
|
111
|
+
parts.push(`missing headers: ${missingHeaders.join(", ")}`);
|
|
112
|
+
}
|
|
113
|
+
if (headerAudit?.error) {
|
|
114
|
+
parts.push(`header check skipped: ${headerAudit.error}`);
|
|
115
|
+
}
|
|
116
|
+
const summary = parts.length > 0
|
|
117
|
+
? parts.join(" | ")
|
|
118
|
+
: "No known vulnerabilities or missing security headers";
|
|
119
|
+
console.log(` Security: ${summary}`);
|
|
120
|
+
return {
|
|
121
|
+
...npmAudit,
|
|
122
|
+
summary,
|
|
123
|
+
missingHeaders,
|
|
124
|
+
headerCheckUrl: headerAudit?.checkedUrl,
|
|
125
|
+
headerCheckError: headerAudit?.error,
|
|
126
|
+
};
|
|
127
|
+
}
|
package/dist/serve.d.ts
CHANGED
|
@@ -9,5 +9,6 @@ export interface ServeResult {
|
|
|
9
9
|
port: number;
|
|
10
10
|
}
|
|
11
11
|
export declare function probeServerStatus(port: number): Promise<number | null>;
|
|
12
|
+
export declare function findAvailablePort(preferredPort: number, maxAttempts?: number): Promise<number>;
|
|
12
13
|
export declare function startDevServer(command: string, port: number, timeoutSec: number, cwd?: string): Promise<ServeResult>;
|
|
13
14
|
export declare function stopDevServer(pid: number): Promise<void>;
|
package/dist/serve.js
CHANGED
|
@@ -38,11 +38,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.DevServerTimeoutError = exports.PortConflictError = void 0;
|
|
40
40
|
exports.probeServerStatus = probeServerStatus;
|
|
41
|
+
exports.findAvailablePort = findAvailablePort;
|
|
41
42
|
exports.startDevServer = startDevServer;
|
|
42
43
|
exports.stopDevServer = stopDevServer;
|
|
43
44
|
const node_child_process_1 = require("node:child_process");
|
|
44
45
|
const fs = __importStar(require("node:fs"));
|
|
45
46
|
const http = __importStar(require("node:http"));
|
|
47
|
+
const net = __importStar(require("node:net"));
|
|
46
48
|
const os = __importStar(require("node:os"));
|
|
47
49
|
const path = __importStar(require("node:path"));
|
|
48
50
|
const tree_kill_1 = __importDefault(require("tree-kill"));
|
|
@@ -158,6 +160,40 @@ function httpGet(url) {
|
|
|
158
160
|
function probeServerStatus(port) {
|
|
159
161
|
return httpGet(`http://localhost:${port}/`);
|
|
160
162
|
}
|
|
163
|
+
async function canBindPort(port) {
|
|
164
|
+
return new Promise((resolve) => {
|
|
165
|
+
const server = net.createServer();
|
|
166
|
+
server.unref?.();
|
|
167
|
+
server.once("error", () => {
|
|
168
|
+
resolve(false);
|
|
169
|
+
});
|
|
170
|
+
server.listen(port, () => {
|
|
171
|
+
server.close(() => resolve(true));
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
async function findAvailablePort(preferredPort, maxAttempts = 20) {
|
|
176
|
+
for (let offset = 0; offset <= maxAttempts; offset++) {
|
|
177
|
+
const candidate = preferredPort + offset;
|
|
178
|
+
if (await canBindPort(candidate)) {
|
|
179
|
+
return candidate;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return new Promise((resolve, reject) => {
|
|
183
|
+
const server = net.createServer();
|
|
184
|
+
server.unref?.();
|
|
185
|
+
server.once("error", reject);
|
|
186
|
+
server.listen(0, () => {
|
|
187
|
+
const address = server.address();
|
|
188
|
+
if (!address || typeof address === "string") {
|
|
189
|
+
server.close(() => reject(new Error("Failed to resolve an available port.")));
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const { port } = address;
|
|
193
|
+
server.close(() => resolve(port));
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
}
|
|
161
197
|
async function startDevServer(command, port, timeoutSec, cwd) {
|
|
162
198
|
return new Promise((resolve, reject) => {
|
|
163
199
|
console.log(`Starting dev server: ${command}${cwd ? ` (cwd: ${cwd})` : ""}`);
|