@@ -2,6 +2,7 @@ import { execSync } from 'child_process'
22import fs from 'fs'
33
44const CONFIG_FILE = 'next.config.js'
5+ const BACKUP_FILE = 'next.config.js.bak'
56
67// Strips numbered prefixes like "01-", "02-" from path segments
78function stripNumberedPrefixes ( path : string ) : string {
@@ -11,89 +12,195 @@ function stripNumberedPrefixes(path: string): string {
1112 . join ( '/' )
1213}
1314
14- function removeRedirectFromConfig ( source : string ) {
15- let content = fs . readFileSync ( CONFIG_FILE , 'utf-8' )
16-
17- // Find the redirect entry
18- const redirectRegex = new RegExp (
19- `\\s*\\{\\s*source:\\s*'${ source } ',[^}]+\\},?\\n?` ,
20- 'g'
15+ // Helper to normalize paths for comparison
16+ function normalizeUrlPath ( filePath : string ) : string {
17+ return stripNumberedPrefixes (
18+ filePath
19+ . replace ( / ^ p a g e s \/ / , '/' )
20+ . replace ( / \. m d $ / , '' )
21+ . replace ( / \/ i n d e x $ / , '' )
22+ . toLowerCase ( ) // Normalize case for comparison
2123 )
24+ }
2225
23- // Remove the redirect
24- content = content . replace ( redirectRegex , '' )
26+ // Helper to find all redirects in config
27+ function parseExistingRedirects (
28+ content : string
29+ ) : Array < { source : string ; destination : string } > {
30+ const redirects : Array < { source : string ; destination : string } > = [ ]
31+ const redirectRegex = / { \s * s o u r c e : \s * ' ( [ ^ ' ] + ) ' , \s * d e s t i n a t i o n : \s * ' ( [ ^ ' ] + ) ' / g
32+ let match : RegExpExecArray | null
2533
26- // Clean up any double newlines created by the removal
27- content = content . replace ( / \n \n \n + / g, '\n\n' )
34+ while ( ( match = redirectRegex . exec ( content ) ) !== null ) {
35+ redirects . push ( {
36+ source : match [ 1 ] ,
37+ destination : match [ 2 ] ,
38+ } )
39+ }
2840
29- // Write back to the file
30- fs . writeFileSync ( CONFIG_FILE , content )
31- console . log ( `Removed redirect for: ${ source } ` )
41+ return redirects
3242}
3343
34- function addRedirectToConfig ( oldPath : string , newPath : string ) {
35- // Read the current next.config.js
36- let content = fs . readFileSync ( CONFIG_FILE , 'utf-8' )
44+ // Helper to detect circular redirects
45+ function detectCircularRedirects (
46+ redirects : Array < { source : string ; destination : string } > ,
47+ newSource : string ,
48+ newDestination : string
49+ ) : boolean {
50+ // Add the new redirect to the list
51+ const allRedirects = [
52+ ...redirects ,
53+ { source : newSource , destination : newDestination } ,
54+ ]
3755
38- // Convert file paths to URL paths and strip numbered prefixes
39- const oldUrl = stripNumberedPrefixes (
40- oldPath
41- . replace ( / ^ p a g e s \/ / , '/' )
42- . replace ( / \. m d $ / , '' )
43- . replace ( / \/ i n d e x $ / , '' )
56+ // Build a map of redirects for faster lookup
57+ const redirectMap = new Map (
58+ allRedirects . map ( ( { source, destination } ) => [ source , destination ] )
4459 )
4560
46- const newUrl = stripNumberedPrefixes (
47- newPath
48- . replace ( / ^ p a g e s \/ / , '/' )
49- . replace ( / \. m d $ / , '' )
50- . replace ( / \/ i n d e x $ / , '' )
51- )
61+ // Check each redirect for cycles
62+ for ( const { source } of allRedirects ) {
63+ let current = source
64+ const seen = new Set < string > ( )
5265
53- // Check if this is a file returning to its original location
54- // by looking for a redirect where this file's new location was the source
55- const returningFileRegex = new RegExp (
56- `source:\\s*'${ newUrl } ',[^}]+destination:\\s*'${ oldUrl } '`
57- )
66+ while ( redirectMap . has ( current ) ) {
67+ if ( seen . has ( current ) ) {
68+ return true // Circular redirect detected
69+ }
70+ seen . add ( current )
71+ current = redirectMap . get ( current ) !
72+ }
73+ }
5874
59- if ( content . match ( returningFileRegex ) ) {
60- console . log ( `File returning to original location: ${ newUrl } -> ${ oldUrl } ` )
61- removeRedirectFromConfig ( newUrl )
75+ return false
76+ }
6277
63- return
64- }
78+ // Improved removeRedirectFromConfig function
79+ function removeRedirectFromConfig ( sourcePath : string ) : void {
80+ try {
81+ let content = fs . readFileSync ( CONFIG_FILE , 'utf-8' )
6582
66- // Check if redirect already exists
67- if ( content . includes ( `source: '${ oldUrl } '` ) ) {
68- console . log ( `Redirect already exists for: ${ oldUrl } ` )
83+ // Create a regex that matches the entire redirect object
84+ const redirectRegex = new RegExp (
85+ `\\s*{\\s*source:\\s*'${ sourcePath } ',[^}]+},?\\n?` ,
86+ 'g'
87+ )
6988
70- return
89+ content = content . replace ( redirectRegex , '' )
90+
91+ // Clean up any empty lines or duplicate commas
92+ content = content
93+ . replace ( / , \s * , / g, ',' )
94+ . replace ( / \[ \s * , / , '[' )
95+ . replace ( / , \s * \] / , ']' )
96+
97+ // Create backup before writing
98+ fs . writeFileSync ( BACKUP_FILE , fs . readFileSync ( CONFIG_FILE ) )
99+ fs . writeFileSync ( CONFIG_FILE , content )
100+ console . log ( `Removed redirect for: ${ sourcePath } ` )
101+ } catch ( error ) {
102+ console . error ( 'Error removing redirect:' , error )
103+ throw error
71104 }
105+ }
72106
73- // Find the redirects array
74- const redirectsStart = content . indexOf ( 'return [' )
107+ function addRedirectToConfig ( oldPath : string , newPath : string ) : void {
108+ try {
109+ // Create backup before modifications
110+ fs . copyFileSync ( CONFIG_FILE , BACKUP_FILE )
75111
76- if ( redirectsStart === - 1 ) {
77- console . error ( 'Could not find redirects array in next.config.js ')
112+ // Read the current next.config.js
113+ let content = fs . readFileSync ( CONFIG_FILE , 'utf-8 ')
78114
79- return
80- }
115+ // Normalize paths for comparison
116+ const oldUrl = normalizeUrlPath ( oldPath )
117+ const newUrl = normalizeUrlPath ( newPath )
118+
119+ // Validate paths
120+ if ( ! oldUrl || ! newUrl ) {
121+ throw new Error ( 'Invalid path format' )
122+ }
123+
124+ // Don't add redirect if source and destination are the same
125+ if ( oldUrl === newUrl ) {
126+ console . log (
127+ `Skipping redirect where source equals destination: ${ oldUrl } `
128+ )
129+
130+ return
131+ }
132+
133+ // Parse existing redirects
134+ const existingRedirects = parseExistingRedirects ( content )
135+
136+ // Check for circular redirects
137+ if ( detectCircularRedirects ( existingRedirects , oldUrl , newUrl ) ) {
138+ console . error (
139+ `Adding redirect from ${ oldUrl } to ${ newUrl } would create a circular reference. Skipping.`
140+ )
141+
142+ return
143+ }
81144
82- // Insert the new redirect at the start of the array
83- const newRedirect = ` {
145+ // Check if this is a file returning to its original location
146+ const reverseRedirect = existingRedirects . find (
147+ ( r ) => r . source === newUrl && r . destination === oldUrl
148+ )
149+
150+ if ( reverseRedirect ) {
151+ console . log ( `File returning to original location: ${ newUrl } -> ${ oldUrl } ` )
152+ removeRedirectFromConfig ( newUrl )
153+
154+ return
155+ }
156+
157+ // Check if redirect already exists
158+ const existingRedirect = existingRedirects . find ( ( r ) => r . source === oldUrl )
159+
160+ if ( existingRedirect ) {
161+ if ( existingRedirect . destination === newUrl ) {
162+ console . log ( `Redirect already exists: ${ oldUrl } -> ${ newUrl } ` )
163+
164+ return
165+ }
166+ // Update existing redirect if destination has changed
167+ console . log (
168+ `Updating existing redirect: ${ oldUrl } -> ${ existingRedirect . destination } to ${ oldUrl } -> ${ newUrl } `
169+ )
170+ removeRedirectFromConfig ( oldUrl )
171+ }
172+
173+ // Find the redirects array
174+ const redirectsStart = content . indexOf ( 'return [' )
175+
176+ if ( redirectsStart === - 1 ) {
177+ throw new Error ( 'Could not find redirects array in next.config.js' )
178+ }
179+
180+ // Insert the new redirect at the start of the array
181+ const newRedirect = ` {
84182 source: '${ oldUrl } ',
85183 destination: '${ newUrl } ',
86184 permanent: true,
87185 },\n`
88186
89- content =
90- content . slice ( 0 , redirectsStart + 8 ) +
91- newRedirect +
92- content . slice ( redirectsStart + 8 )
187+ content =
188+ content . slice ( 0 , redirectsStart + 8 ) +
189+ newRedirect +
190+ content . slice ( redirectsStart + 8 )
93191
94- // Write back to the file
95- fs . writeFileSync ( CONFIG_FILE , content )
96- console . log ( `Added redirect: ${ oldUrl } -> ${ newUrl } ` )
192+ // Write back to the file
193+ fs . writeFileSync ( CONFIG_FILE , content )
194+ console . log ( `Added redirect: ${ oldUrl } -> ${ newUrl } ` )
195+ } catch ( error ) {
196+ console . error ( 'Error adding redirect:' , error )
197+ // Restore backup if it exists
198+ if ( fs . existsSync ( BACKUP_FILE ) ) {
199+ fs . copyFileSync ( BACKUP_FILE , CONFIG_FILE )
200+ console . log ( 'Restored backup due to error' )
201+ }
202+ throw error
203+ }
97204}
98205
99206// Get all renamed/moved markdown files in the pages directory
@@ -131,7 +238,7 @@ function getMovedFiles(): Array<[string, string]> {
131238}
132239
133240// Process all moved files
134- function processMovedFiles ( ) {
241+ function processMovedFiles ( ) : void {
135242 const movedFiles = getMovedFiles ( )
136243
137244 if ( movedFiles . length === 0 ) {
@@ -142,8 +249,12 @@ function processMovedFiles() {
142249
143250 console . log ( 'Processing moved files...' )
144251 movedFiles . forEach ( ( [ oldPath , newPath ] ) => {
145- console . log ( `\nProcessing move: ${ oldPath } -> ${ newPath } ` )
146- addRedirectToConfig ( oldPath , newPath )
252+ try {
253+ console . log ( `\nProcessing move: ${ oldPath } -> ${ newPath } ` )
254+ addRedirectToConfig ( oldPath , newPath )
255+ } catch ( error ) {
256+ console . error ( `Error processing move ${ oldPath } -> ${ newPath } :` , error )
257+ }
147258 } )
148259}
149260
0 commit comments