laxy-verify 1.3.1 → 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 +7 -0
- package/dist/cli.js +22 -29
- package/dist/serve.d.ts +1 -0
- package/dist/serve.js +36 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -161,6 +161,8 @@ Or enable them in `.laxy.yml` and just run:
|
|
|
161
161
|
npx laxy-verify .
|
|
162
162
|
```
|
|
163
163
|
|
|
164
|
+
If the default or configured dev port is already occupied, `laxy-verify` automatically starts its temporary verification server on the next available port. Use `--port` only when you want to attach to an already-running server yourself.
|
|
165
|
+
|
|
164
166
|
## Example output
|
|
165
167
|
|
|
166
168
|
```text
|
|
@@ -432,6 +434,11 @@ It is a pre-merge and pre-release verification layer, not your entire quality sy
|
|
|
432
434
|
|
|
433
435
|
## Changelog
|
|
434
436
|
|
|
437
|
+
### v1.3.2 - Auto port fallback
|
|
438
|
+
|
|
439
|
+
- If port `3000` or your configured verification port is already in use, `laxy-verify` now starts its temporary dev server on the next available port automatically
|
|
440
|
+
- `--port` keeps its existing meaning: attach to an already-running server instead of starting a temporary one
|
|
441
|
+
|
|
435
442
|
### v1.3.1 - Publish fix
|
|
436
443
|
|
|
437
444
|
- Fix npm package contents so `login`, `logout`, and `whoami` ship with the required `dist/init-analysis.js` runtime dependency
|
package/dist/cli.js
CHANGED
|
@@ -113,12 +113,6 @@ function exitGracefully(code) {
|
|
|
113
113
|
}
|
|
114
114
|
process.exit(code);
|
|
115
115
|
}
|
|
116
|
-
async function ensurePortAvailableForVerification(port) {
|
|
117
|
-
const status = await (0, serve_js_1.probeServerStatus)(port);
|
|
118
|
-
if (status === null)
|
|
119
|
-
return;
|
|
120
|
-
throw new Error(`An existing local server is already responding on port ${port} (HTTP ${status}). Stop the running app before using laxy-verify, because the verification build can invalidate an active dev session.`);
|
|
121
|
-
}
|
|
122
116
|
function parseArgs() {
|
|
123
117
|
const raw = process.argv.slice(2);
|
|
124
118
|
let projectDir = ".";
|
|
@@ -405,7 +399,7 @@ async function run() {
|
|
|
405
399
|
--config <path> Path to .laxy.yml
|
|
406
400
|
--fail-on unverified | bronze | silver | gold
|
|
407
401
|
--skip-lighthouse Skip Lighthouse but still run build and E2E
|
|
408
|
-
--port <port> Use an already-running dev server on this port (skip build & server start)
|
|
402
|
+
--port <port> Use an already-running dev server on this port (skip build & server start)
|
|
409
403
|
--share Create a public share link for this verification result (Pro)
|
|
410
404
|
--compare <url> Compare Lighthouse scores against a reference environment URL (Pro)
|
|
411
405
|
--plan-override free | pro | team (testing metadata only)
|
|
@@ -427,9 +421,10 @@ async function run() {
|
|
|
427
421
|
2 Configuration error
|
|
428
422
|
|
|
429
423
|
Examples:
|
|
430
|
-
npx laxy-verify --init --run # Setup + first verification
|
|
431
|
-
npx laxy-verify . # Run in current directory
|
|
432
|
-
npx laxy-verify .
|
|
424
|
+
npx laxy-verify --init --run # Setup + first verification
|
|
425
|
+
npx laxy-verify . # Run in current directory
|
|
426
|
+
npx laxy-verify . # Auto-falls back if port 3000 is already busy
|
|
427
|
+
npx laxy-verify . --ci # CI mode
|
|
433
428
|
npx laxy-verify . --fail-on silver # Block Bronze or worse
|
|
434
429
|
npx laxy-verify . --port 3001 # Use existing dev server on port 3001
|
|
435
430
|
npx laxy-verify . --share # Save and share a public verification link
|
|
@@ -566,28 +561,25 @@ async function run() {
|
|
|
566
561
|
}
|
|
567
562
|
const buildCmd = config.build_command || detected.buildCmd;
|
|
568
563
|
const devCmd = config.dev_command || detected.devCmd;
|
|
569
|
-
const
|
|
564
|
+
const requestedPort = args.port ?? config.port;
|
|
570
565
|
const useExistingServer = args.port !== undefined;
|
|
566
|
+
let verificationPort = requestedPort;
|
|
571
567
|
if (!useExistingServer) {
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
catch (err) {
|
|
576
|
-
console.error(`Preflight error: ${err instanceof Error ? err.message : String(err)}`);
|
|
577
|
-
exitGracefully(2);
|
|
578
|
-
return;
|
|
568
|
+
verificationPort = await (0, serve_js_1.findAvailablePort)(requestedPort);
|
|
569
|
+
if (verificationPort !== requestedPort && args.format !== "json") {
|
|
570
|
+
console.log(`Port ${requestedPort} is busy. laxy-verify will start its temporary dev server on port ${verificationPort} for this run.`);
|
|
579
571
|
}
|
|
580
572
|
}
|
|
581
573
|
else {
|
|
582
574
|
// Verify the existing server is actually reachable
|
|
583
|
-
const status = await (0, serve_js_1.probeServerStatus)(
|
|
575
|
+
const status = await (0, serve_js_1.probeServerStatus)(verificationPort);
|
|
584
576
|
if (status === null) {
|
|
585
|
-
console.error(`Preflight error: No server responding on port ${
|
|
577
|
+
console.error(`Preflight error: No server responding on port ${verificationPort}. Make sure the dev server is running before using --port.`);
|
|
586
578
|
exitGracefully(2);
|
|
587
579
|
return;
|
|
588
580
|
}
|
|
589
581
|
if (args.format !== "json") {
|
|
590
|
-
console.log(`Using existing dev server on port ${
|
|
582
|
+
console.log(`Using existing dev server on port ${verificationPort} (HTTP ${status})`);
|
|
591
583
|
}
|
|
592
584
|
}
|
|
593
585
|
let buildResult;
|
|
@@ -677,11 +669,12 @@ async function run() {
|
|
|
677
669
|
let servePid;
|
|
678
670
|
try {
|
|
679
671
|
if (!useExistingServer) {
|
|
680
|
-
const serve = await (0, serve_js_1.startDevServer)(devCmd,
|
|
672
|
+
const serve = await (0, serve_js_1.startDevServer)(devCmd, verificationPort, config.dev_timeout, args.projectDir);
|
|
681
673
|
servePid = serve.pid;
|
|
682
674
|
activeDevServerPid = serve.pid;
|
|
675
|
+
verificationPort = serve.port;
|
|
683
676
|
}
|
|
684
|
-
const verifyUrl = `http://localhost:${
|
|
677
|
+
const verifyUrl = `http://localhost:${verificationPort}/`;
|
|
685
678
|
const verificationTier = (0, index_js_1.planToVerificationTier)(effectiveFeatures.plan);
|
|
686
679
|
try {
|
|
687
680
|
const runtimeRouteDiscovery = await (0, route_discovery_js_1.discoverRuntimeRoutes)(verifyUrl);
|
|
@@ -702,13 +695,13 @@ async function run() {
|
|
|
702
695
|
: undefined;
|
|
703
696
|
if (explicitRoutes && explicitRoutes.length > 0) {
|
|
704
697
|
// Multi-route mode: run on all explicitly configured routes
|
|
705
|
-
multiRouteLhResult = await (0, lighthouse_js_1.runLighthouseOnRoutes)(
|
|
698
|
+
multiRouteLhResult = await (0, lighthouse_js_1.runLighthouseOnRoutes)(verificationPort, config.lighthouse_runs, explicitRoutes);
|
|
706
699
|
lighthouseErrorCount = multiRouteLhResult.perRoute.reduce((n, r) => n + r.errors.length, 0);
|
|
707
700
|
scores = multiRouteLhResult.aggregated ?? undefined;
|
|
708
701
|
}
|
|
709
702
|
else {
|
|
710
703
|
// Single-route mode (default): root only
|
|
711
|
-
const lhResult = await (0, lighthouse_js_1.runLighthouse)(
|
|
704
|
+
const lhResult = await (0, lighthouse_js_1.runLighthouse)(verificationPort, config.lighthouse_runs);
|
|
712
705
|
lighthouseErrorCount = lhResult.errors.length;
|
|
713
706
|
scores = lhResult.scores ?? undefined;
|
|
714
707
|
}
|
|
@@ -728,7 +721,7 @@ async function run() {
|
|
|
728
721
|
}
|
|
729
722
|
if (!args.skipLighthouse) {
|
|
730
723
|
try {
|
|
731
|
-
multiViewportScores = await (0, multi_viewport_js_1.runMultiViewportLighthouse)(
|
|
724
|
+
multiViewportScores = await (0, multi_viewport_js_1.runMultiViewportLighthouse)(verificationPort);
|
|
732
725
|
(0, multi_viewport_js_1.printMultiViewportResults)(multiViewportScores, adjustedThresholds);
|
|
733
726
|
allViewportsOk = (0, multi_viewport_js_1.allViewportsPass)(multiViewportScores, adjustedThresholds);
|
|
734
727
|
if (multiViewportScores.screenshotDiffs) {
|
|
@@ -844,7 +837,7 @@ async function run() {
|
|
|
844
837
|
...lastCrawlRoutes,
|
|
845
838
|
]).slice(0, config.max_lighthouse_routes);
|
|
846
839
|
console.log(`\n Running Lighthouse on ${crawlRoutes.length} discovered routes...`);
|
|
847
|
-
multiRouteLhResult = await (0, lighthouse_js_1.runLighthouseOnRoutes)(
|
|
840
|
+
multiRouteLhResult = await (0, lighthouse_js_1.runLighthouseOnRoutes)(verificationPort, config.lighthouse_runs, crawlRoutes);
|
|
848
841
|
if (multiRouteLhResult.aggregated) {
|
|
849
842
|
scores = multiRouteLhResult.aggregated;
|
|
850
843
|
lighthouseResult = {
|
|
@@ -895,7 +888,7 @@ async function run() {
|
|
|
895
888
|
// Mobile Lighthouse (standalone — not part of multi-viewport)
|
|
896
889
|
if (!args.skipLighthouse && !args.multiViewport) {
|
|
897
890
|
try {
|
|
898
|
-
mobileLighthouseScores = await (0, multi_viewport_js_1.runMobileLighthouse)(
|
|
891
|
+
mobileLighthouseScores = await (0, multi_viewport_js_1.runMobileLighthouse)(verificationPort);
|
|
899
892
|
}
|
|
900
893
|
catch (mlErr) {
|
|
901
894
|
console.error(`Mobile Lighthouse error: ${mlErr instanceof Error ? mlErr.message : String(mlErr)}`);
|
|
@@ -984,7 +977,7 @@ async function run() {
|
|
|
984
977
|
// Pro: multi-environment comparison
|
|
985
978
|
if (effectiveFeatures.compare_env && args.compareUrl) {
|
|
986
979
|
try {
|
|
987
|
-
compareEnvResult = await (0, compare_env_js_1.runEnvComparison)(
|
|
980
|
+
compareEnvResult = await (0, compare_env_js_1.runEnvComparison)(verificationPort, args.compareUrl, 1);
|
|
988
981
|
}
|
|
989
982
|
catch (cmpErr) {
|
|
990
983
|
console.error(`Env comparison error: ${cmpErr instanceof Error ? cmpErr.message : String(cmpErr)}`);
|
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})` : ""}`);
|