99import { LinkChecker } from 'linkinator' ;
1010import { fileURLToPath } from 'url' ;
1111import { dirname , join } from 'path' ;
12+ import http from 'http' ;
1213import net from 'net' ;
1314import fs from 'fs' ;
14- import { spawn } from 'child_process' ;
15+ import { spawn , spawnSync } from 'child_process' ;
1516
1617const __dirname = dirname ( fileURLToPath ( import . meta. url ) ) ;
17- const distDir = join ( __dirname , '../dist' ) ;
1818
19- // Check if dist exists
20- if ( ! fs . existsSync ( distDir ) ) {
21- console . error ( '❌ dist/ directory not found. Run `npm run build` first.' ) ;
22- process . exit ( 1 ) ;
19+ export function getServeDir ( projectRoot = join ( __dirname , '..' ) ) {
20+ const distDir = join ( projectRoot , 'dist' ) ;
21+ const cloudflareClientDir = join ( distDir , 'client' ) ;
22+
23+ if (
24+ fs . existsSync ( cloudflareClientDir ) &&
25+ fs . existsSync ( join ( cloudflareClientDir , 'index.html' ) )
26+ ) {
27+ return cloudflareClientDir ;
28+ }
29+
30+ return distDir ;
31+ }
32+
33+ export function getServeCommand ( serveDir , port ) {
34+ if ( fs . existsSync ( join ( serveDir , '_worker.js' ) ) ) {
35+ return {
36+ label : 'Cloudflare Pages worker' ,
37+ command : 'npx' ,
38+ args : [ 'wrangler' , 'pages' , 'dev' , serveDir , '--port' , String ( port ) ] ,
39+ } ;
40+ }
41+
42+ return {
43+ label : 'static files' ,
44+ command : 'npx' ,
45+ args : [ 'serve' , serveDir , '-l' , String ( port ) ] ,
46+ } ;
2347}
2448
2549// Find an available port
2650function findPort ( startPort = 3456 ) {
2751 return new Promise ( ( resolve ) => {
2852 const server = net . createServer ( ) ;
29- server . listen ( startPort , ( ) => {
53+ server . listen ( startPort , '127.0.0.1' , ( ) => {
3054 server . close ( ( ) => resolve ( startPort ) ) ;
3155 } ) ;
3256 server . on ( 'error' , ( ) => resolve ( findPort ( startPort + 1 ) ) ) ;
3357 } ) ;
3458}
3559
60+ function killProcessesOnPort ( port ) {
61+ const result = spawnSync ( 'lsof' , [ `-tiTCP:${ port } ` , '-sTCP:LISTEN' ] , {
62+ encoding : 'utf8' ,
63+ } ) ;
64+
65+ if ( result . error || result . status !== 0 || ! result . stdout ?. trim ( ) ) {
66+ return ;
67+ }
68+
69+ for ( const pid of result . stdout . trim ( ) . split ( / \s + / ) ) {
70+ try {
71+ process . kill ( Number ( pid ) , 'SIGKILL' ) ;
72+ } catch {
73+ // Ignore cleanup errors; the primary process may have exited already.
74+ }
75+ }
76+ }
77+
3678// Wait for server to be ready
37- async function waitForServer ( port , maxAttempts = 20 ) {
79+ async function waitForServer ( port , maxAttempts = 60 ) {
3880 for ( let i = 0 ; i < maxAttempts ; i ++ ) {
3981 try {
4082 await new Promise ( ( resolve , reject ) => {
41- const socket = new net . Socket ( ) ;
42- socket . setTimeout ( 500 ) ;
43- socket . on ( 'connect' , ( ) => { socket . destroy ( ) ; resolve ( ) ; } ) ;
44- socket . on ( 'error' , reject ) ;
45- socket . on ( 'timeout' , ( ) => { socket . destroy ( ) ; reject ( ) ; } ) ;
46- socket . connect ( port , 'localhost' ) ;
83+ const request = http . get ( {
84+ hostname : 'localhost' ,
85+ port,
86+ path : '/' ,
87+ timeout : 1000 ,
88+ } , ( response ) => {
89+ response . resume ( ) ;
90+ if ( response . statusCode && response . statusCode < 500 ) {
91+ resolve ( ) ;
92+ } else {
93+ reject ( new Error ( `HTTP ${ response . statusCode } ` ) ) ;
94+ }
95+ } ) ;
96+
97+ request . on ( 'error' , reject ) ;
98+ request . on ( 'timeout' , ( ) => {
99+ request . destroy ( ) ;
100+ reject ( new Error ( 'HTTP readiness timed out' ) ) ;
101+ } ) ;
47102 } ) ;
48103 return true ;
49104 } catch {
@@ -54,14 +109,26 @@ async function waitForServer(port, maxAttempts = 20) {
54109}
55110
56111async function main ( ) {
112+ const projectRoot = join ( __dirname , '..' ) ;
113+ const serveDir = getServeDir ( projectRoot ) ;
114+
115+ // Check if dist exists
116+ if ( ! fs . existsSync ( serveDir ) ) {
117+ console . error ( '❌ dist/ directory not found. Run `npm run build` first.' ) ;
118+ process . exit ( 1 ) ;
119+ }
120+
57121 const port = await findPort ( ) ;
122+ const serveCommand = getServeCommand ( serveDir , port ) ;
58123 console . log ( `🚀 Starting server on port ${ port } ...` ) ;
124+ console . log ( `📁 Serving ${ serveDir } via ${ serveCommand . label } ` ) ;
59125
60- // Start serve in the background (no -s flag so 404s are real 404s)
61- const server = spawn ( 'npx' , [ 'serve' , 'dist' , '-l' , String ( port ) ] , {
62- cwd : join ( __dirname , '..' ) ,
126+ // Start the built site in the background. Cloudflare adapter builds need the
127+ // Pages worker for dynamic routes; static builds can use serve directly.
128+ const server = spawn ( serveCommand . command , serveCommand . args , {
129+ cwd : projectRoot ,
63130 stdio : [ 'ignore' , 'pipe' , 'pipe' ] ,
64- detached : true
131+ detached : false
65132 } ) ;
66133
67134 let serverPid = server . pid ;
@@ -72,6 +139,12 @@ async function main() {
72139 spawnError = err ;
73140 console . error ( '❌ Failed to spawn server:' , err ) ;
74141 } ) ;
142+ server . stdout ?. on ( 'data' , ( chunk ) => {
143+ if ( process . env . CHECK_LINKS_DEBUG ) process . stdout . write ( chunk ) ;
144+ } ) ;
145+ server . stderr ?. on ( 'data' , ( chunk ) => {
146+ if ( process . env . CHECK_LINKS_DEBUG ) process . stderr . write ( chunk ) ;
147+ } ) ;
75148
76149 try {
77150 // Check if spawn failed before waiting
@@ -103,6 +176,8 @@ async function main() {
103176 'railway.com' ,
104177 'api.pirsch.io' ,
105178 'echodash.com' , // Bot protection returns 403
179+ 'github.com' , // GitHub edit/source links often rate-limit CI
180+ 'automem.ai' , // Canonical/OG URLs point at production before deploy
106181 'localhost:4321' , // Dev server references
107182 'localhost:4322' ,
108183 'localhost:4323' ,
@@ -128,15 +203,18 @@ async function main() {
128203 // Only kill if we have a valid PID
129204 if ( serverPid && typeof serverPid === 'number' ) {
130205 try {
131- process . kill ( - serverPid ) ;
206+ server . kill ( 'SIGTERM' ) ;
132207 } catch ( killErr ) {
133208 // Ignore kill errors in cleanup
134209 }
135210 }
211+ killProcessesOnPort ( port ) ;
136212 }
137213}
138214
139- main ( ) . catch ( ( err ) => {
140- console . error ( 'Error:' , err ) ;
141- process . exit ( 1 ) ;
142- } ) ;
215+ if ( process . argv [ 1 ] === fileURLToPath ( import . meta. url ) ) {
216+ main ( ) . catch ( ( err ) => {
217+ console . error ( 'Error:' , err ) ;
218+ process . exit ( 1 ) ;
219+ } ) ;
220+ }
0 commit comments