3
3
import assert from "node:assert"
4
4
import { parseArgs } from "node:util"
5
5
import { join } from "node:path"
6
+ import { readFile , writeFile } from "node:fs/promises"
6
7
7
8
import {
8
9
getSchedule ,
@@ -11,7 +12,6 @@ import {
11
12
RequestContext ,
12
13
} from "@/app/conf/_api/sched-client"
13
14
import type { SchedSpeaker } from "@/app/conf/_api/sched-types"
14
- import { readFile , writeFile } from "node:fs/promises"
15
15
16
16
/**
17
17
* Sched API rate limit is 30 requests per minute per token.
@@ -20,7 +20,7 @@ import { readFile, writeFile } from "node:fs/promises"
20
20
* - one request for the list of speakers with partial details
21
21
* - and N requests for the full details of each speaker
22
22
*/
23
- const SPEAKER_DETAILS_REQUEST_QUOTA = 10
23
+ const SPEAKER_DETAILS_REQUEST_QUOTA = 2
24
24
25
25
const options = {
26
26
year : {
@@ -35,7 +35,7 @@ const options = {
35
35
36
36
const unsafeKeys = Object . keys as < T extends object > ( obj : T ) => Array < keyof T >
37
37
38
- async function main ( ) {
38
+ ; ( async function main ( ) {
39
39
try {
40
40
const { values } = parseArgs ( { options } )
41
41
@@ -60,11 +60,7 @@ async function main() {
60
60
}
61
61
throw error
62
62
}
63
- }
64
-
65
- if ( require . main === module ) {
66
- void main ( )
67
- }
63
+ } ) ( )
68
64
69
65
async function sync ( year : number , token : string ) {
70
66
const apiUrl = {
@@ -77,9 +73,8 @@ async function sync(year: number, token: string) {
77
73
78
74
const ctx : RequestContext = { apiUrl, token }
79
75
80
- const scriptDir = __dirname
81
- const speakersFilePath = join ( scriptDir , "speakers.json" )
82
- const scheduleFilePath = join ( scriptDir , `schedule-${ year } .json` )
76
+ const speakersFilePath = join ( import . meta. dirname , "speakers.json" )
77
+ const scheduleFilePath = join ( import . meta. dirname , `schedule-${ year } .json` )
83
78
84
79
console . log ( "Getting schedule and speakers list..." )
85
80
@@ -172,7 +167,13 @@ async function updateSpeakerDetails(
172
167
const location = locations . get ( speaker . username )
173
168
if ( location ) {
174
169
const [ key , index ] = location
175
- comparison [ key ] [ index ] = speaker
170
+ if ( key === "changed" ) {
171
+ comparison [ key ] [ index ] . new = speaker
172
+ comparison [ key ] [ index ] . new [ "~syncedDetailsAt" ] = Date . now ( )
173
+ } else {
174
+ comparison [ key ] [ index ] = speaker
175
+ comparison [ key ] [ index ] [ "~syncedDetailsAt" ] = Date . now ( )
176
+ }
176
177
}
177
178
}
178
179
}
@@ -208,7 +209,7 @@ function compare<T extends object>(
208
209
for ( const newItem of news ) {
209
210
const oldItem = oldMap . get ( newItem [ key ] )
210
211
if ( oldItem ) {
211
- if ( JSON . stringify ( oldItem ) === JSON . stringify ( newItem ) ) {
212
+ if ( deepStrictEqualWithoutInternals ( oldItem , newItem ) ) {
212
213
unchanged . push ( oldItem )
213
214
} else {
214
215
changed . push ( {
@@ -235,42 +236,48 @@ function printComparison<T extends object>(
235
236
name : string ,
236
237
key : keyof T ,
237
238
) {
238
- console . log ( `Removed: ${ comparison . removed . length } ` )
239
- console . log ( `Changed: ${ comparison . changed . length } ` )
240
- console . log ( `Unchanged: ${ comparison . unchanged . length } ` )
241
-
242
- console . log ( `Added ${ comparison . added . length } ${ name } ` )
243
- for ( const item of comparison . added ) {
244
- console . log ( `+ ${ item } ` )
239
+ if ( comparison . added . length > 0 ) {
240
+ console . log ( `Added ${ comparison . added . length } ${ name } ` )
241
+ for ( const item of comparison . added ) {
242
+ console . log ( green ( `+ ${ JSON . stringify ( item ) } ` ) )
243
+ }
245
244
}
246
245
247
- console . log ( `Removed ${ comparison . removed . length } ${ name } ` )
248
- for ( const item of comparison . removed ) {
249
- console . log ( `- ${ item } ` )
246
+ if ( comparison . removed . length > 0 ) {
247
+ console . log ( `Removed ${ comparison . removed . length } ${ name } ` )
248
+ for ( const item of comparison . removed ) {
249
+ console . log ( red ( `- ${ JSON . stringify ( item ) } ` ) )
250
+ }
250
251
}
251
252
252
- console . log ( `Unchanged ${ comparison . unchanged . length } ${ name } ` )
253
- for ( const item of comparison . unchanged ) {
254
- console . log ( item )
253
+ if ( comparison . unchanged . length > 0 ) {
254
+ console . log ( `Unchanged ${ comparison . unchanged . length } ${ name } ` )
255
+ for ( const item of comparison . unchanged ) {
256
+ console . log ( yellow ( `{ ${ String ( key ) } : ${ item [ key ] } , ... }` ) )
257
+ }
255
258
}
256
259
257
- console . log ( `Changed ${ comparison . changed . length } ${ name } ` )
258
- for ( const change of comparison . changed ) {
259
- console . log ( change . new [ key ] , objectDiff ( change ) )
260
+ if ( comparison . changed . length > 0 ) {
261
+ console . log ( `Changed ${ comparison . changed . length } ${ name } ` )
262
+ for ( const change of comparison . changed ) {
263
+ console . log ( change . new [ key ] + "\n" , objectDiff ( change ) )
264
+ }
260
265
}
261
266
}
262
267
263
268
function objectDiff < T extends object > ( change : Change < T > ) : string {
264
269
const allKeys = [
265
270
...new Set ( [ ...unsafeKeys ( change . old ) , ...unsafeKeys ( change . new ) ] ) ,
266
- ] . sort ( )
271
+ ]
272
+ . sort ( )
273
+ . filter ( key => ! String ( key ) . startsWith ( "~" ) )
267
274
268
275
const diff = allKeys
269
276
. map ( key => {
270
277
const oldValue = change . old [ key ]
271
278
const newValue = change . new [ key ]
272
279
273
- if ( oldValue === newValue ) {
280
+ if ( JSON . stringify ( oldValue ) === JSON . stringify ( newValue ) ) {
274
281
return null
275
282
}
276
283
@@ -280,9 +287,69 @@ function objectDiff<T extends object>(change: Change<T>): string {
280
287
281
288
return diff
282
289
. map ( diff => {
283
- return `\x1b[33m ${ String ( diff . key ) } \x1b[0m: ${ diff . oldValue } -> ${ diff . newValue } `
290
+ return `${ yellow ( String ( diff . key ) ) } :\n ${ red ( "-" + JSON . stringify ( diff . oldValue ) ) } \n ${ green ( "+" + JSON . stringify ( diff . newValue ) ) } `
284
291
} )
285
292
. join ( "\n" )
286
293
}
287
294
295
+ function green ( text : string ) {
296
+ return `\x1b[32m${ text } \x1b[0m`
297
+ }
298
+
299
+ function red ( text : string ) {
300
+ return `\x1b[31m${ text } \x1b[0m`
301
+ }
302
+
303
+ function yellow ( text : string ) {
304
+ return `\x1b[33m${ text } \x1b[0m`
305
+ }
306
+
307
+ function deepStrictEqualWithoutInternals ( a : unknown , b : unknown ) : boolean {
308
+ if ( a === b ) return true
309
+
310
+ if ( a === null || b === null || a === undefined || b === undefined ) {
311
+ return a === b
312
+ }
313
+
314
+ if ( typeof a !== typeof b ) return false
315
+
316
+ if ( typeof a !== "object" ) return false
317
+
318
+ if ( Array . isArray ( a ) !== Array . isArray ( b ) ) return false
319
+
320
+ if ( Array . isArray ( a ) && Array . isArray ( b ) ) {
321
+ if ( a . length !== b . length ) return false
322
+
323
+ for ( let i = 0 ; i < a . length ; i ++ ) {
324
+ if ( ! deepStrictEqualWithoutInternals ( a [ i ] , b [ i ] ) ) {
325
+ return false
326
+ }
327
+ }
328
+ return true
329
+ }
330
+
331
+ const aObj = a as Record < string , unknown >
332
+ const bObj = b as Record < string , unknown >
333
+
334
+ const aKeys = Object . keys ( aObj ) . filter ( key => ! key . startsWith ( "~" ) )
335
+ const bKeys = Object . keys ( bObj ) . filter ( key => ! key . startsWith ( "~" ) )
336
+
337
+ if ( aKeys . length !== bKeys . length ) return false
338
+
339
+ aKeys . sort ( )
340
+ bKeys . sort ( )
341
+
342
+ for ( let i = 0 ; i < aKeys . length ; i ++ ) {
343
+ if ( aKeys [ i ] !== bKeys [ i ] ) return false
344
+ }
345
+
346
+ for ( const key of aKeys ) {
347
+ if ( ! deepStrictEqualWithoutInternals ( aObj [ key ] , bObj [ key ] ) ) {
348
+ return false
349
+ }
350
+ }
351
+
352
+ return true
353
+ }
354
+
288
355
// #endregion utility
0 commit comments