nielsr HF Staff commited on
Commit
f2f339a
·
1 Parent(s): d5c56bd

Add validate conferences Github action

Browse files
.github/workflows/validate-conferences.yml ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Validate Conference Data
2
+
3
+ on:
4
+ pull_request:
5
+ paths:
6
+ - 'src/data/conferences/**'
7
+ - 'scripts/validate-conferences.ts'
8
+ push:
9
+ branches: [main]
10
+ paths:
11
+ - 'src/data/conferences/**'
12
+ - 'scripts/validate-conferences.ts'
13
+
14
+ jobs:
15
+ validate:
16
+ runs-on: ubuntu-latest
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ - uses: actions/setup-node@v4
20
+ with:
21
+ node-version: 20
22
+ cache: npm
23
+ - run: npm ci
24
+ - run: npm run validate
README.md CHANGED
@@ -37,7 +37,7 @@ To keep things minimal, we mainly focus on top-tier conferences in AI.
37
  To add or update a deadline:
38
  - Fork the repository
39
  - Add a new block to the appropriate conference file in [src/data/conferences/](src/data/conferences/). Do not update an existing block of a previous time the conference took place but rather add a new block at the bottom of the file
40
- - Make sure it has the `title`, `year`, `id`, `link`, `deadlines`, `timezone`, `date`, `city`, `country`, `tags` attributes
41
  + For deadlines that use "Anywhere on Earth" timing, always use `AoE` (not `UTC-12`). Other supported formats: IANA timezone names (e.g. `Asia/Seoul`), `UTC±X`, `GMT±X`. See available IANA timezone strings [here](https://momentjs.com/timezone/).
42
  - Optionally add a `venue`, `note` and `hindex` (this refers to the h5-index from [here](https://scholar.google.com/citations?view_op=top_venues&vq=eng)) which indicates the importance of a conference
43
 
 
37
  To add or update a deadline:
38
  - Fork the repository
39
  - Add a new block to the appropriate conference file in [src/data/conferences/](src/data/conferences/). Do not update an existing block of a previous time the conference took place but rather add a new block at the bottom of the file
40
+ - Make sure it has the `title`, `year`, `id`, `link`, `deadlines`, `date`, `city`, `country`, `tags` attributes
41
  + For deadlines that use "Anywhere on Earth" timing, always use `AoE` (not `UTC-12`). Other supported formats: IANA timezone names (e.g. `Asia/Seoul`), `UTC±X`, `GMT±X`. See available IANA timezone strings [here](https://momentjs.com/timezone/).
42
  - Optionally add a `venue`, `note` and `hindex` (this refers to the h5-index from [here](https://scholar.google.com/citations?view_op=top_venues&vq=eng)) which indicates the importance of a conference
43
 
package-lock.json CHANGED
@@ -64,6 +64,7 @@
64
  "@eslint/js": "^9.9.0",
65
  "@modyfi/vite-plugin-yaml": "^1.1.1",
66
  "@tailwindcss/typography": "^0.5.15",
 
67
  "@types/node": "^22.5.5",
68
  "@types/react": "^18.3.3",
69
  "@types/react-dom": "^18.3.0",
@@ -76,6 +77,7 @@
76
  "lovable-tagger": "^1.1.3",
77
  "postcss": "^8.4.47",
78
  "tailwindcss": "^3.4.11",
 
79
  "typescript": "^5.5.3",
80
  "typescript-eslint": "^8.0.1",
81
  "vite": "^5.4.1"
@@ -512,6 +514,23 @@
512
  "node": ">=12"
513
  }
514
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
515
  "node_modules/@esbuild/sunos-x64": {
516
  "version": "0.21.5",
517
  "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
@@ -2940,6 +2959,13 @@
2940
  "dev": true,
2941
  "license": "MIT"
2942
  },
 
 
 
 
 
 
 
2943
  "node_modules/@types/json-schema": {
2944
  "version": "7.0.15",
2945
  "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
@@ -4705,6 +4731,19 @@
4705
  "node": ">=6"
4706
  }
4707
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
4708
  "node_modules/glob": {
4709
  "version": "10.4.5",
4710
  "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
@@ -6349,6 +6388,16 @@
6349
  "node": ">=4"
6350
  }
6351
  },
 
 
 
 
 
 
 
 
 
 
6352
  "node_modules/reusify": {
6353
  "version": "1.0.4",
6354
  "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
@@ -6784,6 +6833,493 @@
6784
  "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==",
6785
  "license": "0BSD"
6786
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6787
  "node_modules/type-check": {
6788
  "version": "0.4.0",
6789
  "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
 
64
  "@eslint/js": "^9.9.0",
65
  "@modyfi/vite-plugin-yaml": "^1.1.1",
66
  "@tailwindcss/typography": "^0.5.15",
67
+ "@types/js-yaml": "^4.0.9",
68
  "@types/node": "^22.5.5",
69
  "@types/react": "^18.3.3",
70
  "@types/react-dom": "^18.3.0",
 
77
  "lovable-tagger": "^1.1.3",
78
  "postcss": "^8.4.47",
79
  "tailwindcss": "^3.4.11",
80
+ "tsx": "^4.21.0",
81
  "typescript": "^5.5.3",
82
  "typescript-eslint": "^8.0.1",
83
  "vite": "^5.4.1"
 
514
  "node": ">=12"
515
  }
516
  },
517
+ "node_modules/@esbuild/openharmony-arm64": {
518
+ "version": "0.27.3",
519
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz",
520
+ "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==",
521
+ "cpu": [
522
+ "arm64"
523
+ ],
524
+ "dev": true,
525
+ "license": "MIT",
526
+ "optional": true,
527
+ "os": [
528
+ "openharmony"
529
+ ],
530
+ "engines": {
531
+ "node": ">=18"
532
+ }
533
+ },
534
  "node_modules/@esbuild/sunos-x64": {
535
  "version": "0.21.5",
536
  "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
 
2959
  "dev": true,
2960
  "license": "MIT"
2961
  },
2962
+ "node_modules/@types/js-yaml": {
2963
+ "version": "4.0.9",
2964
+ "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz",
2965
+ "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==",
2966
+ "dev": true,
2967
+ "license": "MIT"
2968
+ },
2969
  "node_modules/@types/json-schema": {
2970
  "version": "7.0.15",
2971
  "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
 
4731
  "node": ">=6"
4732
  }
4733
  },
4734
+ "node_modules/get-tsconfig": {
4735
+ "version": "4.13.6",
4736
+ "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz",
4737
+ "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==",
4738
+ "dev": true,
4739
+ "license": "MIT",
4740
+ "dependencies": {
4741
+ "resolve-pkg-maps": "^1.0.0"
4742
+ },
4743
+ "funding": {
4744
+ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
4745
+ }
4746
+ },
4747
  "node_modules/glob": {
4748
  "version": "10.4.5",
4749
  "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
 
6388
  "node": ">=4"
6389
  }
6390
  },
6391
+ "node_modules/resolve-pkg-maps": {
6392
+ "version": "1.0.0",
6393
+ "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
6394
+ "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
6395
+ "dev": true,
6396
+ "license": "MIT",
6397
+ "funding": {
6398
+ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
6399
+ }
6400
+ },
6401
  "node_modules/reusify": {
6402
  "version": "1.0.4",
6403
  "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
 
6833
  "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==",
6834
  "license": "0BSD"
6835
  },
6836
+ "node_modules/tsx": {
6837
+ "version": "4.21.0",
6838
+ "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
6839
+ "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
6840
+ "dev": true,
6841
+ "license": "MIT",
6842
+ "dependencies": {
6843
+ "esbuild": "~0.27.0",
6844
+ "get-tsconfig": "^4.7.5"
6845
+ },
6846
+ "bin": {
6847
+ "tsx": "dist/cli.mjs"
6848
+ },
6849
+ "engines": {
6850
+ "node": ">=18.0.0"
6851
+ },
6852
+ "optionalDependencies": {
6853
+ "fsevents": "~2.3.3"
6854
+ }
6855
+ },
6856
+ "node_modules/tsx/node_modules/@esbuild/aix-ppc64": {
6857
+ "version": "0.27.3",
6858
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz",
6859
+ "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==",
6860
+ "cpu": [
6861
+ "ppc64"
6862
+ ],
6863
+ "dev": true,
6864
+ "license": "MIT",
6865
+ "optional": true,
6866
+ "os": [
6867
+ "aix"
6868
+ ],
6869
+ "engines": {
6870
+ "node": ">=18"
6871
+ }
6872
+ },
6873
+ "node_modules/tsx/node_modules/@esbuild/android-arm": {
6874
+ "version": "0.27.3",
6875
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz",
6876
+ "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==",
6877
+ "cpu": [
6878
+ "arm"
6879
+ ],
6880
+ "dev": true,
6881
+ "license": "MIT",
6882
+ "optional": true,
6883
+ "os": [
6884
+ "android"
6885
+ ],
6886
+ "engines": {
6887
+ "node": ">=18"
6888
+ }
6889
+ },
6890
+ "node_modules/tsx/node_modules/@esbuild/android-arm64": {
6891
+ "version": "0.27.3",
6892
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz",
6893
+ "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==",
6894
+ "cpu": [
6895
+ "arm64"
6896
+ ],
6897
+ "dev": true,
6898
+ "license": "MIT",
6899
+ "optional": true,
6900
+ "os": [
6901
+ "android"
6902
+ ],
6903
+ "engines": {
6904
+ "node": ">=18"
6905
+ }
6906
+ },
6907
+ "node_modules/tsx/node_modules/@esbuild/android-x64": {
6908
+ "version": "0.27.3",
6909
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz",
6910
+ "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==",
6911
+ "cpu": [
6912
+ "x64"
6913
+ ],
6914
+ "dev": true,
6915
+ "license": "MIT",
6916
+ "optional": true,
6917
+ "os": [
6918
+ "android"
6919
+ ],
6920
+ "engines": {
6921
+ "node": ">=18"
6922
+ }
6923
+ },
6924
+ "node_modules/tsx/node_modules/@esbuild/darwin-arm64": {
6925
+ "version": "0.27.3",
6926
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz",
6927
+ "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==",
6928
+ "cpu": [
6929
+ "arm64"
6930
+ ],
6931
+ "dev": true,
6932
+ "license": "MIT",
6933
+ "optional": true,
6934
+ "os": [
6935
+ "darwin"
6936
+ ],
6937
+ "engines": {
6938
+ "node": ">=18"
6939
+ }
6940
+ },
6941
+ "node_modules/tsx/node_modules/@esbuild/darwin-x64": {
6942
+ "version": "0.27.3",
6943
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz",
6944
+ "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==",
6945
+ "cpu": [
6946
+ "x64"
6947
+ ],
6948
+ "dev": true,
6949
+ "license": "MIT",
6950
+ "optional": true,
6951
+ "os": [
6952
+ "darwin"
6953
+ ],
6954
+ "engines": {
6955
+ "node": ">=18"
6956
+ }
6957
+ },
6958
+ "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": {
6959
+ "version": "0.27.3",
6960
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz",
6961
+ "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==",
6962
+ "cpu": [
6963
+ "arm64"
6964
+ ],
6965
+ "dev": true,
6966
+ "license": "MIT",
6967
+ "optional": true,
6968
+ "os": [
6969
+ "freebsd"
6970
+ ],
6971
+ "engines": {
6972
+ "node": ">=18"
6973
+ }
6974
+ },
6975
+ "node_modules/tsx/node_modules/@esbuild/freebsd-x64": {
6976
+ "version": "0.27.3",
6977
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz",
6978
+ "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==",
6979
+ "cpu": [
6980
+ "x64"
6981
+ ],
6982
+ "dev": true,
6983
+ "license": "MIT",
6984
+ "optional": true,
6985
+ "os": [
6986
+ "freebsd"
6987
+ ],
6988
+ "engines": {
6989
+ "node": ">=18"
6990
+ }
6991
+ },
6992
+ "node_modules/tsx/node_modules/@esbuild/linux-arm": {
6993
+ "version": "0.27.3",
6994
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz",
6995
+ "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==",
6996
+ "cpu": [
6997
+ "arm"
6998
+ ],
6999
+ "dev": true,
7000
+ "license": "MIT",
7001
+ "optional": true,
7002
+ "os": [
7003
+ "linux"
7004
+ ],
7005
+ "engines": {
7006
+ "node": ">=18"
7007
+ }
7008
+ },
7009
+ "node_modules/tsx/node_modules/@esbuild/linux-arm64": {
7010
+ "version": "0.27.3",
7011
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz",
7012
+ "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==",
7013
+ "cpu": [
7014
+ "arm64"
7015
+ ],
7016
+ "dev": true,
7017
+ "license": "MIT",
7018
+ "optional": true,
7019
+ "os": [
7020
+ "linux"
7021
+ ],
7022
+ "engines": {
7023
+ "node": ">=18"
7024
+ }
7025
+ },
7026
+ "node_modules/tsx/node_modules/@esbuild/linux-ia32": {
7027
+ "version": "0.27.3",
7028
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz",
7029
+ "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==",
7030
+ "cpu": [
7031
+ "ia32"
7032
+ ],
7033
+ "dev": true,
7034
+ "license": "MIT",
7035
+ "optional": true,
7036
+ "os": [
7037
+ "linux"
7038
+ ],
7039
+ "engines": {
7040
+ "node": ">=18"
7041
+ }
7042
+ },
7043
+ "node_modules/tsx/node_modules/@esbuild/linux-loong64": {
7044
+ "version": "0.27.3",
7045
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz",
7046
+ "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==",
7047
+ "cpu": [
7048
+ "loong64"
7049
+ ],
7050
+ "dev": true,
7051
+ "license": "MIT",
7052
+ "optional": true,
7053
+ "os": [
7054
+ "linux"
7055
+ ],
7056
+ "engines": {
7057
+ "node": ">=18"
7058
+ }
7059
+ },
7060
+ "node_modules/tsx/node_modules/@esbuild/linux-mips64el": {
7061
+ "version": "0.27.3",
7062
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz",
7063
+ "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==",
7064
+ "cpu": [
7065
+ "mips64el"
7066
+ ],
7067
+ "dev": true,
7068
+ "license": "MIT",
7069
+ "optional": true,
7070
+ "os": [
7071
+ "linux"
7072
+ ],
7073
+ "engines": {
7074
+ "node": ">=18"
7075
+ }
7076
+ },
7077
+ "node_modules/tsx/node_modules/@esbuild/linux-ppc64": {
7078
+ "version": "0.27.3",
7079
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz",
7080
+ "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==",
7081
+ "cpu": [
7082
+ "ppc64"
7083
+ ],
7084
+ "dev": true,
7085
+ "license": "MIT",
7086
+ "optional": true,
7087
+ "os": [
7088
+ "linux"
7089
+ ],
7090
+ "engines": {
7091
+ "node": ">=18"
7092
+ }
7093
+ },
7094
+ "node_modules/tsx/node_modules/@esbuild/linux-riscv64": {
7095
+ "version": "0.27.3",
7096
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz",
7097
+ "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==",
7098
+ "cpu": [
7099
+ "riscv64"
7100
+ ],
7101
+ "dev": true,
7102
+ "license": "MIT",
7103
+ "optional": true,
7104
+ "os": [
7105
+ "linux"
7106
+ ],
7107
+ "engines": {
7108
+ "node": ">=18"
7109
+ }
7110
+ },
7111
+ "node_modules/tsx/node_modules/@esbuild/linux-s390x": {
7112
+ "version": "0.27.3",
7113
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz",
7114
+ "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==",
7115
+ "cpu": [
7116
+ "s390x"
7117
+ ],
7118
+ "dev": true,
7119
+ "license": "MIT",
7120
+ "optional": true,
7121
+ "os": [
7122
+ "linux"
7123
+ ],
7124
+ "engines": {
7125
+ "node": ">=18"
7126
+ }
7127
+ },
7128
+ "node_modules/tsx/node_modules/@esbuild/linux-x64": {
7129
+ "version": "0.27.3",
7130
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz",
7131
+ "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==",
7132
+ "cpu": [
7133
+ "x64"
7134
+ ],
7135
+ "dev": true,
7136
+ "license": "MIT",
7137
+ "optional": true,
7138
+ "os": [
7139
+ "linux"
7140
+ ],
7141
+ "engines": {
7142
+ "node": ">=18"
7143
+ }
7144
+ },
7145
+ "node_modules/tsx/node_modules/@esbuild/netbsd-arm64": {
7146
+ "version": "0.27.3",
7147
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz",
7148
+ "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==",
7149
+ "cpu": [
7150
+ "arm64"
7151
+ ],
7152
+ "dev": true,
7153
+ "license": "MIT",
7154
+ "optional": true,
7155
+ "os": [
7156
+ "netbsd"
7157
+ ],
7158
+ "engines": {
7159
+ "node": ">=18"
7160
+ }
7161
+ },
7162
+ "node_modules/tsx/node_modules/@esbuild/netbsd-x64": {
7163
+ "version": "0.27.3",
7164
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz",
7165
+ "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==",
7166
+ "cpu": [
7167
+ "x64"
7168
+ ],
7169
+ "dev": true,
7170
+ "license": "MIT",
7171
+ "optional": true,
7172
+ "os": [
7173
+ "netbsd"
7174
+ ],
7175
+ "engines": {
7176
+ "node": ">=18"
7177
+ }
7178
+ },
7179
+ "node_modules/tsx/node_modules/@esbuild/openbsd-arm64": {
7180
+ "version": "0.27.3",
7181
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz",
7182
+ "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==",
7183
+ "cpu": [
7184
+ "arm64"
7185
+ ],
7186
+ "dev": true,
7187
+ "license": "MIT",
7188
+ "optional": true,
7189
+ "os": [
7190
+ "openbsd"
7191
+ ],
7192
+ "engines": {
7193
+ "node": ">=18"
7194
+ }
7195
+ },
7196
+ "node_modules/tsx/node_modules/@esbuild/openbsd-x64": {
7197
+ "version": "0.27.3",
7198
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz",
7199
+ "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==",
7200
+ "cpu": [
7201
+ "x64"
7202
+ ],
7203
+ "dev": true,
7204
+ "license": "MIT",
7205
+ "optional": true,
7206
+ "os": [
7207
+ "openbsd"
7208
+ ],
7209
+ "engines": {
7210
+ "node": ">=18"
7211
+ }
7212
+ },
7213
+ "node_modules/tsx/node_modules/@esbuild/sunos-x64": {
7214
+ "version": "0.27.3",
7215
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz",
7216
+ "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==",
7217
+ "cpu": [
7218
+ "x64"
7219
+ ],
7220
+ "dev": true,
7221
+ "license": "MIT",
7222
+ "optional": true,
7223
+ "os": [
7224
+ "sunos"
7225
+ ],
7226
+ "engines": {
7227
+ "node": ">=18"
7228
+ }
7229
+ },
7230
+ "node_modules/tsx/node_modules/@esbuild/win32-arm64": {
7231
+ "version": "0.27.3",
7232
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz",
7233
+ "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==",
7234
+ "cpu": [
7235
+ "arm64"
7236
+ ],
7237
+ "dev": true,
7238
+ "license": "MIT",
7239
+ "optional": true,
7240
+ "os": [
7241
+ "win32"
7242
+ ],
7243
+ "engines": {
7244
+ "node": ">=18"
7245
+ }
7246
+ },
7247
+ "node_modules/tsx/node_modules/@esbuild/win32-ia32": {
7248
+ "version": "0.27.3",
7249
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz",
7250
+ "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==",
7251
+ "cpu": [
7252
+ "ia32"
7253
+ ],
7254
+ "dev": true,
7255
+ "license": "MIT",
7256
+ "optional": true,
7257
+ "os": [
7258
+ "win32"
7259
+ ],
7260
+ "engines": {
7261
+ "node": ">=18"
7262
+ }
7263
+ },
7264
+ "node_modules/tsx/node_modules/@esbuild/win32-x64": {
7265
+ "version": "0.27.3",
7266
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz",
7267
+ "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==",
7268
+ "cpu": [
7269
+ "x64"
7270
+ ],
7271
+ "dev": true,
7272
+ "license": "MIT",
7273
+ "optional": true,
7274
+ "os": [
7275
+ "win32"
7276
+ ],
7277
+ "engines": {
7278
+ "node": ">=18"
7279
+ }
7280
+ },
7281
+ "node_modules/tsx/node_modules/esbuild": {
7282
+ "version": "0.27.3",
7283
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz",
7284
+ "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==",
7285
+ "dev": true,
7286
+ "hasInstallScript": true,
7287
+ "license": "MIT",
7288
+ "bin": {
7289
+ "esbuild": "bin/esbuild"
7290
+ },
7291
+ "engines": {
7292
+ "node": ">=18"
7293
+ },
7294
+ "optionalDependencies": {
7295
+ "@esbuild/aix-ppc64": "0.27.3",
7296
+ "@esbuild/android-arm": "0.27.3",
7297
+ "@esbuild/android-arm64": "0.27.3",
7298
+ "@esbuild/android-x64": "0.27.3",
7299
+ "@esbuild/darwin-arm64": "0.27.3",
7300
+ "@esbuild/darwin-x64": "0.27.3",
7301
+ "@esbuild/freebsd-arm64": "0.27.3",
7302
+ "@esbuild/freebsd-x64": "0.27.3",
7303
+ "@esbuild/linux-arm": "0.27.3",
7304
+ "@esbuild/linux-arm64": "0.27.3",
7305
+ "@esbuild/linux-ia32": "0.27.3",
7306
+ "@esbuild/linux-loong64": "0.27.3",
7307
+ "@esbuild/linux-mips64el": "0.27.3",
7308
+ "@esbuild/linux-ppc64": "0.27.3",
7309
+ "@esbuild/linux-riscv64": "0.27.3",
7310
+ "@esbuild/linux-s390x": "0.27.3",
7311
+ "@esbuild/linux-x64": "0.27.3",
7312
+ "@esbuild/netbsd-arm64": "0.27.3",
7313
+ "@esbuild/netbsd-x64": "0.27.3",
7314
+ "@esbuild/openbsd-arm64": "0.27.3",
7315
+ "@esbuild/openbsd-x64": "0.27.3",
7316
+ "@esbuild/openharmony-arm64": "0.27.3",
7317
+ "@esbuild/sunos-x64": "0.27.3",
7318
+ "@esbuild/win32-arm64": "0.27.3",
7319
+ "@esbuild/win32-ia32": "0.27.3",
7320
+ "@esbuild/win32-x64": "0.27.3"
7321
+ }
7322
+ },
7323
  "node_modules/type-check": {
7324
  "version": "0.4.0",
7325
  "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
package.json CHANGED
@@ -8,7 +8,8 @@
8
  "build": "vite build",
9
  "build:dev": "vite build --mode development",
10
  "lint": "eslint .",
11
- "preview": "vite preview"
 
12
  },
13
  "dependencies": {
14
  "@hookform/resolvers": "^3.9.0",
@@ -67,6 +68,7 @@
67
  "@eslint/js": "^9.9.0",
68
  "@modyfi/vite-plugin-yaml": "^1.1.1",
69
  "@tailwindcss/typography": "^0.5.15",
 
70
  "@types/node": "^22.5.5",
71
  "@types/react": "^18.3.3",
72
  "@types/react-dom": "^18.3.0",
@@ -79,6 +81,7 @@
79
  "lovable-tagger": "^1.1.3",
80
  "postcss": "^8.4.47",
81
  "tailwindcss": "^3.4.11",
 
82
  "typescript": "^5.5.3",
83
  "typescript-eslint": "^8.0.1",
84
  "vite": "^5.4.1"
 
8
  "build": "vite build",
9
  "build:dev": "vite build --mode development",
10
  "lint": "eslint .",
11
+ "preview": "vite preview",
12
+ "validate": "tsx scripts/validate-conferences.ts"
13
  },
14
  "dependencies": {
15
  "@hookform/resolvers": "^3.9.0",
 
68
  "@eslint/js": "^9.9.0",
69
  "@modyfi/vite-plugin-yaml": "^1.1.1",
70
  "@tailwindcss/typography": "^0.5.15",
71
+ "@types/js-yaml": "^4.0.9",
72
  "@types/node": "^22.5.5",
73
  "@types/react": "^18.3.3",
74
  "@types/react-dom": "^18.3.0",
 
81
  "lovable-tagger": "^1.1.3",
82
  "postcss": "^8.4.47",
83
  "tailwindcss": "^3.4.11",
84
+ "tsx": "^4.21.0",
85
  "typescript": "^5.5.3",
86
  "typescript-eslint": "^8.0.1",
87
  "vite": "^5.4.1"
scripts/validate-conferences.ts ADDED
@@ -0,0 +1,305 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { z } from "zod";
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+ import { fileURLToPath } from "url";
5
+ import * as yaml from "js-yaml";
6
+
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
+ const CONFERENCES_DIR = path.resolve(__dirname, "../src/data/conferences");
9
+
10
+ const DATETIME_RE = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/;
11
+ const UTC_OFFSET_RE = /^(UTC|GMT)([+-]\d{1,2})?$/;
12
+
13
+ const KNOWN_ABBREVIATIONS = new Set([
14
+ "PST",
15
+ "PDT",
16
+ "MST",
17
+ "MDT",
18
+ "CST",
19
+ "CDT",
20
+ "EST",
21
+ "EDT",
22
+ "CET",
23
+ "CEST",
24
+ "BST",
25
+ "IST",
26
+ "JST",
27
+ "KST",
28
+ "AEST",
29
+ "AEDT",
30
+ ]);
31
+
32
+ function isValidTimezone(tz: string): boolean {
33
+ if (tz === "AoE") return true;
34
+ if (UTC_OFFSET_RE.test(tz)) return true;
35
+ if (KNOWN_ABBREVIATIONS.has(tz)) return true;
36
+ try {
37
+ Intl.DateTimeFormat(undefined, { timeZone: tz });
38
+ return true;
39
+ } catch {
40
+ return false;
41
+ }
42
+ }
43
+
44
+ const timezoneSchema = z.string().refine(isValidTimezone, {
45
+ message:
46
+ "Must be 'AoE', a valid IANA timezone, UTC/GMT offset, or known abbreviation (e.g. PST)",
47
+ });
48
+
49
+ const deadlineEntrySchema = z.object({
50
+ type: z.string().min(1, "Deadline type is required"),
51
+ label: z.string().min(1, "Deadline label is required"),
52
+ date: z.string().regex(DATETIME_RE, {
53
+ message: "Deadline date must match 'YYYY-MM-DD HH:mm:ss'",
54
+ }),
55
+ timezone: timezoneSchema.optional(),
56
+ });
57
+
58
+ // Helper: accept both undefined and null for optional fields (YAML parses missing vs explicit null)
59
+ function nullable<T extends z.ZodTypeAny>(schema: T) {
60
+ return schema.nullable().optional();
61
+ }
62
+
63
+ const conferenceSchema = z.object({
64
+ title: z.string().min(1, "Title is required"),
65
+ year: z.number().int().positive("Year must be a positive integer"),
66
+ id: z
67
+ .string()
68
+ .min(1, "ID is required")
69
+ .regex(
70
+ /^[a-z0-9+-]+$/,
71
+ "ID must be lowercase alphanumeric (hyphens and plus signs allowed)"
72
+ ),
73
+ full_name: nullable(z.string()),
74
+ link: nullable(z.string().url("Link must be a valid URL")),
75
+ deadline: nullable(
76
+ z.string().regex(DATETIME_RE, {
77
+ message: "deadline must match 'YYYY-MM-DD HH:mm:ss'",
78
+ })
79
+ ),
80
+ deadlines: nullable(z.array(deadlineEntrySchema)),
81
+ timezone: nullable(timezoneSchema),
82
+ date: z.string().min(1, "Date is required"),
83
+ place: nullable(z.string()),
84
+ city: nullable(z.string()),
85
+ country: nullable(z.string()),
86
+ venue: nullable(z.string()),
87
+ tags: nullable(z.array(z.string())),
88
+ note: nullable(z.string()),
89
+ abstract_deadline: nullable(
90
+ z.string().regex(DATETIME_RE, {
91
+ message: "abstract_deadline must match 'YYYY-MM-DD HH:mm:ss'",
92
+ })
93
+ ),
94
+ start: nullable(z.union([z.string(), z.date()])),
95
+ end: nullable(z.union([z.string(), z.date()])),
96
+ rankings: nullable(z.string()),
97
+ hindex: nullable(z.number()),
98
+ rebuttal_period_start: nullable(z.string()),
99
+ rebuttal_period_end: nullable(z.string()),
100
+ final_decision_date: nullable(z.string()),
101
+ review_release_date: nullable(z.string()),
102
+ submission_deadline: nullable(z.string()),
103
+ timezone_submission: nullable(z.string()),
104
+ commitment_deadline: nullable(z.string()),
105
+ paperslink: nullable(z.string()),
106
+ pwclink: nullable(z.string()),
107
+ era_rating: nullable(z.string()),
108
+ });
109
+
110
+ interface ValidationIssue {
111
+ file: string;
112
+ index: number;
113
+ id?: string;
114
+ severity: "error" | "warning";
115
+ message: string;
116
+ }
117
+
118
+ function extractYearFromId(id: string): number | null {
119
+ const match = id.match(/(\d{2,4})(?:[a-z]*)$/);
120
+ if (!match) return null;
121
+ const digits = match[1];
122
+ if (digits.length === 4) return parseInt(digits, 10);
123
+ if (digits.length === 2) return 2000 + parseInt(digits, 10);
124
+ return null;
125
+ }
126
+
127
+ function validateConferences(): boolean {
128
+ const issues: ValidationIssue[] = [];
129
+ const allIds = new Map<string, string>(); // id -> file path
130
+
131
+ const files = fs
132
+ .readdirSync(CONFERENCES_DIR)
133
+ .filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"))
134
+ .sort();
135
+
136
+ if (files.length === 0) {
137
+ console.error("No conference YAML files found in", CONFERENCES_DIR);
138
+ process.exit(1);
139
+ }
140
+
141
+ for (const file of files) {
142
+ const filePath = path.join(CONFERENCES_DIR, file);
143
+ const relPath = path.relative(process.cwd(), filePath);
144
+
145
+ let content: unknown;
146
+ try {
147
+ const raw = fs.readFileSync(filePath, "utf-8");
148
+ content = yaml.load(raw);
149
+ } catch (e) {
150
+ issues.push({
151
+ file: relPath,
152
+ index: -1,
153
+ severity: "error",
154
+ message: `Failed to parse YAML: ${e instanceof Error ? e.message : String(e)}`,
155
+ });
156
+ continue;
157
+ }
158
+
159
+ if (!Array.isArray(content)) {
160
+ issues.push({
161
+ file: relPath,
162
+ index: -1,
163
+ severity: "error",
164
+ message: "File must contain a YAML array of conference entries",
165
+ });
166
+ continue;
167
+ }
168
+
169
+ for (let i = 0; i < content.length; i++) {
170
+ const entry = content[i];
171
+ const entryId = entry?.id ?? `entry[${i}]`;
172
+ const result = conferenceSchema.safeParse(entry);
173
+
174
+ if (!result.success) {
175
+ for (const issue of result.error.issues) {
176
+ issues.push({
177
+ file: relPath,
178
+ index: i,
179
+ id: entryId,
180
+ severity: "error",
181
+ message: `${issue.path.join(".")}: ${issue.message}`,
182
+ });
183
+ }
184
+ continue;
185
+ }
186
+
187
+ const conf = result.data;
188
+
189
+ // Warn if conference has no deadline information at all
190
+ const hasDeadline = conf.deadline || (conf.deadlines && conf.deadlines.length > 0);
191
+ if (!hasDeadline) {
192
+ issues.push({
193
+ file: relPath,
194
+ index: i,
195
+ id: conf.id,
196
+ severity: "warning",
197
+ message: "No 'deadline' or 'deadlines' - conference won't appear in deadline views",
198
+ });
199
+ }
200
+
201
+ // Check for duplicate IDs across all files
202
+ const existingFile = allIds.get(conf.id);
203
+ if (existingFile) {
204
+ issues.push({
205
+ file: relPath,
206
+ index: i,
207
+ id: conf.id,
208
+ severity: "error",
209
+ message: `Duplicate id '${conf.id}' (also in ${existingFile})`,
210
+ });
211
+ } else {
212
+ allIds.set(conf.id, relPath);
213
+ }
214
+
215
+ // Warn if id year suffix doesn't match year field
216
+ const idYear = extractYearFromId(conf.id);
217
+ if (idYear !== null && idYear !== conf.year) {
218
+ issues.push({
219
+ file: relPath,
220
+ index: i,
221
+ id: conf.id,
222
+ severity: "warning",
223
+ message: `ID year suffix (${idYear}) does not match year field (${conf.year})`,
224
+ });
225
+ }
226
+
227
+ // Warn about missing tags on entries that have future deadlines
228
+ if (!conf.tags || conf.tags.length === 0) {
229
+ issues.push({
230
+ file: relPath,
231
+ index: i,
232
+ id: conf.id,
233
+ severity: "warning",
234
+ message: "Missing 'tags' - conference will not appear in filtered views",
235
+ });
236
+ }
237
+
238
+ // Warn about missing era_rating
239
+ if (!conf.era_rating) {
240
+ issues.push({
241
+ file: relPath,
242
+ index: i,
243
+ id: conf.id,
244
+ severity: "warning",
245
+ message: "Missing 'era_rating' - conference has no ERA rating defined",
246
+ });
247
+ }
248
+
249
+ // Warn about timezone abbreviations (not portable)
250
+ const allTimezones: string[] = [];
251
+ if (conf.timezone) allTimezones.push(conf.timezone);
252
+ if (conf.deadlines) {
253
+ for (const d of conf.deadlines) {
254
+ if (d.timezone) allTimezones.push(d.timezone);
255
+ }
256
+ }
257
+ for (const tz of allTimezones) {
258
+ if (KNOWN_ABBREVIATIONS.has(tz)) {
259
+ issues.push({
260
+ file: relPath,
261
+ index: i,
262
+ id: conf.id,
263
+ severity: "warning",
264
+ message: `Timezone '${tz}' is an abbreviation - prefer 'AoE', IANA name, or UTC/GMT offset`,
265
+ });
266
+ break;
267
+ }
268
+ }
269
+ }
270
+ }
271
+
272
+ // Print results
273
+ const errors = issues.filter((i) => i.severity === "error");
274
+ const warnings = issues.filter((i) => i.severity === "warning");
275
+
276
+ if (warnings.length > 0) {
277
+ console.log(`\n⚠ ${warnings.length} warning(s):\n`);
278
+ for (const w of warnings) {
279
+ const loc = w.index >= 0 ? ` [${w.id ?? `#${w.index}`}]` : "";
280
+ console.log(` ${w.file}${loc}: ${w.message}`);
281
+ }
282
+ }
283
+
284
+ if (errors.length > 0) {
285
+ console.log(`\n✗ ${errors.length} error(s):\n`);
286
+ for (const e of errors) {
287
+ const loc = e.index >= 0 ? ` [${e.id ?? `#${e.index}`}]` : "";
288
+ console.log(` ${e.file}${loc}: ${e.message}`);
289
+ }
290
+ console.log(
291
+ `\nValidation failed: ${errors.length} error(s), ${warnings.length} warning(s) across ${files.length} files.\n`
292
+ );
293
+ return false;
294
+ }
295
+
296
+ console.log(
297
+ `\n✓ All ${allIds.size} conferences in ${files.length} files passed validation.` +
298
+ (warnings.length > 0 ? ` (${warnings.length} warning(s))` : "") +
299
+ "\n"
300
+ );
301
+ return true;
302
+ }
303
+
304
+ const success = validateConferences();
305
+ process.exit(success ? 0 : 1);