@@ -7,6 +7,14 @@ import { handleUnprocessedJiraIssues } from './handleUnprocessedJiraIssues.js';
77
88export let jiraIssues = [ ] ;
99
10+ const isValidISOString = ( str ) => {
11+ if ( typeof str !== 'string' ) {
12+ return false ;
13+ }
14+ // Matches ISO 8601 format: YYYY-MM-DDTHH:MM:SS with optional milliseconds (.XXX) and timezone (Z or ±HH:MM)
15+ return / ^ \d { 4 } - \d { 2 } - \d { 2 } T \d { 2 } : \d { 2 } : \d { 2 } (?: \. \d { 1 , 3 } ) ? (?: Z | [ + - ] \d { 2 } : ? \d { 2 } ) ? $ / i. test ( str ) ;
16+ } ;
17+
1018// Parse command line arguments
1119function parseArgs ( ) {
1220 const args = process . argv . slice ( 2 ) ;
@@ -15,24 +23,34 @@ function parseArgs() {
1523 for ( let i = 0 ; i < args . length ; i ++ ) {
1624 if ( args [ i ] === '--since' && i + 1 < args . length ) {
1725 const sinceValue = args [ i + 1 ] ;
18-
19- // Check if the value is in MM-DD-YYYY format
20- const mmddPattern = / ^ ( \d { 1 , 2 } ) - ( \d { 1 , 2 } ) - ( \d { 4 } ) $ / ;
21- const match = sinceValue . match ( mmddPattern ) ;
22-
23- if ( match ) {
24- // Convert MM-DD-YYYY to ISO string
25- const month = parseInt ( match [ 1 ] ) - 1 ; // Month is 0-indexed in Date constructor
26- const day = parseInt ( match [ 2 ] ) ;
27- const year = parseInt ( match [ 3 ] ) ;
28- const date = new Date ( year , month , day ) ;
29- options . since = date . toISOString ( ) ;
30- } else {
31- // Assume it's already in ISO format or another valid format
26+
27+ if ( isValidISOString ( sinceValue ) ) {
3228 options . since = sinceValue ;
29+ } else {
30+ // Check if the value is in MM-DD-YYYY format
31+ const mmddPattern = / ^ ( \d { 1 , 2 } ) - ( \d { 1 , 2 } ) - ( \d { 4 } ) $ / ;
32+ const match = sinceValue . match ( mmddPattern ) ;
33+
34+ if ( match ) {
35+ // Convert MM-DD-YYYY to ISO string
36+ const month = parseInt ( match [ 1 ] ) - 1 ; // Month is 0-indexed in Date constructor
37+ const day = parseInt ( match [ 2 ] ) ;
38+ const year = parseInt ( match [ 3 ] ) ;
39+ const date = new Date ( year , month , day ) ;
40+ options . since = date . toISOString ( ) ;
41+ } else {
42+ // Default fallback: 7 days ago
43+ console . error ( `** ERROR: Invalid date format: ${ sinceValue } \nDefaulting to 7 days ago` ) ;
44+ options . since = ( ( ) => {
45+ const date = new Date ( ) ;
46+ date . setDate ( date . getDate ( ) - 7 ) ;
47+ return date . toISOString ( ) ;
48+ } ) ( ) ;
49+ }
3350 }
3451
35- i ++ ; // Skip the next argument since we consumed it
52+ // Skip the next argument since we consumed it
53+ i ++ ;
3654 }
3755 }
3856
@@ -61,14 +79,13 @@ class ErrorCollector {
6179 logErrors ( ) {
6280 if ( ! this . hasErrors ( ) ) return ;
6381
64- console . error ( '\n=== Sync Errors ===' ) ;
6582 this . errors . forEach ( ( { context, message, response } ) => {
66- console . error ( `${ context } : ${ message } ` ) ;
83+ console . error ( ` ${ context } : ${ message } ` ) ;
6784 if ( response ) {
6885 console . error ( ` Response: ${ JSON . stringify ( response ) } ` ) ;
6986 }
87+ console . error ( ' ==============================================' ) ;
7088 } ) ;
71- console . error ( '==================\n' ) ;
7289 }
7390
7491 clear ( ) {
@@ -79,62 +96,52 @@ class ErrorCollector {
7996// Create global error collector instance
8097export const errorCollector = new ErrorCollector ( ) ;
8198
82- async function syncIssues ( repo , owner , since ) {
83- /* DEBUG
84- debugger;
85- return;
86- */
99+ const fetchJiraIssues = async ( owner , repo , since ) => {
100+ console . log ( ' - fetching Jira...' ) ;
101+ const response = await jiraClient . get ( '/rest/api/2/search' , {
102+ params : {
103+ jql : `project = PF AND component = "${ repo } " AND status not in (Closed, Resolved) ORDER BY key ASC` ,
104+ maxResults : 1000 ,
105+ fields : 'key,id,description,status, issuetype' ,
106+ } ,
107+ } ) ;
108+ const jiraIssues = response ?. data ?. issues || [ ] ;
109+ console . log ( ` --> Found ${ jiraIssues . length } open Jira issues for Jira component ${ repo } ` ) ;
110+ return jiraIssues ;
111+ } ;
112+
113+ const fetchGitHubIssues = async ( owner , repo , since ) => {
114+ console . log ( ' - fetching GitHub...' ) ;
115+ const githubApiResponse = await getRepoIssues ( repo , owner , since ) ;
116+ // Sort by number to ensure consistent order, enables easier debugging
117+ const githubIssues = githubApiResponse . repository . issues . nodes . sort (
118+ ( a , b ) => a . number - b . number
119+ ) ;
120+
121+ console . log ( ` --> Found ${ githubIssues . length } updated GitHub issue${ githubIssues . length === 1 ? '' : 's' } in ${ owner } /${ repo } \n` ) ;
122+ return githubIssues ;
123+ } ;
124+
125+ async function syncIssues ( owner , repo , since ) {
126+ console . log ( `\n=== START Syncing issues for repo ${ owner } /${ repo } updated since ${ since } ===\n` ) ;
87127 try {
88- jiraIssues = [ ] ;
89128 // Clear any previous errors
90129 // errorCollector.clear();
91- // Fetch all Jira issues for the specific repo/component
92- console . log ( 'fetching Jira' ) ;
93- const response = await jiraClient . get ( '/rest/api/2/search' , {
94- params : {
95- jql : `project = PF AND component = "${ repo } " AND status not in (Closed, Resolved) ORDER BY key ASC` ,
96- maxResults : 1000 ,
97- fields : 'key,id,description,status, issuetype' ,
98- } ,
99- } ) ;
100- // Assign the issues to our exported variable
101- jiraIssues = response . data . issues ;
102- // Get GitHub issues from GraphQL response
103- console . log ( 'fetching GH' ) ;
104- const githubApiResponse = await getRepoIssues ( repo , owner , since ) ;
105- // Sort by number to ensure consistent order, enables easier debugging
106- const githubIssues = githubApiResponse . repository . issues . nodes . sort (
107- ( a , b ) => a . number - b . number
108- ) ;
109130
110- console . log (
111- `** Found ${ jiraIssues . length } open Jira issues for repo ${
112- repo
113- } and ${ githubIssues . length } open GitHub issues **\n`
114- ) ;
131+ // Fetch all open Jira issues for the specific repo/component, save to exported variable
132+ jiraIssues = await fetchJiraIssues ( owner , repo , since ) ;
133+
134+ // Fetch all updated GitHub issues from GraphQL response
135+ const githubIssues = await fetchGitHubIssues ( owner , repo , since ) ;
115136
116137 // Keep track of which Jira issues we've processed
117138 const processedJiraIssues = new Set ( ) ;
118139
119140 // Process GitHub issues
120141 for ( const [ index , issue ] of githubIssues . entries ( ) ) {
121- // Skip if the issue is a pull request (GraphQL doesn't return pull requests)
122- if ( issue . pull_request ) {
123- console . log (
124- `(${ index + 1 } /${ githubIssues . length } ) Skipping pull request #${
125- issue . number
126- } `
127- ) ;
128- continue ;
129- }
130-
131- // Skip if the issue is an Initiative
132- if ( issue ?. issueType ?. name === 'Initiative' ) {
133- console . log (
134- `(${ index + 1 } /${ githubIssues . length } ) Skipping Initiative #${
135- issue . number
136- } \n`
137- ) ;
142+ // Skip if the issue is a pull request (GraphQL doesn't return pull requests) or an Initiative
143+ if ( issue . pull_request || issue ?. issueType ?. name === 'Initiative' ) {
144+ console . log ( `(${ index + 1 } /${ githubIssues . length } ) Skipping ${ issue . pull_request ? 'pull request' : 'Initiative' } #${ issue . number } ` ) ;
138145 continue ;
139146 }
140147
@@ -143,19 +150,11 @@ async function syncIssues(repo, owner, since) {
143150
144151 if ( ! jiraIssue ) {
145152 // Create new Jira issue
146- console . log (
147- `(${ index + 1 } /${
148- githubIssues . length
149- } ) Creating new Jira issue for GitHub issue #${ issue . number } `
150- ) ;
153+ console . log ( `(${ index + 1 } /${ githubIssues . length } ) Creating new Jira issue for GitHub issue #${ issue . number } ` ) ;
151154 await createJiraIssue ( issue ) ;
152155 } else {
153156 // Update existing Jira issue
154- console . log (
155- `(${ index + 1 } /${
156- githubIssues . length
157- } ) Updating existing Jira issue: ${ jiraIssue . key } ...`
158- ) ;
157+ console . log ( `(${ index + 1 } /${ githubIssues . length } ) Updating existing Jira issue: ${ jiraIssue . key } ` ) ;
159158 await updateJiraIssue ( jiraIssue , issue ) ;
160159 processedJiraIssues . add ( jiraIssue . key ) ;
161160 }
@@ -167,20 +166,20 @@ async function syncIssues(repo, owner, since) {
167166 ( issue ) => ! processedJiraIssues . has ( issue . key )
168167 ) ;
169168
170- if ( unprocessedJiraIssues . length > 0 ) {
171- // Uncomment to process all open Jira issues regardless of GitHub status
169+ // Uncomment to process all open Jira issues regardless of GitHub status
170+ // if (unprocessedJiraIssues.length > 0) {
172171 // await handleUnprocessedJiraIssues(unprocessedJiraIssues, repo);
173- }
174-
175- // Log any collected errors at the end
176- console . log ( `\n=== ${ repo . toUpperCase ( ) } ERRORS ===\n` ) ;
177- errorCollector . logErrors ( ) ;
178- console . log ( `\n=== END ${ repo . toUpperCase ( ) } ERRORS ===\n` ) ;
172+ // }
179173 } catch ( error ) {
180174 errorCollector . addError ( 'INDEX: Sync process' , error ) ;
181- console . log ( `\n=== ${ repo . toUpperCase ( ) } ERRORS ===\n` ) ;
182- errorCollector . logErrors ( ) ;
183- console . log ( `\n=== END ${ repo . toUpperCase ( ) } ERRORS ===\n` ) ;
175+ } finally {
176+ if ( errorCollector . hasErrors ( ) ) {
177+ // Log any collected errors at the end
178+ console . log ( `\n=== ${ repo . toUpperCase ( ) } ERRORS ===\n` ) ;
179+ errorCollector . logErrors ( ) ;
180+ console . log ( `\n=== END ${ repo . toUpperCase ( ) } ERRORS ===\n` ) ;
181+ errorCollector . clear ( ) ;
182+ }
184183 }
185184}
186185
@@ -190,11 +189,11 @@ const since = options.since || (() => {
190189 const date = new Date ( ) ;
191190 date . setDate ( date . getDate ( ) - 7 ) ;
192191 return date . toISOString ( ) ;
193- } ) ( ) ; // Default fallback: 7 days ago
192+ } ) ( ) ;
194193
195194console . log ( `Syncing issues since: ${ since } ` ) ;
196195
197196// If syncing all, loop through availableComponents
198197for ( const { name, owner} of availableComponents ) {
199- await syncIssues ( name , owner , since ) ;
198+ await syncIssues ( owner , name , since ) ;
200199}
0 commit comments