fitzroy 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +2283 -505
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -1,83 +1,1851 @@
1
1
  #!/usr/bin/env node
2
- import { createRequire } from "node:module";
3
2
  var __defProp = Object.defineProperty;
4
- var __returnValue = (v) => v;
5
- function __exportSetter(name, newValue) {
6
- this[name] = __returnValue.bind(null, newValue);
7
- }
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
8
7
  var __export = (target, all) => {
9
8
  for (var name in all)
10
- __defProp(target, name, {
11
- get: all[name],
12
- enumerable: true,
13
- configurable: true,
14
- set: __exportSetter.bind(all, name)
15
- });
9
+ __defProp(target, name, { get: all[name], enumerable: true });
16
10
  };
17
- var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
11
+
12
+ // src/lib/errors.ts
13
+ var AflApiError, ScrapeError, UnsupportedSourceError, ValidationError;
14
+ var init_errors = __esm({
15
+ "src/lib/errors.ts"() {
16
+ "use strict";
17
+ AflApiError = class extends Error {
18
+ constructor(message, statusCode) {
19
+ super(message);
20
+ this.statusCode = statusCode;
21
+ }
22
+ name = "AflApiError";
23
+ };
24
+ ScrapeError = class extends Error {
25
+ constructor(message, source) {
26
+ super(message);
27
+ this.source = source;
28
+ }
29
+ name = "ScrapeError";
30
+ };
31
+ UnsupportedSourceError = class extends Error {
32
+ constructor(message, source) {
33
+ super(message);
34
+ this.source = source;
35
+ }
36
+ name = "UnsupportedSourceError";
37
+ };
38
+ ValidationError = class extends Error {
39
+ constructor(message, issues) {
40
+ super(message);
41
+ this.issues = issues;
42
+ }
43
+ name = "ValidationError";
44
+ };
45
+ }
46
+ });
47
+
48
+ // src/lib/result.ts
49
+ function ok(data) {
50
+ return { success: true, data };
51
+ }
52
+ function err(error) {
53
+ return { success: false, error };
54
+ }
55
+ var init_result = __esm({
56
+ "src/lib/result.ts"() {
57
+ "use strict";
58
+ }
59
+ });
60
+
61
+ // src/lib/team-mapping.ts
62
+ function normaliseTeamName(raw) {
63
+ const trimmed = raw.trim();
64
+ return ALIAS_MAP.get(trimmed.toLowerCase()) ?? trimmed;
65
+ }
66
+ var TEAM_ALIASES, ALIAS_MAP;
67
+ var init_team_mapping = __esm({
68
+ "src/lib/team-mapping.ts"() {
69
+ "use strict";
70
+ TEAM_ALIASES = [
71
+ ["Adelaide Crows", "Adelaide", "Crows", "ADEL", "AD"],
72
+ ["Brisbane Lions", "Brisbane", "Brisbane Bears", "Bears", "Fitzroy Lions", "BL", "BRIS"],
73
+ ["Carlton", "Carlton Blues", "Blues", "CARL", "CA"],
74
+ ["Collingwood", "Collingwood Magpies", "Magpies", "COLL", "CW"],
75
+ ["Essendon", "Essendon Bombers", "Bombers", "ESS", "ES"],
76
+ ["Fremantle", "Fremantle Dockers", "Dockers", "FRE", "FR"],
77
+ ["Geelong Cats", "Geelong", "Cats", "GEEL", "GE"],
78
+ [
79
+ "Gold Coast Suns",
80
+ "Gold Coast",
81
+ "Gold Coast SUNS",
82
+ "Gold Coast Football Club",
83
+ "Suns",
84
+ "GCFC",
85
+ "GC"
86
+ ],
87
+ [
88
+ "GWS Giants",
89
+ "GWS",
90
+ "GWS GIANTS",
91
+ "Greater Western Sydney",
92
+ "Giants",
93
+ "Greater Western Sydney Giants",
94
+ "GW"
95
+ ],
96
+ ["Hawthorn", "Hawthorn Hawks", "Hawks", "HAW", "HW"],
97
+ ["Melbourne", "Melbourne Demons", "Demons", "MELB", "ME"],
98
+ ["North Melbourne", "North Melbourne Kangaroos", "Kangaroos", "Kangas", "North", "NMFC", "NM"],
99
+ ["Port Adelaide", "Port Adelaide Power", "Power", "Port", "PA", "PAFC"],
100
+ ["Richmond", "Richmond Tigers", "Tigers", "RICH", "RI"],
101
+ ["St Kilda", "St Kilda Saints", "Saints", "Saint Kilda", "STK", "SK"],
102
+ ["Sydney Swans", "Sydney", "Swans", "South Melbourne", "South Melbourne Swans", "SYD", "SY"],
103
+ ["West Coast Eagles", "West Coast", "Eagles", "WCE", "WC"],
104
+ ["Western Bulldogs", "Bulldogs", "Footscray", "Footscray Bulldogs", "WB", "WBD"],
105
+ // Historical / defunct VFL teams
106
+ ["Fitzroy", "Fitzroy Reds", "Fitzroy Gorillas", "Fitzroy Maroons", "FI"],
107
+ ["University", "University Blacks"]
108
+ ];
109
+ ALIAS_MAP = (() => {
110
+ const map = /* @__PURE__ */ new Map();
111
+ for (const [canonical, ...aliases] of TEAM_ALIASES) {
112
+ map.set(canonical.toLowerCase(), canonical);
113
+ for (const alias of aliases) {
114
+ map.set(alias.toLowerCase(), canonical);
115
+ }
116
+ }
117
+ return map;
118
+ })();
119
+ }
120
+ });
121
+
122
+ // src/lib/validation.ts
123
+ import { z } from "zod/v4";
124
+ var AflApiTokenSchema, CompetitionSchema, CompetitionListSchema, CompseasonSchema, CompseasonListSchema, RoundSchema, RoundListSchema, ScoreSchema, PeriodScoreSchema, TeamScoreSchema, CfsMatchTeamSchema, CfsMatchSchema, CfsScoreSchema, CfsVenueSchema, MatchItemSchema, MatchItemListSchema, CfsPlayerInnerSchema, PlayerGameStatsSchema, PlayerStatsItemSchema, PlayerStatsListSchema, RosterPlayerSchema, TeamPlayersSchema, MatchRosterSchema, TeamItemSchema, TeamListSchema, SquadPlayerInnerSchema, SquadPlayerItemSchema, SquadSchema, SquadListSchema, WinLossRecordSchema, LadderEntryRawSchema, LadderResponseSchema;
125
+ var init_validation = __esm({
126
+ "src/lib/validation.ts"() {
127
+ "use strict";
128
+ AflApiTokenSchema = z.object({
129
+ token: z.string(),
130
+ disclaimer: z.string().optional()
131
+ }).passthrough();
132
+ CompetitionSchema = z.object({
133
+ id: z.number(),
134
+ name: z.string(),
135
+ code: z.string().optional()
136
+ }).passthrough();
137
+ CompetitionListSchema = z.object({
138
+ competitions: z.array(CompetitionSchema)
139
+ }).passthrough();
140
+ CompseasonSchema = z.object({
141
+ id: z.number(),
142
+ name: z.string(),
143
+ shortName: z.string().optional(),
144
+ currentRoundNumber: z.number().optional()
145
+ }).passthrough();
146
+ CompseasonListSchema = z.object({
147
+ compSeasons: z.array(CompseasonSchema)
148
+ }).passthrough();
149
+ RoundSchema = z.object({
150
+ id: z.number(),
151
+ /** Provider ID used by /cfs/ endpoints (e.g. "CD_R202501401"). */
152
+ providerId: z.string().optional(),
153
+ name: z.string(),
154
+ abbreviation: z.string().optional(),
155
+ roundNumber: z.number(),
156
+ utcStartTime: z.string().optional(),
157
+ utcEndTime: z.string().optional()
158
+ }).passthrough();
159
+ RoundListSchema = z.object({
160
+ rounds: z.array(RoundSchema)
161
+ }).passthrough();
162
+ ScoreSchema = z.object({
163
+ totalScore: z.number(),
164
+ goals: z.number(),
165
+ behinds: z.number(),
166
+ superGoals: z.number().nullable().optional()
167
+ }).passthrough();
168
+ PeriodScoreSchema = z.object({
169
+ periodNumber: z.number(),
170
+ score: ScoreSchema
171
+ }).passthrough();
172
+ TeamScoreSchema = z.object({
173
+ matchScore: ScoreSchema,
174
+ periodScore: z.array(PeriodScoreSchema).optional(),
175
+ rushedBehinds: z.number().optional(),
176
+ minutesInFront: z.number().optional()
177
+ }).passthrough();
178
+ CfsMatchTeamSchema = z.object({
179
+ name: z.string(),
180
+ teamId: z.string(),
181
+ abbr: z.string().optional(),
182
+ nickname: z.string().optional()
183
+ }).passthrough();
184
+ CfsMatchSchema = z.object({
185
+ matchId: z.string(),
186
+ name: z.string().optional(),
187
+ status: z.string(),
188
+ utcStartTime: z.string(),
189
+ homeTeamId: z.string(),
190
+ awayTeamId: z.string(),
191
+ homeTeam: CfsMatchTeamSchema,
192
+ awayTeam: CfsMatchTeamSchema,
193
+ round: z.string().optional(),
194
+ abbr: z.string().optional()
195
+ }).passthrough();
196
+ CfsScoreSchema = z.object({
197
+ status: z.string(),
198
+ matchId: z.string(),
199
+ homeTeamScore: TeamScoreSchema,
200
+ awayTeamScore: TeamScoreSchema
201
+ }).passthrough();
202
+ CfsVenueSchema = z.object({
203
+ name: z.string(),
204
+ venueId: z.string().optional(),
205
+ state: z.string().optional(),
206
+ timeZone: z.string().optional()
207
+ }).passthrough();
208
+ MatchItemSchema = z.object({
209
+ match: CfsMatchSchema,
210
+ score: CfsScoreSchema.optional(),
211
+ venue: CfsVenueSchema.optional(),
212
+ round: z.object({
213
+ name: z.string(),
214
+ roundId: z.string(),
215
+ roundNumber: z.number()
216
+ }).passthrough().optional()
217
+ }).passthrough();
218
+ MatchItemListSchema = z.object({
219
+ roundId: z.string().optional(),
220
+ items: z.array(MatchItemSchema)
221
+ }).passthrough();
222
+ CfsPlayerInnerSchema = z.object({
223
+ playerId: z.string(),
224
+ playerName: z.object({
225
+ givenName: z.string(),
226
+ surname: z.string()
227
+ }).passthrough(),
228
+ captain: z.boolean().optional(),
229
+ playerJumperNumber: z.number().optional()
230
+ }).passthrough();
231
+ PlayerGameStatsSchema = z.object({
232
+ goals: z.number().optional(),
233
+ behinds: z.number().optional(),
234
+ kicks: z.number().optional(),
235
+ handballs: z.number().optional(),
236
+ disposals: z.number().optional(),
237
+ marks: z.number().optional(),
238
+ bounces: z.number().optional(),
239
+ tackles: z.number().optional(),
240
+ contestedPossessions: z.number().optional(),
241
+ uncontestedPossessions: z.number().optional(),
242
+ totalPossessions: z.number().optional(),
243
+ inside50s: z.number().optional(),
244
+ marksInside50: z.number().optional(),
245
+ contestedMarks: z.number().optional(),
246
+ hitouts: z.number().optional(),
247
+ onePercenters: z.number().optional(),
248
+ disposalEfficiency: z.number().optional(),
249
+ clangers: z.number().optional(),
250
+ freesFor: z.number().optional(),
251
+ freesAgainst: z.number().optional(),
252
+ dreamTeamPoints: z.number().optional(),
253
+ clearances: z.object({
254
+ centreClearances: z.number().optional(),
255
+ stoppageClearances: z.number().optional(),
256
+ totalClearances: z.number().optional()
257
+ }).passthrough().optional(),
258
+ rebound50s: z.number().optional(),
259
+ goalAssists: z.number().optional(),
260
+ goalAccuracy: z.number().optional(),
261
+ turnovers: z.number().optional(),
262
+ intercepts: z.number().optional(),
263
+ tacklesInside50: z.number().optional(),
264
+ shotsAtGoal: z.number().optional(),
265
+ metresGained: z.number().optional(),
266
+ scoreInvolvements: z.number().optional(),
267
+ ratingPoints: z.number().optional(),
268
+ extendedStats: z.object({
269
+ effectiveDisposals: z.number().optional(),
270
+ effectiveKicks: z.number().optional(),
271
+ kickEfficiency: z.number().optional(),
272
+ kickToHandballRatio: z.number().optional(),
273
+ pressureActs: z.number().optional(),
274
+ defHalfPressureActs: z.number().optional(),
275
+ spoils: z.number().optional(),
276
+ hitoutsToAdvantage: z.number().optional(),
277
+ hitoutWinPercentage: z.number().optional(),
278
+ hitoutToAdvantageRate: z.number().optional(),
279
+ groundBallGets: z.number().optional(),
280
+ f50GroundBallGets: z.number().optional(),
281
+ interceptMarks: z.number().optional(),
282
+ marksOnLead: z.number().optional(),
283
+ contestedPossessionRate: z.number().optional(),
284
+ contestOffOneOnOnes: z.number().optional(),
285
+ contestOffWins: z.number().optional(),
286
+ contestOffWinsPercentage: z.number().optional(),
287
+ contestDefOneOnOnes: z.number().optional(),
288
+ contestDefLosses: z.number().optional(),
289
+ contestDefLossPercentage: z.number().optional(),
290
+ centreBounceAttendances: z.number().optional(),
291
+ kickins: z.number().optional(),
292
+ kickinsPlayon: z.number().optional(),
293
+ ruckContests: z.number().optional(),
294
+ scoreLaunches: z.number().optional()
295
+ }).passthrough().optional()
296
+ }).passthrough();
297
+ PlayerStatsItemSchema = z.object({
298
+ player: z.object({
299
+ player: z.object({
300
+ position: z.string().optional(),
301
+ player: CfsPlayerInnerSchema
302
+ }).passthrough(),
303
+ jumperNumber: z.number().optional()
304
+ }).passthrough(),
305
+ teamId: z.string(),
306
+ playerStats: z.object({
307
+ stats: PlayerGameStatsSchema,
308
+ timeOnGroundPercentage: z.number().optional()
309
+ }).passthrough()
310
+ }).passthrough();
311
+ PlayerStatsListSchema = z.object({
312
+ homeTeamPlayerStats: z.array(PlayerStatsItemSchema),
313
+ awayTeamPlayerStats: z.array(PlayerStatsItemSchema)
314
+ }).passthrough();
315
+ RosterPlayerSchema = z.object({
316
+ player: z.object({
317
+ position: z.string().optional(),
318
+ player: CfsPlayerInnerSchema
319
+ }).passthrough(),
320
+ jumperNumber: z.number().optional()
321
+ }).passthrough();
322
+ TeamPlayersSchema = z.object({
323
+ teamId: z.string(),
324
+ players: z.array(RosterPlayerSchema)
325
+ }).passthrough();
326
+ MatchRosterSchema = z.object({
327
+ match: CfsMatchSchema,
328
+ teamPlayers: z.array(TeamPlayersSchema)
329
+ }).passthrough();
330
+ TeamItemSchema = z.object({
331
+ id: z.number(),
332
+ name: z.string(),
333
+ abbreviation: z.string().optional(),
334
+ teamType: z.string().optional()
335
+ }).passthrough();
336
+ TeamListSchema = z.object({
337
+ teams: z.array(TeamItemSchema)
338
+ }).passthrough();
339
+ SquadPlayerInnerSchema = z.object({
340
+ id: z.number(),
341
+ providerId: z.string().optional(),
342
+ firstName: z.string(),
343
+ surname: z.string(),
344
+ dateOfBirth: z.string().optional(),
345
+ heightInCm: z.number().optional(),
346
+ weightInKg: z.number().optional(),
347
+ draftYear: z.string().optional(),
348
+ draftPosition: z.string().optional(),
349
+ draftType: z.string().optional(),
350
+ debutYear: z.string().optional(),
351
+ recruitedFrom: z.string().optional()
352
+ }).passthrough();
353
+ SquadPlayerItemSchema = z.object({
354
+ player: SquadPlayerInnerSchema,
355
+ jumperNumber: z.number().optional(),
356
+ position: z.string().optional()
357
+ }).passthrough();
358
+ SquadSchema = z.object({
359
+ team: z.object({
360
+ name: z.string()
361
+ }).passthrough().optional(),
362
+ players: z.array(SquadPlayerItemSchema)
363
+ }).passthrough();
364
+ SquadListSchema = z.object({
365
+ squad: SquadSchema
366
+ }).passthrough();
367
+ WinLossRecordSchema = z.object({
368
+ wins: z.number(),
369
+ losses: z.number(),
370
+ draws: z.number(),
371
+ played: z.number().optional()
372
+ }).passthrough();
373
+ LadderEntryRawSchema = z.object({
374
+ position: z.number(),
375
+ team: z.object({
376
+ name: z.string(),
377
+ id: z.number().optional(),
378
+ abbreviation: z.string().optional()
379
+ }).passthrough(),
380
+ played: z.number().optional(),
381
+ pointsFor: z.number().optional(),
382
+ pointsAgainst: z.number().optional(),
383
+ thisSeasonRecord: z.object({
384
+ aggregatePoints: z.number().optional(),
385
+ percentage: z.number().optional(),
386
+ winLossRecord: WinLossRecordSchema.optional()
387
+ }).passthrough().optional(),
388
+ form: z.string().optional()
389
+ }).passthrough();
390
+ LadderResponseSchema = z.object({
391
+ ladders: z.array(
392
+ z.object({
393
+ entries: z.array(LadderEntryRawSchema)
394
+ }).passthrough()
395
+ ),
396
+ round: z.object({
397
+ roundNumber: z.number(),
398
+ name: z.string().optional()
399
+ }).passthrough().optional()
400
+ }).passthrough();
401
+ }
402
+ });
403
+
404
+ // src/sources/afl-api.ts
405
+ var TOKEN_URL, API_BASE, CFS_BASE, AflApiClient;
406
+ var init_afl_api = __esm({
407
+ "src/sources/afl-api.ts"() {
408
+ "use strict";
409
+ init_errors();
410
+ init_result();
411
+ init_validation();
412
+ TOKEN_URL = "https://api.afl.com.au/cfs/afl/WMCTok";
413
+ API_BASE = "https://aflapi.afl.com.au/afl/v2";
414
+ CFS_BASE = "https://api.afl.com.au/cfs/afl";
415
+ AflApiClient = class {
416
+ fetchFn;
417
+ tokenUrl;
418
+ cachedToken = null;
419
+ constructor(options) {
420
+ this.fetchFn = options?.fetchFn ?? globalThis.fetch;
421
+ this.tokenUrl = options?.tokenUrl ?? TOKEN_URL;
422
+ }
423
+ /**
424
+ * Authenticate with the WMCTok token endpoint and cache the token.
425
+ *
426
+ * @returns The access token on success, or an error Result.
427
+ */
428
+ async authenticate() {
429
+ try {
430
+ const response = await this.fetchFn(this.tokenUrl, {
431
+ method: "POST",
432
+ headers: { "Content-Length": "0" }
433
+ });
434
+ if (!response.ok) {
435
+ return err(new AflApiError(`Token request failed: ${response.status}`, response.status));
436
+ }
437
+ const json = await response.json();
438
+ const parsed = AflApiTokenSchema.safeParse(json);
439
+ if (!parsed.success) {
440
+ return err(new AflApiError("Invalid token response format"));
441
+ }
442
+ const ttlMs = 30 * 60 * 1e3;
443
+ this.cachedToken = {
444
+ accessToken: parsed.data.token,
445
+ expiresAt: Date.now() + ttlMs
446
+ };
447
+ return ok(parsed.data.token);
448
+ } catch (cause) {
449
+ return err(
450
+ new AflApiError(
451
+ `Token request failed: ${cause instanceof Error ? cause.message : String(cause)}`
452
+ )
453
+ );
454
+ }
455
+ }
456
+ /**
457
+ * Whether the cached token is still valid (not expired).
458
+ */
459
+ get isAuthenticated() {
460
+ return this.cachedToken !== null && Date.now() < this.cachedToken.expiresAt;
461
+ }
462
+ /**
463
+ * Perform an authenticated fetch, automatically adding the bearer token.
464
+ * Retries once on 401 by re-authenticating.
465
+ *
466
+ * @param url - The URL to fetch.
467
+ * @param init - Additional fetch options.
468
+ * @returns The Response on success, or an error Result.
469
+ */
470
+ async authedFetch(url, init) {
471
+ if (!this.isAuthenticated) {
472
+ const authResult = await this.authenticate();
473
+ if (!authResult.success) {
474
+ return authResult;
475
+ }
476
+ }
477
+ const doFetch = async () => {
478
+ const token = this.cachedToken;
479
+ if (!token) {
480
+ throw new AflApiError("No cached token available");
481
+ }
482
+ const headers = new Headers(init?.headers);
483
+ headers.set("x-media-mis-token", token.accessToken);
484
+ return this.fetchFn(url, { ...init, headers });
485
+ };
486
+ try {
487
+ let response = await doFetch();
488
+ if (response.status === 401) {
489
+ const authResult = await this.authenticate();
490
+ if (!authResult.success) {
491
+ return authResult;
492
+ }
493
+ response = await doFetch();
494
+ }
495
+ if (!response.ok) {
496
+ return err(
497
+ new AflApiError(
498
+ `Request failed: ${response.status} ${response.statusText}`,
499
+ response.status
500
+ )
501
+ );
502
+ }
503
+ return ok(response);
504
+ } catch (cause) {
505
+ return err(
506
+ new AflApiError(
507
+ `Request failed: ${cause instanceof Error ? cause.message : String(cause)}`
508
+ )
509
+ );
510
+ }
511
+ }
512
+ /**
513
+ * Fetch JSON from a URL, validate with a Zod schema, and return a typed Result.
514
+ *
515
+ * @param url - The URL to fetch.
516
+ * @param schema - Zod schema to validate the response against.
517
+ * @returns Validated data on success, or an error Result.
518
+ */
519
+ async fetchJson(url, schema) {
520
+ const isPublic = url.startsWith(API_BASE);
521
+ let response;
522
+ if (isPublic) {
523
+ try {
524
+ response = await this.fetchFn(url);
525
+ if (!response.ok) {
526
+ return err(
527
+ new AflApiError(
528
+ `Request failed: ${response.status} ${response.statusText}`,
529
+ response.status
530
+ )
531
+ );
532
+ }
533
+ } catch (cause) {
534
+ return err(
535
+ new AflApiError(
536
+ `Request failed: ${cause instanceof Error ? cause.message : String(cause)}`
537
+ )
538
+ );
539
+ }
540
+ } else {
541
+ const fetchResult = await this.authedFetch(url);
542
+ if (!fetchResult.success) {
543
+ return fetchResult;
544
+ }
545
+ response = fetchResult.data;
546
+ }
547
+ try {
548
+ const json = await response.json();
549
+ const parsed = schema.safeParse(json);
550
+ if (!parsed.success) {
551
+ return err(
552
+ new ValidationError("Response validation failed", [
553
+ { path: url, message: String(parsed.error) }
554
+ ])
555
+ );
556
+ }
557
+ return ok(parsed.data);
558
+ } catch (cause) {
559
+ return err(
560
+ new AflApiError(
561
+ `JSON parse failed: ${cause instanceof Error ? cause.message : String(cause)}`
562
+ )
563
+ );
564
+ }
565
+ }
566
+ /**
567
+ * Resolve a competition code (e.g. "AFLM") to its API competition ID.
568
+ *
569
+ * @param code - The competition code to resolve.
570
+ * @returns The competition ID string on success.
571
+ */
572
+ async resolveCompetitionId(code) {
573
+ const result = await this.fetchJson(
574
+ `${API_BASE}/competitions?pageSize=50`,
575
+ CompetitionListSchema
576
+ );
577
+ if (!result.success) {
578
+ return result;
579
+ }
580
+ const apiCode = code === "AFLM" ? "AFL" : code;
581
+ const competition = result.data.competitions.find((c) => c.code === apiCode);
582
+ if (!competition) {
583
+ return err(new AflApiError(`Competition not found for code: ${code}`));
584
+ }
585
+ return ok(competition.id);
586
+ }
587
+ /**
588
+ * Resolve a season (compseason) ID from a competition ID and year.
589
+ *
590
+ * @param competitionId - The competition ID (from {@link resolveCompetitionId}).
591
+ * @param year - The season year (e.g. 2024).
592
+ * @returns The compseason ID string on success.
593
+ */
594
+ async resolveSeasonId(competitionId, year) {
595
+ const result = await this.fetchJson(
596
+ `${API_BASE}/competitions/${competitionId}/compseasons?pageSize=100`,
597
+ CompseasonListSchema
598
+ );
599
+ if (!result.success) {
600
+ return result;
601
+ }
602
+ const yearStr = String(year);
603
+ const season = result.data.compSeasons.find((cs) => cs.name.includes(yearStr));
604
+ if (!season) {
605
+ return err(new AflApiError(`Season not found for year: ${year}`));
606
+ }
607
+ return ok(season.id);
608
+ }
609
+ /**
610
+ * Resolve a season ID from a competition code and year in one step.
611
+ *
612
+ * @param code - The competition code (e.g. "AFLM").
613
+ * @param year - The season year (e.g. 2025).
614
+ * @returns The compseason ID on success.
615
+ */
616
+ async resolveCompSeason(code, year) {
617
+ const compResult = await this.resolveCompetitionId(code);
618
+ if (!compResult.success) return compResult;
619
+ return this.resolveSeasonId(compResult.data, year);
620
+ }
621
+ /**
622
+ * Fetch all rounds for a season with their metadata.
623
+ *
624
+ * @param seasonId - The compseason ID (from {@link resolveSeasonId}).
625
+ * @returns Array of round objects on success.
626
+ */
627
+ async resolveRounds(seasonId) {
628
+ const result = await this.fetchJson(
629
+ `${API_BASE}/compseasons/${seasonId}/rounds?pageSize=50`,
630
+ RoundListSchema
631
+ );
632
+ if (!result.success) {
633
+ return result;
634
+ }
635
+ return ok(result.data.rounds);
636
+ }
637
+ /**
638
+ * Fetch match items for a round using the /cfs/ endpoint.
639
+ *
640
+ * @param roundProviderId - The round provider ID (e.g. "CD_R202501401").
641
+ * @returns Array of match items on success.
642
+ */
643
+ async fetchRoundMatchItems(roundProviderId) {
644
+ const result = await this.fetchJson(
645
+ `${CFS_BASE}/matchItems/round/${roundProviderId}`,
646
+ MatchItemListSchema
647
+ );
648
+ if (!result.success) {
649
+ return result;
650
+ }
651
+ return ok(result.data.items);
652
+ }
653
+ /**
654
+ * Fetch match items for a round by resolving the round provider ID from season and round number.
655
+ *
656
+ * @param seasonId - The compseason ID.
657
+ * @param roundNumber - The round number.
658
+ * @returns Array of match items on success.
659
+ */
660
+ async fetchRoundMatchItemsByNumber(seasonId, roundNumber) {
661
+ const roundsResult = await this.resolveRounds(seasonId);
662
+ if (!roundsResult.success) {
663
+ return roundsResult;
664
+ }
665
+ const round = roundsResult.data.find((r) => r.roundNumber === roundNumber);
666
+ if (!round?.providerId) {
667
+ return err(new AflApiError(`Round not found or missing providerId: round ${roundNumber}`));
668
+ }
669
+ return this.fetchRoundMatchItems(round.providerId);
670
+ }
671
+ /**
672
+ * Fetch match items for all completed rounds in a season.
673
+ *
674
+ * @param seasonId - The compseason ID.
675
+ * @returns Aggregated array of match items from all completed rounds.
676
+ */
677
+ async fetchSeasonMatchItems(seasonId) {
678
+ const roundsResult = await this.resolveRounds(seasonId);
679
+ if (!roundsResult.success) {
680
+ return roundsResult;
681
+ }
682
+ const providerIds = roundsResult.data.flatMap((r) => r.providerId ? [r.providerId] : []);
683
+ const results = await Promise.all(providerIds.map((id) => this.fetchRoundMatchItems(id)));
684
+ const allItems = [];
685
+ for (const result of results) {
686
+ if (!result.success) {
687
+ return result;
688
+ }
689
+ const concluded = result.data.filter(
690
+ (item) => item.match.status === "CONCLUDED" || item.match.status === "COMPLETE"
691
+ );
692
+ allItems.push(...concluded);
693
+ }
694
+ return ok(allItems);
695
+ }
696
+ /**
697
+ * Fetch per-player statistics for a match.
698
+ *
699
+ * @param matchProviderId - The match provider ID (e.g. "CD_M20250140101").
700
+ * @returns Player stats list with home and away arrays.
701
+ */
702
+ async fetchPlayerStats(matchProviderId) {
703
+ return this.fetchJson(
704
+ `${CFS_BASE}/playerStats/match/${matchProviderId}`,
705
+ PlayerStatsListSchema
706
+ );
707
+ }
708
+ /**
709
+ * Fetch match roster (lineup) for a match.
710
+ *
711
+ * @param matchProviderId - The match provider ID (e.g. "CD_M20250140101").
712
+ * @returns Match roster with team players.
713
+ */
714
+ async fetchMatchRoster(matchProviderId) {
715
+ return this.fetchJson(`${CFS_BASE}/matchRoster/full/${matchProviderId}`, MatchRosterSchema);
716
+ }
717
+ /**
718
+ * Fetch team list, optionally filtered by team type.
719
+ *
720
+ * @param teamType - Optional filter (e.g. "MEN", "WOMEN").
721
+ * @returns Array of team items.
722
+ */
723
+ async fetchTeams(teamType) {
724
+ const result = await this.fetchJson(`${API_BASE}/teams?pageSize=100`, TeamListSchema);
725
+ if (!result.success) {
726
+ return result;
727
+ }
728
+ if (teamType) {
729
+ return ok(result.data.teams.filter((t) => t.teamType === teamType));
730
+ }
731
+ return ok(result.data.teams);
732
+ }
733
+ /**
734
+ * Fetch squad (roster) for a team in a specific season.
735
+ *
736
+ * @param teamId - The numeric team ID.
737
+ * @param compSeasonId - The compseason ID.
738
+ * @returns Squad list response.
739
+ */
740
+ async fetchSquad(teamId, compSeasonId) {
741
+ return this.fetchJson(
742
+ `${API_BASE}/squads?teamId=${teamId}&compSeasonId=${compSeasonId}`,
743
+ SquadListSchema
744
+ );
745
+ }
746
+ /**
747
+ * Fetch ladder standings for a season (optionally for a specific round).
748
+ *
749
+ * @param seasonId - The compseason ID.
750
+ * @param roundId - Optional round ID (numeric `id`, not `providerId`).
751
+ * @returns Ladder response with entries.
752
+ */
753
+ async fetchLadder(seasonId, roundId) {
754
+ let url = `${API_BASE}/compseasons/${seasonId}/ladders`;
755
+ if (roundId != null) {
756
+ url += `?roundId=${roundId}`;
757
+ }
758
+ return this.fetchJson(url, LadderResponseSchema);
759
+ }
760
+ };
761
+ }
762
+ });
763
+
764
+ // src/transforms/match-results.ts
765
+ function inferRoundType(roundName) {
766
+ return FINALS_PATTERN.test(roundName) ? "Finals" : "HomeAndAway";
767
+ }
768
+ function toMatchStatus(raw) {
769
+ switch (raw) {
770
+ case "CONCLUDED":
771
+ case "COMPLETE":
772
+ return "Complete";
773
+ case "LIVE":
774
+ case "IN_PROGRESS":
775
+ return "Live";
776
+ case "UPCOMING":
777
+ case "SCHEDULED":
778
+ return "Upcoming";
779
+ case "POSTPONED":
780
+ return "Postponed";
781
+ case "CANCELLED":
782
+ return "Cancelled";
783
+ default:
784
+ return "Complete";
785
+ }
786
+ }
787
+ function toQuarterScore(period) {
788
+ return {
789
+ goals: period.score.goals,
790
+ behinds: period.score.behinds,
791
+ points: period.score.totalScore
792
+ };
793
+ }
794
+ function findPeriod(periods, quarter) {
795
+ if (!periods) return null;
796
+ const period = periods.find((p) => p.periodNumber === quarter);
797
+ return period ? toQuarterScore(period) : null;
798
+ }
799
+ function transformMatchItems(items, season, competition, source = "afl-api") {
800
+ return items.map((item) => {
801
+ const homeScore = item.score?.homeTeamScore;
802
+ const awayScore = item.score?.awayTeamScore;
803
+ const homePoints = homeScore?.matchScore.totalScore ?? 0;
804
+ const awayPoints = awayScore?.matchScore.totalScore ?? 0;
805
+ return {
806
+ matchId: item.match.matchId,
807
+ season,
808
+ roundNumber: item.round?.roundNumber ?? 0,
809
+ roundType: inferRoundType(item.round?.name ?? ""),
810
+ date: new Date(item.match.utcStartTime),
811
+ venue: item.venue?.name ?? "",
812
+ homeTeam: normaliseTeamName(item.match.homeTeam.name),
813
+ awayTeam: normaliseTeamName(item.match.awayTeam.name),
814
+ homeGoals: homeScore?.matchScore.goals ?? 0,
815
+ homeBehinds: homeScore?.matchScore.behinds ?? 0,
816
+ homePoints,
817
+ awayGoals: awayScore?.matchScore.goals ?? 0,
818
+ awayBehinds: awayScore?.matchScore.behinds ?? 0,
819
+ awayPoints,
820
+ margin: homePoints - awayPoints,
821
+ q1Home: findPeriod(homeScore?.periodScore, 1),
822
+ q2Home: findPeriod(homeScore?.periodScore, 2),
823
+ q3Home: findPeriod(homeScore?.periodScore, 3),
824
+ q4Home: findPeriod(homeScore?.periodScore, 4),
825
+ q1Away: findPeriod(awayScore?.periodScore, 1),
826
+ q2Away: findPeriod(awayScore?.periodScore, 2),
827
+ q3Away: findPeriod(awayScore?.periodScore, 3),
828
+ q4Away: findPeriod(awayScore?.periodScore, 4),
829
+ status: toMatchStatus(item.match.status),
830
+ attendance: null,
831
+ venueState: item.venue?.state ?? null,
832
+ venueTimezone: item.venue?.timeZone ?? null,
833
+ homeRushedBehinds: homeScore?.rushedBehinds ?? null,
834
+ awayRushedBehinds: awayScore?.rushedBehinds ?? null,
835
+ homeMinutesInFront: homeScore?.minutesInFront ?? null,
836
+ awayMinutesInFront: awayScore?.minutesInFront ?? null,
837
+ source,
838
+ competition
839
+ };
840
+ });
841
+ }
842
+ var FINALS_PATTERN;
843
+ var init_match_results = __esm({
844
+ "src/transforms/match-results.ts"() {
845
+ "use strict";
846
+ init_team_mapping();
847
+ FINALS_PATTERN = /final|elimination|qualifying|preliminary|semi|grand/i;
848
+ }
849
+ });
850
+
851
+ // src/api/fixture.ts
852
+ function toFixture(item, season, fallbackRoundNumber, competition) {
853
+ return {
854
+ matchId: item.match.matchId,
855
+ season,
856
+ roundNumber: item.round?.roundNumber ?? fallbackRoundNumber,
857
+ roundType: inferRoundType(item.round?.name ?? ""),
858
+ date: new Date(item.match.utcStartTime),
859
+ venue: item.venue?.name ?? "",
860
+ homeTeam: normaliseTeamName(item.match.homeTeam.name),
861
+ awayTeam: normaliseTeamName(item.match.awayTeam.name),
862
+ status: toMatchStatus(item.match.status),
863
+ competition
864
+ };
865
+ }
866
+ async function fetchFixture(query) {
867
+ const competition = query.competition ?? "AFLM";
868
+ if (query.source !== "afl-api") {
869
+ return err(
870
+ new UnsupportedSourceError(
871
+ "Fixture data is only available from the AFL API source.",
872
+ query.source
873
+ )
874
+ );
875
+ }
876
+ const client = new AflApiClient();
877
+ const seasonResult = await client.resolveCompSeason(competition, query.season);
878
+ if (!seasonResult.success) return seasonResult;
879
+ if (query.round != null) {
880
+ const itemsResult = await client.fetchRoundMatchItemsByNumber(seasonResult.data, query.round);
881
+ if (!itemsResult.success) return itemsResult;
882
+ return ok(itemsResult.data.map((item) => toFixture(item, query.season, 0, competition)));
883
+ }
884
+ const roundsResult = await client.resolveRounds(seasonResult.data);
885
+ if (!roundsResult.success) return roundsResult;
886
+ const roundProviderIds = roundsResult.data.flatMap(
887
+ (r) => r.providerId ? [{ providerId: r.providerId, roundNumber: r.roundNumber }] : []
888
+ );
889
+ const roundResults = await Promise.all(
890
+ roundProviderIds.map((r) => client.fetchRoundMatchItems(r.providerId))
891
+ );
892
+ const fixtures = [];
893
+ for (let i = 0; i < roundResults.length; i++) {
894
+ const result = roundResults[i];
895
+ if (!result?.success) continue;
896
+ const roundNumber = roundProviderIds[i]?.roundNumber ?? 0;
897
+ for (const item of result.data) {
898
+ fixtures.push(toFixture(item, query.season, roundNumber, competition));
899
+ }
900
+ }
901
+ return ok(fixtures);
902
+ }
903
+ var init_fixture = __esm({
904
+ "src/api/fixture.ts"() {
905
+ "use strict";
906
+ init_errors();
907
+ init_result();
908
+ init_team_mapping();
909
+ init_afl_api();
910
+ init_match_results();
911
+ }
912
+ });
913
+
914
+ // src/transforms/ladder.ts
915
+ function transformLadderEntries(entries) {
916
+ return entries.map((entry) => {
917
+ const record = entry.thisSeasonRecord;
918
+ const wl = record?.winLossRecord;
919
+ return {
920
+ position: entry.position,
921
+ team: normaliseTeamName(entry.team.name),
922
+ played: entry.played ?? wl?.played ?? 0,
923
+ wins: wl?.wins ?? 0,
924
+ losses: wl?.losses ?? 0,
925
+ draws: wl?.draws ?? 0,
926
+ pointsFor: entry.pointsFor ?? 0,
927
+ pointsAgainst: entry.pointsAgainst ?? 0,
928
+ percentage: record?.percentage ?? 0,
929
+ premiershipsPoints: record?.aggregatePoints ?? 0,
930
+ form: entry.form ?? null
931
+ };
932
+ });
933
+ }
934
+ var init_ladder = __esm({
935
+ "src/transforms/ladder.ts"() {
936
+ "use strict";
937
+ init_team_mapping();
938
+ }
939
+ });
940
+
941
+ // src/api/ladder.ts
942
+ async function fetchLadder(query) {
943
+ const competition = query.competition ?? "AFLM";
944
+ if (query.source !== "afl-api") {
945
+ return err(
946
+ new UnsupportedSourceError(
947
+ "Ladder data is only available from the AFL API source.",
948
+ query.source
949
+ )
950
+ );
951
+ }
952
+ const client = new AflApiClient();
953
+ const seasonResult = await client.resolveCompSeason(competition, query.season);
954
+ if (!seasonResult.success) return seasonResult;
955
+ let roundId;
956
+ if (query.round != null) {
957
+ const roundsResult = await client.resolveRounds(seasonResult.data);
958
+ if (!roundsResult.success) return roundsResult;
959
+ const round = roundsResult.data.find((r) => r.roundNumber === query.round);
960
+ if (round) {
961
+ roundId = round.id;
962
+ }
963
+ }
964
+ const ladderResult = await client.fetchLadder(seasonResult.data, roundId);
965
+ if (!ladderResult.success) return ladderResult;
966
+ const firstLadder = ladderResult.data.ladders[0];
967
+ const entries = firstLadder ? transformLadderEntries(firstLadder.entries) : [];
968
+ return ok({
969
+ season: query.season,
970
+ roundNumber: ladderResult.data.round?.roundNumber ?? null,
971
+ entries,
972
+ competition
973
+ });
974
+ }
975
+ var init_ladder2 = __esm({
976
+ "src/api/ladder.ts"() {
977
+ "use strict";
978
+ init_errors();
979
+ init_result();
980
+ init_afl_api();
981
+ init_ladder();
982
+ }
983
+ });
984
+
985
+ // src/transforms/lineup.ts
986
+ function transformMatchRoster(roster, season, roundNumber, competition) {
987
+ const homeTeamId = roster.match.homeTeamId;
988
+ const awayTeamId = roster.match.awayTeamId;
989
+ const homeTeamPlayers = roster.teamPlayers.find((tp) => tp.teamId === homeTeamId);
990
+ const awayTeamPlayers = roster.teamPlayers.find((tp) => tp.teamId === awayTeamId);
991
+ const mapPlayers = (players) => players.map((p) => {
992
+ const inner = p.player.player;
993
+ const position = p.player.position ?? null;
994
+ return {
995
+ playerId: inner.playerId,
996
+ givenName: inner.playerName.givenName,
997
+ surname: inner.playerName.surname,
998
+ displayName: `${inner.playerName.givenName} ${inner.playerName.surname}`,
999
+ jumperNumber: p.jumperNumber ?? null,
1000
+ position,
1001
+ isEmergency: position !== null && EMERGENCY_POSITIONS.has(position),
1002
+ isSubstitute: position !== null && SUBSTITUTE_POSITIONS.has(position)
1003
+ };
1004
+ });
1005
+ return {
1006
+ matchId: roster.match.matchId,
1007
+ season,
1008
+ roundNumber,
1009
+ homeTeam: normaliseTeamName(roster.match.homeTeam.name),
1010
+ awayTeam: normaliseTeamName(roster.match.awayTeam.name),
1011
+ homePlayers: homeTeamPlayers ? mapPlayers(homeTeamPlayers.players) : [],
1012
+ awayPlayers: awayTeamPlayers ? mapPlayers(awayTeamPlayers.players) : [],
1013
+ competition
1014
+ };
1015
+ }
1016
+ var EMERGENCY_POSITIONS, SUBSTITUTE_POSITIONS;
1017
+ var init_lineup = __esm({
1018
+ "src/transforms/lineup.ts"() {
1019
+ "use strict";
1020
+ init_team_mapping();
1021
+ EMERGENCY_POSITIONS = /* @__PURE__ */ new Set(["EMG", "EMERG"]);
1022
+ SUBSTITUTE_POSITIONS = /* @__PURE__ */ new Set(["SUB", "INT"]);
1023
+ }
1024
+ });
1025
+
1026
+ // src/api/lineup.ts
1027
+ async function fetchLineup(query) {
1028
+ const competition = query.competition ?? "AFLM";
1029
+ if (query.source !== "afl-api") {
1030
+ return err(
1031
+ new UnsupportedSourceError(
1032
+ "Lineup data is only available from the AFL API source.",
1033
+ query.source
1034
+ )
1035
+ );
1036
+ }
1037
+ const client = new AflApiClient();
1038
+ if (query.matchId) {
1039
+ const rosterResult = await client.fetchMatchRoster(query.matchId);
1040
+ if (!rosterResult.success) return rosterResult;
1041
+ return ok([transformMatchRoster(rosterResult.data, query.season, query.round, competition)]);
1042
+ }
1043
+ const seasonResult = await client.resolveCompSeason(competition, query.season);
1044
+ if (!seasonResult.success) return seasonResult;
1045
+ const matchItems = await client.fetchRoundMatchItemsByNumber(seasonResult.data, query.round);
1046
+ if (!matchItems.success) return matchItems;
1047
+ if (matchItems.data.length === 0) {
1048
+ return err(new AflApiError(`No matches found for round ${query.round}`));
1049
+ }
1050
+ const rosterResults = await Promise.all(
1051
+ matchItems.data.map((item) => client.fetchMatchRoster(item.match.matchId))
1052
+ );
1053
+ const lineups = [];
1054
+ for (const rosterResult of rosterResults) {
1055
+ if (!rosterResult.success) return rosterResult;
1056
+ lineups.push(transformMatchRoster(rosterResult.data, query.season, query.round, competition));
1057
+ }
1058
+ return ok(lineups);
1059
+ }
1060
+ var init_lineup2 = __esm({
1061
+ "src/api/lineup.ts"() {
1062
+ "use strict";
1063
+ init_errors();
1064
+ init_result();
1065
+ init_afl_api();
1066
+ init_lineup();
1067
+ }
1068
+ });
1069
+
1070
+ // src/lib/date-utils.ts
1071
+ function parseFootyWireDate(dateStr) {
1072
+ const trimmed = dateStr.trim();
1073
+ if (trimmed === "") {
1074
+ return null;
1075
+ }
1076
+ const withoutDow = trimmed.replace(/^(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)\w*\s+/i, "");
1077
+ const normalised = withoutDow.replace(/-/g, " ");
1078
+ const match = /^(\d{1,2})\s+([A-Za-z]+)\s+(\d{4})$/.exec(normalised);
1079
+ if (!match) {
1080
+ return null;
1081
+ }
1082
+ const [, dayStr, monthStr, yearStr] = match;
1083
+ if (!dayStr || !monthStr || !yearStr) {
1084
+ return null;
1085
+ }
1086
+ const monthIndex = MONTH_ABBREV_TO_INDEX.get(monthStr.toLowerCase());
1087
+ if (monthIndex === void 0) {
1088
+ return null;
1089
+ }
1090
+ const year = Number.parseInt(yearStr, 10);
1091
+ const day = Number.parseInt(dayStr, 10);
1092
+ const date = new Date(Date.UTC(year, monthIndex, day));
1093
+ if (Number.isNaN(date.getTime())) {
1094
+ return null;
1095
+ }
1096
+ if (date.getUTCFullYear() !== year || date.getUTCMonth() !== monthIndex || date.getUTCDate() !== day) {
1097
+ return null;
1098
+ }
1099
+ return date;
1100
+ }
1101
+ function parseAflTablesDate(dateStr) {
1102
+ const trimmed = dateStr.trim();
1103
+ if (trimmed === "") {
1104
+ return null;
1105
+ }
1106
+ const withoutDow = trimmed.replace(/^(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)\w*\s+/i, "");
1107
+ const normalised = withoutDow.replace(/[-/]/g, " ");
1108
+ const dmy = /^(\d{1,2})\s+([A-Za-z]+)\s+(\d{4})$/.exec(normalised);
1109
+ if (dmy) {
1110
+ const [, dayStr, monthStr, yearStr] = dmy;
1111
+ if (dayStr && monthStr && yearStr) {
1112
+ return buildUtcDate(Number.parseInt(yearStr, 10), monthStr, Number.parseInt(dayStr, 10));
1113
+ }
1114
+ }
1115
+ const mdy = /^([A-Za-z]+)\s+(\d{1,2})\s+(\d{4})$/.exec(normalised);
1116
+ if (mdy) {
1117
+ const [, monthStr, dayStr, yearStr] = mdy;
1118
+ if (dayStr && monthStr && yearStr) {
1119
+ return buildUtcDate(Number.parseInt(yearStr, 10), monthStr, Number.parseInt(dayStr, 10));
1120
+ }
1121
+ }
1122
+ return null;
1123
+ }
1124
+ function buildUtcDate(year, monthStr, day) {
1125
+ const monthIndex = MONTH_ABBREV_TO_INDEX.get(monthStr.toLowerCase());
1126
+ if (monthIndex === void 0) {
1127
+ return null;
1128
+ }
1129
+ const date = new Date(Date.UTC(year, monthIndex, day));
1130
+ if (Number.isNaN(date.getTime())) {
1131
+ return null;
1132
+ }
1133
+ if (date.getUTCFullYear() !== year || date.getUTCMonth() !== monthIndex || date.getUTCDate() !== day) {
1134
+ return null;
1135
+ }
1136
+ return date;
1137
+ }
1138
+ var MONTH_ABBREV_TO_INDEX;
1139
+ var init_date_utils = __esm({
1140
+ "src/lib/date-utils.ts"() {
1141
+ "use strict";
1142
+ MONTH_ABBREV_TO_INDEX = /* @__PURE__ */ new Map([
1143
+ ["jan", 0],
1144
+ ["feb", 1],
1145
+ ["mar", 2],
1146
+ ["apr", 3],
1147
+ ["may", 4],
1148
+ ["jun", 5],
1149
+ ["jul", 6],
1150
+ ["aug", 7],
1151
+ ["sep", 8],
1152
+ ["oct", 9],
1153
+ ["nov", 10],
1154
+ ["dec", 11],
1155
+ ["january", 0],
1156
+ ["february", 1],
1157
+ ["march", 2],
1158
+ ["april", 3],
1159
+ ["june", 5],
1160
+ ["july", 6],
1161
+ ["august", 7],
1162
+ ["september", 8],
1163
+ ["october", 9],
1164
+ ["november", 10],
1165
+ ["december", 11]
1166
+ ]);
1167
+ }
1168
+ });
1169
+
1170
+ // src/sources/afl-tables.ts
1171
+ import * as cheerio from "cheerio";
1172
+ function parseSeasonPage(html, year) {
1173
+ const $ = cheerio.load(html);
1174
+ const results = [];
1175
+ let currentRound = 0;
1176
+ let currentRoundType = "HomeAndAway";
1177
+ let matchCounter = 0;
1178
+ $("table").each((_i, table) => {
1179
+ const $table = $(table);
1180
+ const text = $table.text().trim();
1181
+ const roundMatch = /^Round\s+(\d+)/i.exec(text);
1182
+ if (roundMatch?.[1] && !$table.attr("border")) {
1183
+ currentRound = Number.parseInt(roundMatch[1], 10);
1184
+ currentRoundType = inferRoundType(text);
1185
+ return;
1186
+ }
1187
+ if (!$table.attr("border") && inferRoundType(text) === "Finals") {
1188
+ currentRoundType = "Finals";
1189
+ return;
1190
+ }
1191
+ if ($table.attr("border") !== "1") return;
1192
+ const rows = $table.find("tr");
1193
+ if (rows.length !== 2) return;
1194
+ const homeRow = $(rows[0]);
1195
+ const awayRow = $(rows[1]);
1196
+ const homeCells = homeRow.find("td");
1197
+ const awayCells = awayRow.find("td");
1198
+ if (homeCells.length < 4 || awayCells.length < 3) return;
1199
+ const homeTeam = normaliseTeamName($(homeCells[0]).find("a").text().trim());
1200
+ const awayTeam = normaliseTeamName($(awayCells[0]).find("a").text().trim());
1201
+ if (!homeTeam || !awayTeam) return;
1202
+ const homeQuarters = parseQuarterScores($(homeCells[1]).text());
1203
+ const awayQuarters = parseQuarterScores($(awayCells[1]).text());
1204
+ const homePoints = Number.parseInt($(homeCells[2]).text().trim(), 10) || 0;
1205
+ const awayPoints = Number.parseInt($(awayCells[2]).text().trim(), 10) || 0;
1206
+ const infoText = $(homeCells[3]).text().trim();
1207
+ const date = parseDateFromInfo(infoText, year);
1208
+ const venue = parseVenueFromInfo($(homeCells[3]).html() ?? "");
1209
+ const attendance = parseAttendanceFromInfo(infoText);
1210
+ const homeFinal = homeQuarters[3];
1211
+ const awayFinal = awayQuarters[3];
1212
+ matchCounter++;
1213
+ results.push({
1214
+ matchId: `AT_${year}_${matchCounter}`,
1215
+ season: year,
1216
+ roundNumber: currentRound,
1217
+ roundType: currentRoundType,
1218
+ date,
1219
+ venue,
1220
+ homeTeam,
1221
+ awayTeam,
1222
+ homeGoals: homeFinal?.goals ?? 0,
1223
+ homeBehinds: homeFinal?.behinds ?? 0,
1224
+ homePoints,
1225
+ awayGoals: awayFinal?.goals ?? 0,
1226
+ awayBehinds: awayFinal?.behinds ?? 0,
1227
+ awayPoints,
1228
+ margin: homePoints - awayPoints,
1229
+ q1Home: homeQuarters[0] ?? null,
1230
+ q2Home: homeQuarters[1] ?? null,
1231
+ q3Home: homeQuarters[2] ?? null,
1232
+ q4Home: homeQuarters[3] ?? null,
1233
+ q1Away: awayQuarters[0] ?? null,
1234
+ q2Away: awayQuarters[1] ?? null,
1235
+ q3Away: awayQuarters[2] ?? null,
1236
+ q4Away: awayQuarters[3] ?? null,
1237
+ status: "Complete",
1238
+ attendance,
1239
+ venueState: null,
1240
+ venueTimezone: null,
1241
+ homeRushedBehinds: null,
1242
+ awayRushedBehinds: null,
1243
+ homeMinutesInFront: null,
1244
+ awayMinutesInFront: null,
1245
+ source: "afl-tables",
1246
+ competition: "AFLM"
1247
+ });
1248
+ });
1249
+ return results;
1250
+ }
1251
+ function parseQuarterScores(text) {
1252
+ const clean = text.replace(/\u00a0/g, " ").trim();
1253
+ const matches = [...clean.matchAll(/(\d+)\.(\d+)/g)];
1254
+ return matches.map((m) => {
1255
+ const goals = Number.parseInt(m[1] ?? "0", 10);
1256
+ const behinds = Number.parseInt(m[2] ?? "0", 10);
1257
+ return { goals, behinds, points: goals * 6 + behinds };
1258
+ });
1259
+ }
1260
+ function parseDateFromInfo(text, year) {
1261
+ const dateMatch = /(\d{1,2}-[A-Z][a-z]{2}-\d{4})/.exec(text);
1262
+ if (dateMatch?.[1]) {
1263
+ return parseAflTablesDate(dateMatch[1]) ?? new Date(year, 0, 1);
1264
+ }
1265
+ return parseAflTablesDate(text) ?? new Date(year, 0, 1);
1266
+ }
1267
+ function parseVenueFromInfo(html) {
1268
+ const $ = cheerio.load(html);
1269
+ const venueLink = $("a[href*='venues']");
1270
+ if (venueLink.length > 0) {
1271
+ return venueLink.text().trim();
1272
+ }
1273
+ const venueMatch = /Venue:\s*(.+?)(?:<|$)/i.exec(html);
1274
+ return venueMatch?.[1]?.trim() ?? "";
1275
+ }
1276
+ function parseAttendanceFromInfo(text) {
1277
+ const match = /Att:\s*([\d,]+)/i.exec(text);
1278
+ if (!match?.[1]) return null;
1279
+ return Number.parseInt(match[1].replace(/,/g, ""), 10) || null;
1280
+ }
1281
+ var AFL_TABLES_BASE, AflTablesClient;
1282
+ var init_afl_tables = __esm({
1283
+ "src/sources/afl-tables.ts"() {
1284
+ "use strict";
1285
+ init_date_utils();
1286
+ init_errors();
1287
+ init_result();
1288
+ init_team_mapping();
1289
+ init_match_results();
1290
+ AFL_TABLES_BASE = "https://afltables.com/afl/seas";
1291
+ AflTablesClient = class {
1292
+ fetchFn;
1293
+ constructor(options) {
1294
+ this.fetchFn = options?.fetchFn ?? globalThis.fetch;
1295
+ }
1296
+ /**
1297
+ * Fetch season match results from AFL Tables.
1298
+ *
1299
+ * @param year - The season year (1897 to present).
1300
+ * @returns Array of match results.
1301
+ */
1302
+ async fetchSeasonResults(year) {
1303
+ const url = `${AFL_TABLES_BASE}/${year}.html`;
1304
+ try {
1305
+ const response = await this.fetchFn(url, {
1306
+ headers: { "User-Agent": "Mozilla/5.0" }
1307
+ });
1308
+ if (!response.ok) {
1309
+ return err(
1310
+ new ScrapeError(`AFL Tables request failed: ${response.status} (${url})`, "afl-tables")
1311
+ );
1312
+ }
1313
+ const html = await response.text();
1314
+ const results = parseSeasonPage(html, year);
1315
+ return ok(results);
1316
+ } catch (cause) {
1317
+ return err(
1318
+ new ScrapeError(
1319
+ `AFL Tables request failed: ${cause instanceof Error ? cause.message : String(cause)}`,
1320
+ "afl-tables"
1321
+ )
1322
+ );
1323
+ }
1324
+ }
1325
+ };
1326
+ }
1327
+ });
1328
+
1329
+ // src/sources/footywire.ts
1330
+ import * as cheerio2 from "cheerio";
1331
+ function parseMatchList(html, year) {
1332
+ const $ = cheerio2.load(html);
1333
+ const results = [];
1334
+ let currentRound = 0;
1335
+ let currentRoundType = "HomeAndAway";
1336
+ $("tr").each((_i, row) => {
1337
+ const roundHeader = $(row).find("td[colspan='7']");
1338
+ if (roundHeader.length > 0) {
1339
+ const text = roundHeader.text().trim();
1340
+ currentRoundType = inferRoundType(text);
1341
+ const roundMatch = /Round\s+(\d+)/i.exec(text);
1342
+ if (roundMatch?.[1]) {
1343
+ currentRound = Number.parseInt(roundMatch[1], 10);
1344
+ }
1345
+ return;
1346
+ }
1347
+ const cells = $(row).find("td.data");
1348
+ if (cells.length < 5) return;
1349
+ const dateText = $(cells[0]).text().trim();
1350
+ const teamsCell = $(cells[1]);
1351
+ const venue = $(cells[2]).text().trim();
1352
+ const attendance = $(cells[3]).text().trim();
1353
+ const scoreCell = $(cells[4]);
1354
+ if (venue === "BYE") return;
1355
+ const teamLinks = teamsCell.find("a");
1356
+ if (teamLinks.length < 2) return;
1357
+ const homeTeam = normaliseTeamName($(teamLinks[0]).text().trim());
1358
+ const awayTeam = normaliseTeamName($(teamLinks[1]).text().trim());
1359
+ const scoreText = scoreCell.text().trim();
1360
+ const scoreMatch = /(\d+)-(\d+)/.exec(scoreText);
1361
+ if (!scoreMatch) return;
1362
+ const homePoints = Number.parseInt(scoreMatch[1] ?? "0", 10);
1363
+ const awayPoints = Number.parseInt(scoreMatch[2] ?? "0", 10);
1364
+ const scoreLink = scoreCell.find("a").attr("href") ?? "";
1365
+ const midMatch = /mid=(\d+)/.exec(scoreLink);
1366
+ const matchId = midMatch?.[1] ? `FW_${midMatch[1]}` : `FW_${year}_R${currentRound}_${homeTeam}`;
1367
+ const date = parseFootyWireDate(dateText) ?? new Date(year, 0, 1);
1368
+ const homeGoals = Math.floor(homePoints / 6);
1369
+ const homeBehinds = homePoints - homeGoals * 6;
1370
+ const awayGoals = Math.floor(awayPoints / 6);
1371
+ const awayBehinds = awayPoints - awayGoals * 6;
1372
+ results.push({
1373
+ matchId,
1374
+ season: year,
1375
+ roundNumber: currentRound,
1376
+ roundType: currentRoundType,
1377
+ date,
1378
+ venue,
1379
+ homeTeam,
1380
+ awayTeam,
1381
+ homeGoals,
1382
+ homeBehinds,
1383
+ homePoints,
1384
+ awayGoals,
1385
+ awayBehinds,
1386
+ awayPoints,
1387
+ margin: homePoints - awayPoints,
1388
+ q1Home: null,
1389
+ q2Home: null,
1390
+ q3Home: null,
1391
+ q4Home: null,
1392
+ q1Away: null,
1393
+ q2Away: null,
1394
+ q3Away: null,
1395
+ q4Away: null,
1396
+ status: "Complete",
1397
+ attendance: attendance ? Number.parseInt(attendance, 10) || null : null,
1398
+ venueState: null,
1399
+ venueTimezone: null,
1400
+ homeRushedBehinds: null,
1401
+ awayRushedBehinds: null,
1402
+ homeMinutesInFront: null,
1403
+ awayMinutesInFront: null,
1404
+ source: "footywire",
1405
+ competition: "AFLM"
1406
+ });
1407
+ });
1408
+ return results;
1409
+ }
1410
+ var FOOTYWIRE_BASE, FootyWireClient;
1411
+ var init_footywire = __esm({
1412
+ "src/sources/footywire.ts"() {
1413
+ "use strict";
1414
+ init_date_utils();
1415
+ init_errors();
1416
+ init_result();
1417
+ init_team_mapping();
1418
+ init_match_results();
1419
+ FOOTYWIRE_BASE = "https://www.footywire.com/afl/footy";
1420
+ FootyWireClient = class {
1421
+ fetchFn;
1422
+ constructor(options) {
1423
+ this.fetchFn = options?.fetchFn ?? globalThis.fetch;
1424
+ }
1425
+ /**
1426
+ * Fetch the HTML content of a FootyWire page.
1427
+ */
1428
+ async fetchHtml(url) {
1429
+ try {
1430
+ const response = await this.fetchFn(url, {
1431
+ headers: {
1432
+ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
1433
+ }
1434
+ });
1435
+ if (!response.ok) {
1436
+ return err(
1437
+ new ScrapeError(`FootyWire request failed: ${response.status} (${url})`, "footywire")
1438
+ );
1439
+ }
1440
+ const html = await response.text();
1441
+ return ok(html);
1442
+ } catch (cause) {
1443
+ return err(
1444
+ new ScrapeError(
1445
+ `FootyWire request failed: ${cause instanceof Error ? cause.message : String(cause)}`,
1446
+ "footywire"
1447
+ )
1448
+ );
1449
+ }
1450
+ }
1451
+ /**
1452
+ * Fetch season match results from FootyWire.
1453
+ *
1454
+ * @param year - The season year.
1455
+ * @returns Array of match results.
1456
+ */
1457
+ async fetchSeasonResults(year) {
1458
+ const url = `${FOOTYWIRE_BASE}/ft_match_list?year=${year}`;
1459
+ const htmlResult = await this.fetchHtml(url);
1460
+ if (!htmlResult.success) {
1461
+ return htmlResult;
1462
+ }
1463
+ try {
1464
+ const results = parseMatchList(htmlResult.data, year);
1465
+ return ok(results);
1466
+ } catch (cause) {
1467
+ return err(
1468
+ new ScrapeError(
1469
+ `Failed to parse match list: ${cause instanceof Error ? cause.message : String(cause)}`,
1470
+ "footywire"
1471
+ )
1472
+ );
1473
+ }
1474
+ }
1475
+ };
1476
+ }
1477
+ });
1478
+
1479
+ // src/api/match-results.ts
1480
+ async function fetchMatchResults(query) {
1481
+ const competition = query.competition ?? "AFLM";
1482
+ switch (query.source) {
1483
+ case "afl-api": {
1484
+ const client = new AflApiClient();
1485
+ const seasonResult = await client.resolveCompSeason(competition, query.season);
1486
+ if (!seasonResult.success) return seasonResult;
1487
+ if (query.round != null) {
1488
+ const itemsResult2 = await client.fetchRoundMatchItemsByNumber(
1489
+ seasonResult.data,
1490
+ query.round
1491
+ );
1492
+ if (!itemsResult2.success) return itemsResult2;
1493
+ return ok(transformMatchItems(itemsResult2.data, query.season, competition));
1494
+ }
1495
+ const itemsResult = await client.fetchSeasonMatchItems(seasonResult.data);
1496
+ if (!itemsResult.success) return itemsResult;
1497
+ return ok(transformMatchItems(itemsResult.data, query.season, competition));
1498
+ }
1499
+ case "footywire": {
1500
+ const client = new FootyWireClient();
1501
+ const result = await client.fetchSeasonResults(query.season);
1502
+ if (!result.success) return result;
1503
+ if (query.round != null) {
1504
+ return ok(result.data.filter((m) => m.roundNumber === query.round));
1505
+ }
1506
+ return result;
1507
+ }
1508
+ case "afl-tables": {
1509
+ const client = new AflTablesClient();
1510
+ const result = await client.fetchSeasonResults(query.season);
1511
+ if (!result.success) return result;
1512
+ if (query.round != null) {
1513
+ return ok(result.data.filter((m) => m.roundNumber === query.round));
1514
+ }
1515
+ return result;
1516
+ }
1517
+ default:
1518
+ return err(new UnsupportedSourceError(`Unsupported source: ${query.source}`, query.source));
1519
+ }
1520
+ }
1521
+ var init_match_results2 = __esm({
1522
+ "src/api/match-results.ts"() {
1523
+ "use strict";
1524
+ init_errors();
1525
+ init_result();
1526
+ init_afl_api();
1527
+ init_afl_tables();
1528
+ init_footywire();
1529
+ init_match_results();
1530
+ }
1531
+ });
1532
+
1533
+ // src/transforms/player-stats.ts
1534
+ function toNullable(value) {
1535
+ return value ?? null;
1536
+ }
1537
+ function transformOne(item, matchId, season, roundNumber, competition, source, teamIdMap) {
1538
+ const inner = item.player.player.player;
1539
+ const stats = item.playerStats.stats;
1540
+ const clearances = stats.clearances;
1541
+ return {
1542
+ matchId,
1543
+ season,
1544
+ roundNumber,
1545
+ team: normaliseTeamName(teamIdMap?.get(item.teamId) ?? item.teamId),
1546
+ competition,
1547
+ playerId: inner.playerId,
1548
+ givenName: inner.playerName.givenName,
1549
+ surname: inner.playerName.surname,
1550
+ displayName: `${inner.playerName.givenName} ${inner.playerName.surname}`,
1551
+ jumperNumber: item.player.jumperNumber ?? null,
1552
+ kicks: toNullable(stats.kicks),
1553
+ handballs: toNullable(stats.handballs),
1554
+ disposals: toNullable(stats.disposals),
1555
+ marks: toNullable(stats.marks),
1556
+ goals: toNullable(stats.goals),
1557
+ behinds: toNullable(stats.behinds),
1558
+ tackles: toNullable(stats.tackles),
1559
+ hitouts: toNullable(stats.hitouts),
1560
+ freesFor: toNullable(stats.freesFor),
1561
+ freesAgainst: toNullable(stats.freesAgainst),
1562
+ contestedPossessions: toNullable(stats.contestedPossessions),
1563
+ uncontestedPossessions: toNullable(stats.uncontestedPossessions),
1564
+ contestedMarks: toNullable(stats.contestedMarks),
1565
+ intercepts: toNullable(stats.intercepts),
1566
+ centreClearances: toNullable(clearances?.centreClearances),
1567
+ stoppageClearances: toNullable(clearances?.stoppageClearances),
1568
+ totalClearances: toNullable(clearances?.totalClearances),
1569
+ inside50s: toNullable(stats.inside50s),
1570
+ rebound50s: toNullable(stats.rebound50s),
1571
+ clangers: toNullable(stats.clangers),
1572
+ turnovers: toNullable(stats.turnovers),
1573
+ onePercenters: toNullable(stats.onePercenters),
1574
+ bounces: toNullable(stats.bounces),
1575
+ goalAssists: toNullable(stats.goalAssists),
1576
+ disposalEfficiency: toNullable(stats.disposalEfficiency),
1577
+ metresGained: toNullable(stats.metresGained),
1578
+ goalAccuracy: toNullable(stats.goalAccuracy),
1579
+ marksInside50: toNullable(stats.marksInside50),
1580
+ tacklesInside50: toNullable(stats.tacklesInside50),
1581
+ shotsAtGoal: toNullable(stats.shotsAtGoal),
1582
+ scoreInvolvements: toNullable(stats.scoreInvolvements),
1583
+ totalPossessions: toNullable(stats.totalPossessions),
1584
+ timeOnGroundPercentage: toNullable(item.playerStats.timeOnGroundPercentage),
1585
+ ratingPoints: toNullable(stats.ratingPoints),
1586
+ dreamTeamPoints: toNullable(stats.dreamTeamPoints),
1587
+ effectiveDisposals: toNullable(stats.extendedStats?.effectiveDisposals),
1588
+ effectiveKicks: toNullable(stats.extendedStats?.effectiveKicks),
1589
+ kickEfficiency: toNullable(stats.extendedStats?.kickEfficiency),
1590
+ kickToHandballRatio: toNullable(stats.extendedStats?.kickToHandballRatio),
1591
+ pressureActs: toNullable(stats.extendedStats?.pressureActs),
1592
+ defHalfPressureActs: toNullable(stats.extendedStats?.defHalfPressureActs),
1593
+ spoils: toNullable(stats.extendedStats?.spoils),
1594
+ hitoutsToAdvantage: toNullable(stats.extendedStats?.hitoutsToAdvantage),
1595
+ hitoutWinPercentage: toNullable(stats.extendedStats?.hitoutWinPercentage),
1596
+ hitoutToAdvantageRate: toNullable(stats.extendedStats?.hitoutToAdvantageRate),
1597
+ groundBallGets: toNullable(stats.extendedStats?.groundBallGets),
1598
+ f50GroundBallGets: toNullable(stats.extendedStats?.f50GroundBallGets),
1599
+ interceptMarks: toNullable(stats.extendedStats?.interceptMarks),
1600
+ marksOnLead: toNullable(stats.extendedStats?.marksOnLead),
1601
+ contestedPossessionRate: toNullable(stats.extendedStats?.contestedPossessionRate),
1602
+ contestOffOneOnOnes: toNullable(stats.extendedStats?.contestOffOneOnOnes),
1603
+ contestOffWins: toNullable(stats.extendedStats?.contestOffWins),
1604
+ contestOffWinsPercentage: toNullable(stats.extendedStats?.contestOffWinsPercentage),
1605
+ contestDefOneOnOnes: toNullable(stats.extendedStats?.contestDefOneOnOnes),
1606
+ contestDefLosses: toNullable(stats.extendedStats?.contestDefLosses),
1607
+ contestDefLossPercentage: toNullable(stats.extendedStats?.contestDefLossPercentage),
1608
+ centreBounceAttendances: toNullable(stats.extendedStats?.centreBounceAttendances),
1609
+ kickins: toNullable(stats.extendedStats?.kickins),
1610
+ kickinsPlayon: toNullable(stats.extendedStats?.kickinsPlayon),
1611
+ ruckContests: toNullable(stats.extendedStats?.ruckContests),
1612
+ scoreLaunches: toNullable(stats.extendedStats?.scoreLaunches),
1613
+ source
1614
+ };
1615
+ }
1616
+ function transformPlayerStats(data, matchId, season, roundNumber, competition, source = "afl-api", teamIdMap) {
1617
+ const home = data.homeTeamPlayerStats.map(
1618
+ (item) => transformOne(item, matchId, season, roundNumber, competition, source, teamIdMap)
1619
+ );
1620
+ const away = data.awayTeamPlayerStats.map(
1621
+ (item) => transformOne(item, matchId, season, roundNumber, competition, source, teamIdMap)
1622
+ );
1623
+ return [...home, ...away];
1624
+ }
1625
+ var init_player_stats = __esm({
1626
+ "src/transforms/player-stats.ts"() {
1627
+ "use strict";
1628
+ init_team_mapping();
1629
+ }
1630
+ });
1631
+
1632
+ // src/api/player-stats.ts
1633
+ async function fetchPlayerStats(query) {
1634
+ const competition = query.competition ?? "AFLM";
1635
+ switch (query.source) {
1636
+ case "afl-api": {
1637
+ const client = new AflApiClient();
1638
+ if (query.matchId) {
1639
+ const result = await client.fetchPlayerStats(query.matchId);
1640
+ if (!result.success) return result;
1641
+ return ok(
1642
+ transformPlayerStats(
1643
+ result.data,
1644
+ query.matchId,
1645
+ query.season,
1646
+ query.round ?? 0,
1647
+ competition
1648
+ )
1649
+ );
1650
+ }
1651
+ const seasonResult = await client.resolveCompSeason(competition, query.season);
1652
+ if (!seasonResult.success) return seasonResult;
1653
+ const roundNumber = query.round ?? 1;
1654
+ const matchItemsResult = await client.fetchRoundMatchItemsByNumber(
1655
+ seasonResult.data,
1656
+ roundNumber
1657
+ );
1658
+ if (!matchItemsResult.success) return matchItemsResult;
1659
+ const teamIdMap = /* @__PURE__ */ new Map();
1660
+ for (const item of matchItemsResult.data) {
1661
+ teamIdMap.set(item.match.homeTeamId, item.match.homeTeam.name);
1662
+ teamIdMap.set(item.match.awayTeamId, item.match.awayTeam.name);
1663
+ }
1664
+ const statsResults = await Promise.all(
1665
+ matchItemsResult.data.map((item) => client.fetchPlayerStats(item.match.matchId))
1666
+ );
1667
+ const allStats = [];
1668
+ for (let i = 0; i < statsResults.length; i++) {
1669
+ const statsResult = statsResults[i];
1670
+ if (!statsResult?.success)
1671
+ return statsResult ?? err(new AflApiError("Missing stats result"));
1672
+ const item = matchItemsResult.data[i];
1673
+ if (!item) continue;
1674
+ allStats.push(
1675
+ ...transformPlayerStats(
1676
+ statsResult.data,
1677
+ item.match.matchId,
1678
+ query.season,
1679
+ roundNumber,
1680
+ competition,
1681
+ "afl-api",
1682
+ teamIdMap
1683
+ )
1684
+ );
1685
+ }
1686
+ return ok(allStats);
1687
+ }
1688
+ case "footywire":
1689
+ return err(
1690
+ new UnsupportedSourceError(
1691
+ "Player stats from FootyWire are not yet supported. Use source: 'afl-api'.",
1692
+ "footywire"
1693
+ )
1694
+ );
1695
+ case "afl-tables":
1696
+ return err(
1697
+ new UnsupportedSourceError(
1698
+ "Player stats from AFL Tables are not yet supported. Use source: 'afl-api'.",
1699
+ "afl-tables"
1700
+ )
1701
+ );
1702
+ default:
1703
+ return err(new UnsupportedSourceError(`Unsupported source: ${query.source}`, query.source));
1704
+ }
1705
+ }
1706
+ var init_player_stats2 = __esm({
1707
+ "src/api/player-stats.ts"() {
1708
+ "use strict";
1709
+ init_errors();
1710
+ init_result();
1711
+ init_afl_api();
1712
+ init_player_stats();
1713
+ }
1714
+ });
1715
+
1716
+ // src/api/teams.ts
1717
+ function teamTypeForComp(comp) {
1718
+ return comp === "AFLW" ? "WOMEN" : "MEN";
1719
+ }
1720
+ async function fetchTeams(query) {
1721
+ const client = new AflApiClient();
1722
+ const teamType = query?.teamType ?? (query?.competition ? teamTypeForComp(query.competition) : void 0);
1723
+ const result = await client.fetchTeams(teamType);
1724
+ if (!result.success) return result;
1725
+ const competition = query?.competition ?? "AFLM";
1726
+ return ok(
1727
+ result.data.map((t) => ({
1728
+ teamId: String(t.id),
1729
+ name: normaliseTeamName(t.name),
1730
+ abbreviation: t.abbreviation ?? "",
1731
+ competition
1732
+ }))
1733
+ );
1734
+ }
1735
+ async function fetchSquad(query) {
1736
+ const client = new AflApiClient();
1737
+ const competition = query.competition ?? "AFLM";
1738
+ const seasonResult = await client.resolveCompSeason(competition, query.season);
1739
+ if (!seasonResult.success) return seasonResult;
1740
+ const teamId = Number.parseInt(query.teamId, 10);
1741
+ if (Number.isNaN(teamId)) {
1742
+ return err(new ValidationError(`Invalid team ID: ${query.teamId}`));
1743
+ }
1744
+ const squadResult = await client.fetchSquad(teamId, seasonResult.data);
1745
+ if (!squadResult.success) return squadResult;
1746
+ const players = squadResult.data.squad.players.map((p) => ({
1747
+ playerId: p.player.providerId ?? String(p.player.id),
1748
+ givenName: p.player.firstName,
1749
+ surname: p.player.surname,
1750
+ displayName: `${p.player.firstName} ${p.player.surname}`,
1751
+ jumperNumber: p.jumperNumber ?? null,
1752
+ position: p.position ?? null,
1753
+ dateOfBirth: p.player.dateOfBirth ? new Date(p.player.dateOfBirth) : null,
1754
+ heightCm: p.player.heightInCm ?? null,
1755
+ weightKg: p.player.weightInKg ?? null,
1756
+ draftYear: p.player.draftYear ? Number.parseInt(p.player.draftYear, 10) || null : null,
1757
+ draftPosition: p.player.draftPosition ? Number.parseInt(p.player.draftPosition, 10) || null : null,
1758
+ draftType: p.player.draftType ?? null,
1759
+ debutYear: p.player.debutYear ? Number.parseInt(p.player.debutYear, 10) || null : null,
1760
+ recruitedFrom: p.player.recruitedFrom ?? null
1761
+ }));
1762
+ return ok({
1763
+ teamId: query.teamId,
1764
+ teamName: normaliseTeamName(squadResult.data.squad.team?.name ?? query.teamId),
1765
+ season: query.season,
1766
+ players,
1767
+ competition
1768
+ });
1769
+ }
1770
+ var init_teams = __esm({
1771
+ "src/api/teams.ts"() {
1772
+ "use strict";
1773
+ init_errors();
1774
+ init_result();
1775
+ init_team_mapping();
1776
+ init_afl_api();
1777
+ }
1778
+ });
18
1779
 
19
1780
  // src/index.ts
20
- var init_src = () => {};
1781
+ var init_index = __esm({
1782
+ "src/index.ts"() {
1783
+ "use strict";
1784
+ init_fixture();
1785
+ init_ladder2();
1786
+ init_lineup2();
1787
+ init_match_results2();
1788
+ init_player_stats2();
1789
+ init_teams();
1790
+ }
1791
+ });
21
1792
 
22
1793
  // src/cli/formatters/csv.ts
23
1794
  function escapeField(value) {
24
- if (value.includes(",") || value.includes('"') || value.includes(`
25
- `) || value.includes("\r")) {
1795
+ if (value.includes(",") || value.includes('"') || value.includes("\n") || value.includes("\r")) {
26
1796
  return `"${value.replace(/"/g, '""')}"`;
27
1797
  }
28
1798
  return value;
29
1799
  }
30
1800
  function toStringValue(value) {
31
- if (value === null || value === undefined)
32
- return "";
33
- if (value instanceof Date)
34
- return value.toISOString();
35
- if (typeof value === "object")
36
- return JSON.stringify(value);
1801
+ if (value === null || value === void 0) return "";
1802
+ if (value instanceof Date) return value.toISOString();
1803
+ if (typeof value === "object") return JSON.stringify(value);
37
1804
  return String(value);
38
1805
  }
39
1806
  function formatCsv(data) {
40
- if (data.length === 0)
41
- return "";
1807
+ if (data.length === 0) return "";
42
1808
  const firstRow = data[0];
43
- if (!firstRow)
44
- return "";
1809
+ if (!firstRow) return "";
45
1810
  const headers = Object.keys(firstRow);
46
1811
  const lines = [headers.map(escapeField).join(",")];
47
1812
  for (const row of data) {
48
1813
  const values = headers.map((h) => escapeField(toStringValue(row[h])));
49
1814
  lines.push(values.join(","));
50
1815
  }
51
- return lines.join(`
52
- `);
1816
+ return lines.join("\n");
53
1817
  }
1818
+ var init_csv = __esm({
1819
+ "src/cli/formatters/csv.ts"() {
1820
+ "use strict";
1821
+ }
1822
+ });
54
1823
 
55
1824
  // src/cli/formatters/json.ts
56
1825
  function formatJson(data) {
57
1826
  return JSON.stringify(data, null, 2);
58
1827
  }
1828
+ var init_json = __esm({
1829
+ "src/cli/formatters/json.ts"() {
1830
+ "use strict";
1831
+ }
1832
+ });
59
1833
 
60
1834
  // src/cli/formatters/table.ts
61
1835
  function toDisplayValue(value) {
62
- if (value === null || value === undefined)
63
- return "-";
64
- if (value instanceof Date)
65
- return value.toISOString().slice(0, 16).replace("T", " ");
66
- if (typeof value === "object")
67
- return JSON.stringify(value);
1836
+ if (value === null || value === void 0) return "-";
1837
+ if (value instanceof Date) return value.toISOString().slice(0, 16).replace("T", " ");
1838
+ if (typeof value === "object") return JSON.stringify(value);
68
1839
  return String(value);
69
1840
  }
70
1841
  function truncate(str, maxLen) {
71
- if (str.length <= maxLen)
72
- return str;
73
- return `${str.slice(0, maxLen - 1)}…`;
1842
+ if (str.length <= maxLen) return str;
1843
+ return `${str.slice(0, maxLen - 1)}\u2026`;
74
1844
  }
75
1845
  function formatTable(data, options = {}) {
76
- if (data.length === 0)
77
- return "No data.";
1846
+ if (data.length === 0) return "No data.";
78
1847
  const firstRow = data[0];
79
- if (!firstRow)
80
- return "No data.";
1848
+ if (!firstRow) return "No data.";
81
1849
  const termWidth = options.terminalWidth ?? process.stdout.columns ?? 120;
82
1850
  const allKeys = Object.keys(firstRow);
83
1851
  let columns;
@@ -88,78 +1856,72 @@ function formatTable(data, options = {}) {
88
1856
  }
89
1857
  const colWidths = columns.map((col) => (col.label ?? col.key).length);
90
1858
  for (const row of data) {
91
- for (let i = 0;i < columns.length; i++) {
1859
+ for (let i = 0; i < columns.length; i++) {
92
1860
  const col = columns[i];
93
- if (!col)
94
- continue;
1861
+ if (!col) continue;
95
1862
  const len = toDisplayValue(row[col.key]).length;
96
1863
  const current = colWidths[i];
97
- if (current !== undefined && len > current) {
1864
+ if (current !== void 0 && len > current) {
98
1865
  colWidths[i] = len;
99
1866
  }
100
1867
  }
101
1868
  }
102
- for (let i = 0;i < columns.length; i++) {
1869
+ for (let i = 0; i < columns.length; i++) {
103
1870
  const col = columns[i];
104
1871
  const width = colWidths[i];
105
- if (!col || width === undefined)
106
- continue;
1872
+ if (!col || width === void 0) continue;
107
1873
  colWidths[i] = Math.min(col.maxWidth ?? 30, width);
108
1874
  }
109
1875
  const gap = 2;
110
1876
  const visibleCols = [];
111
1877
  let usedWidth = 0;
112
- for (let i = 0;i < columns.length; i++) {
1878
+ for (let i = 0; i < columns.length; i++) {
113
1879
  const colWidth = colWidths[i];
114
- if (colWidth === undefined)
115
- continue;
1880
+ if (colWidth === void 0) continue;
116
1881
  const needed = usedWidth > 0 ? colWidth + gap : colWidth;
117
- if (usedWidth + needed > termWidth && visibleCols.length > 0)
118
- break;
1882
+ if (usedWidth + needed > termWidth && visibleCols.length > 0) break;
119
1883
  visibleCols.push(i);
120
1884
  usedWidth += needed;
121
1885
  }
122
1886
  const headerParts = visibleCols.map((i) => {
123
1887
  const col = columns[i];
124
1888
  const width = colWidths[i];
125
- if (!col || width === undefined)
126
- return "";
1889
+ if (!col || width === void 0) return "";
127
1890
  const label = (col.label ?? col.key).toUpperCase();
128
1891
  return truncate(label, width).padEnd(width);
129
1892
  });
130
1893
  const header = headerParts.join(" ");
131
1894
  const separator = visibleCols.map((i) => {
132
1895
  const width = colWidths[i];
133
- if (width === undefined)
134
- return "";
135
- return "─".repeat(width);
1896
+ if (width === void 0) return "";
1897
+ return "\u2500".repeat(width);
136
1898
  }).join(" ");
137
1899
  const rows = data.map((row) => {
138
1900
  const parts = visibleCols.map((i) => {
139
1901
  const col = columns[i];
140
1902
  const width = colWidths[i];
141
- if (!col || width === undefined)
142
- return "";
1903
+ if (!col || width === void 0) return "";
143
1904
  const val = toDisplayValue(row[col.key]);
144
1905
  return truncate(val, width).padEnd(width);
145
1906
  });
146
1907
  return parts.join(" ");
147
1908
  });
148
- return [header, separator, ...rows].join(`
149
- `);
1909
+ return [header, separator, ...rows].join("\n");
150
1910
  }
1911
+ var init_table = __esm({
1912
+ "src/cli/formatters/table.ts"() {
1913
+ "use strict";
1914
+ }
1915
+ });
151
1916
 
152
1917
  // src/cli/formatters/index.ts
153
1918
  function resolveFormat(options) {
154
- if (options.json)
155
- return "json";
156
- if (options.csv)
157
- return "csv";
1919
+ if (options.json) return "json";
1920
+ if (options.csv) return "csv";
158
1921
  if (options.format === "json" || options.format === "csv" || options.format === "table") {
159
1922
  return options.format;
160
1923
  }
161
- if (!process.stdout.isTTY)
162
- return "json";
1924
+ if (!process.stdout.isTTY) return "json";
163
1925
  return "table";
164
1926
  }
165
1927
  function formatOutput(data, options) {
@@ -177,7 +1939,14 @@ function formatOutput(data, options) {
177
1939
  });
178
1940
  }
179
1941
  }
180
- var init_formatters = () => {};
1942
+ var init_formatters = __esm({
1943
+ "src/cli/formatters/index.ts"() {
1944
+ "use strict";
1945
+ init_csv();
1946
+ init_json();
1947
+ init_table();
1948
+ }
1949
+ });
181
1950
 
182
1951
  // src/cli/ui.ts
183
1952
  import { spinner } from "@clack/prompts";
@@ -198,517 +1967,526 @@ async function withSpinner(message, fn) {
198
1967
  }
199
1968
  }
200
1969
  function showSummary(message) {
201
- if (!isTTY)
202
- return;
1970
+ if (!isTTY) return;
203
1971
  console.error(pc.dim(message));
204
1972
  }
205
1973
  var isTTY;
206
- var init_ui = __esm(() => {
207
- isTTY = process.stdout.isTTY === true;
1974
+ var init_ui = __esm({
1975
+ "src/cli/ui.ts"() {
1976
+ "use strict";
1977
+ isTTY = process.stdout.isTTY === true;
1978
+ }
208
1979
  });
209
1980
 
210
1981
  // src/cli/commands/matches.ts
211
- var exports_matches = {};
212
- __export(exports_matches, {
1982
+ var matches_exports = {};
1983
+ __export(matches_exports, {
213
1984
  matchesCommand: () => matchesCommand
214
1985
  });
215
1986
  import { defineCommand } from "citty";
216
1987
  var DEFAULT_COLUMNS, matchesCommand;
217
- var init_matches = __esm(() => {
218
- init_src();
219
- init_formatters();
220
- init_ui();
221
- DEFAULT_COLUMNS = [
222
- { key: "date", label: "Date", maxWidth: 16 },
223
- { key: "roundNumber", label: "Round", maxWidth: 6 },
224
- { key: "homeTeam", label: "Home", maxWidth: 20 },
225
- { key: "awayTeam", label: "Away", maxWidth: 20 },
226
- { key: "homePoints", label: "H.Pts", maxWidth: 6 },
227
- { key: "awayPoints", label: "A.Pts", maxWidth: 6 },
228
- { key: "venue", label: "Venue", maxWidth: 24 }
229
- ];
230
- matchesCommand = defineCommand({
231
- meta: {
232
- name: "matches",
233
- description: "Fetch match results for a season"
234
- },
235
- args: {
236
- season: { type: "string", description: "Season year (e.g. 2025)", required: true },
237
- round: { type: "string", description: "Round number" },
238
- source: { type: "string", description: "Data source", default: "afl-api" },
239
- competition: {
240
- type: "string",
241
- description: "Competition code (AFLM or AFLW)",
242
- default: "AFLM"
1988
+ var init_matches = __esm({
1989
+ "src/cli/commands/matches.ts"() {
1990
+ "use strict";
1991
+ init_index();
1992
+ init_formatters();
1993
+ init_ui();
1994
+ DEFAULT_COLUMNS = [
1995
+ { key: "date", label: "Date", maxWidth: 16 },
1996
+ { key: "roundNumber", label: "Round", maxWidth: 6 },
1997
+ { key: "homeTeam", label: "Home", maxWidth: 20 },
1998
+ { key: "awayTeam", label: "Away", maxWidth: 20 },
1999
+ { key: "homePoints", label: "H.Pts", maxWidth: 6 },
2000
+ { key: "awayPoints", label: "A.Pts", maxWidth: 6 },
2001
+ { key: "venue", label: "Venue", maxWidth: 24 }
2002
+ ];
2003
+ matchesCommand = defineCommand({
2004
+ meta: {
2005
+ name: "matches",
2006
+ description: "Fetch match results for a season"
243
2007
  },
244
- json: { type: "boolean", description: "Output as JSON" },
245
- csv: { type: "boolean", description: "Output as CSV" },
246
- format: { type: "string", description: "Output format: table, json, csv" },
247
- full: { type: "boolean", description: "Show all columns in table output" }
248
- },
249
- async run({ args }) {
250
- const season = Number(args.season);
251
- const round = args.round ? Number(args.round) : undefined;
252
- const result = await withSpinner("Fetching match results…", () => fetchMatchResults({
253
- source: args.source,
254
- season,
255
- round,
256
- competition: args.competition
257
- }));
258
- if (!result.success) {
259
- throw result.error;
260
- }
261
- const data = result.data;
262
- showSummary(`Loaded ${data.length} matches for ${season}${round ? ` round ${round}` : ""}`);
263
- const formatOptions = {
264
- json: args.json,
265
- csv: args.csv,
266
- format: args.format,
267
- full: args.full,
268
- columns: DEFAULT_COLUMNS
269
- };
270
- console.log(formatOutput(data, formatOptions));
271
- }
272
- });
2008
+ args: {
2009
+ season: { type: "string", description: "Season year (e.g. 2025)", required: true },
2010
+ round: { type: "string", description: "Round number" },
2011
+ source: { type: "string", description: "Data source", default: "afl-api" },
2012
+ competition: {
2013
+ type: "string",
2014
+ description: "Competition code (AFLM or AFLW)",
2015
+ default: "AFLM"
2016
+ },
2017
+ json: { type: "boolean", description: "Output as JSON" },
2018
+ csv: { type: "boolean", description: "Output as CSV" },
2019
+ format: { type: "string", description: "Output format: table, json, csv" },
2020
+ full: { type: "boolean", description: "Show all columns in table output" }
2021
+ },
2022
+ async run({ args }) {
2023
+ const season = Number(args.season);
2024
+ const round = args.round ? Number(args.round) : void 0;
2025
+ const result = await withSpinner(
2026
+ "Fetching match results\u2026",
2027
+ () => fetchMatchResults({
2028
+ source: args.source,
2029
+ season,
2030
+ round,
2031
+ competition: args.competition
2032
+ })
2033
+ );
2034
+ if (!result.success) {
2035
+ throw result.error;
2036
+ }
2037
+ const data = result.data;
2038
+ showSummary(`Loaded ${data.length} matches for ${season}${round ? ` round ${round}` : ""}`);
2039
+ const formatOptions = {
2040
+ json: args.json,
2041
+ csv: args.csv,
2042
+ format: args.format,
2043
+ full: args.full,
2044
+ columns: DEFAULT_COLUMNS
2045
+ };
2046
+ console.log(formatOutput(data, formatOptions));
2047
+ }
2048
+ });
2049
+ }
273
2050
  });
274
2051
 
275
2052
  // src/cli/commands/stats.ts
276
- var exports_stats = {};
277
- __export(exports_stats, {
2053
+ var stats_exports = {};
2054
+ __export(stats_exports, {
278
2055
  statsCommand: () => statsCommand
279
2056
  });
280
2057
  import { defineCommand as defineCommand2 } from "citty";
281
2058
  var DEFAULT_COLUMNS2, statsCommand;
282
- var init_stats = __esm(() => {
283
- init_src();
284
- init_formatters();
285
- init_ui();
286
- DEFAULT_COLUMNS2 = [
287
- { key: "displayName", label: "Player", maxWidth: 22 },
288
- { key: "team", label: "Team", maxWidth: 18 },
289
- { key: "disposals", label: "Disp", maxWidth: 6 },
290
- { key: "kicks", label: "Kicks", maxWidth: 6 },
291
- { key: "handballs", label: "HB", maxWidth: 6 },
292
- { key: "marks", label: "Marks", maxWidth: 6 },
293
- { key: "goals", label: "Goals", maxWidth: 6 }
294
- ];
295
- statsCommand = defineCommand2({
296
- meta: {
297
- name: "stats",
298
- description: "Fetch player statistics for a season"
299
- },
300
- args: {
301
- season: { type: "string", description: "Season year (e.g. 2025)", required: true },
302
- round: { type: "string", description: "Round number" },
303
- "match-id": { type: "string", description: "Specific match ID" },
304
- source: { type: "string", description: "Data source", default: "afl-api" },
305
- competition: {
306
- type: "string",
307
- description: "Competition code (AFLM or AFLW)",
308
- default: "AFLM"
2059
+ var init_stats = __esm({
2060
+ "src/cli/commands/stats.ts"() {
2061
+ "use strict";
2062
+ init_index();
2063
+ init_formatters();
2064
+ init_ui();
2065
+ DEFAULT_COLUMNS2 = [
2066
+ { key: "displayName", label: "Player", maxWidth: 22 },
2067
+ { key: "team", label: "Team", maxWidth: 18 },
2068
+ { key: "disposals", label: "Disp", maxWidth: 6 },
2069
+ { key: "kicks", label: "Kicks", maxWidth: 6 },
2070
+ { key: "handballs", label: "HB", maxWidth: 6 },
2071
+ { key: "marks", label: "Marks", maxWidth: 6 },
2072
+ { key: "goals", label: "Goals", maxWidth: 6 }
2073
+ ];
2074
+ statsCommand = defineCommand2({
2075
+ meta: {
2076
+ name: "stats",
2077
+ description: "Fetch player statistics for a season"
309
2078
  },
310
- json: { type: "boolean", description: "Output as JSON" },
311
- csv: { type: "boolean", description: "Output as CSV" },
312
- format: { type: "string", description: "Output format: table, json, csv" },
313
- full: { type: "boolean", description: "Show all columns in table output" }
314
- },
315
- async run({ args }) {
316
- const season = Number(args.season);
317
- const round = args.round ? Number(args.round) : undefined;
318
- const matchId = args["match-id"];
319
- const result = await withSpinner("Fetching player stats…", () => fetchPlayerStats({
320
- source: args.source,
321
- season,
322
- round,
323
- matchId,
324
- competition: args.competition
325
- }));
326
- if (!result.success) {
327
- throw result.error;
328
- }
329
- const data = result.data;
330
- showSummary(`Loaded ${data.length} player stat lines for ${season}${round ? ` round ${round}` : ""}`);
331
- const formatOptions = {
332
- json: args.json,
333
- csv: args.csv,
334
- format: args.format,
335
- full: args.full,
336
- columns: DEFAULT_COLUMNS2
337
- };
338
- console.log(formatOutput(data, formatOptions));
339
- }
340
- });
2079
+ args: {
2080
+ season: { type: "string", description: "Season year (e.g. 2025)", required: true },
2081
+ round: { type: "string", description: "Round number" },
2082
+ "match-id": { type: "string", description: "Specific match ID" },
2083
+ source: { type: "string", description: "Data source", default: "afl-api" },
2084
+ competition: {
2085
+ type: "string",
2086
+ description: "Competition code (AFLM or AFLW)",
2087
+ default: "AFLM"
2088
+ },
2089
+ json: { type: "boolean", description: "Output as JSON" },
2090
+ csv: { type: "boolean", description: "Output as CSV" },
2091
+ format: { type: "string", description: "Output format: table, json, csv" },
2092
+ full: { type: "boolean", description: "Show all columns in table output" }
2093
+ },
2094
+ async run({ args }) {
2095
+ const season = Number(args.season);
2096
+ const round = args.round ? Number(args.round) : void 0;
2097
+ const matchId = args["match-id"];
2098
+ const result = await withSpinner(
2099
+ "Fetching player stats\u2026",
2100
+ () => fetchPlayerStats({
2101
+ source: args.source,
2102
+ season,
2103
+ round,
2104
+ matchId,
2105
+ competition: args.competition
2106
+ })
2107
+ );
2108
+ if (!result.success) {
2109
+ throw result.error;
2110
+ }
2111
+ const data = result.data;
2112
+ showSummary(
2113
+ `Loaded ${data.length} player stat lines for ${season}${round ? ` round ${round}` : ""}`
2114
+ );
2115
+ const formatOptions = {
2116
+ json: args.json,
2117
+ csv: args.csv,
2118
+ format: args.format,
2119
+ full: args.full,
2120
+ columns: DEFAULT_COLUMNS2
2121
+ };
2122
+ console.log(formatOutput(data, formatOptions));
2123
+ }
2124
+ });
2125
+ }
341
2126
  });
342
2127
 
343
2128
  // src/cli/commands/fixture.ts
344
- var exports_fixture = {};
345
- __export(exports_fixture, {
2129
+ var fixture_exports = {};
2130
+ __export(fixture_exports, {
346
2131
  fixtureCommand: () => fixtureCommand
347
2132
  });
348
2133
  import { defineCommand as defineCommand3 } from "citty";
349
2134
  var DEFAULT_COLUMNS3, fixtureCommand;
350
- var init_fixture = __esm(() => {
351
- init_src();
352
- init_formatters();
353
- init_ui();
354
- DEFAULT_COLUMNS3 = [
355
- { key: "roundNumber", label: "Round", maxWidth: 6 },
356
- { key: "date", label: "Date", maxWidth: 16 },
357
- { key: "homeTeam", label: "Home", maxWidth: 20 },
358
- { key: "awayTeam", label: "Away", maxWidth: 20 },
359
- { key: "venue", label: "Venue", maxWidth: 24 }
360
- ];
361
- fixtureCommand = defineCommand3({
362
- meta: {
363
- name: "fixture",
364
- description: "Fetch fixture/schedule for a season"
365
- },
366
- args: {
367
- season: { type: "string", description: "Season year (e.g. 2025)", required: true },
368
- round: { type: "string", description: "Round number" },
369
- source: { type: "string", description: "Data source", default: "afl-api" },
370
- competition: {
371
- type: "string",
372
- description: "Competition code (AFLM or AFLW)",
373
- default: "AFLM"
2135
+ var init_fixture2 = __esm({
2136
+ "src/cli/commands/fixture.ts"() {
2137
+ "use strict";
2138
+ init_index();
2139
+ init_formatters();
2140
+ init_ui();
2141
+ DEFAULT_COLUMNS3 = [
2142
+ { key: "roundNumber", label: "Round", maxWidth: 6 },
2143
+ { key: "date", label: "Date", maxWidth: 16 },
2144
+ { key: "homeTeam", label: "Home", maxWidth: 20 },
2145
+ { key: "awayTeam", label: "Away", maxWidth: 20 },
2146
+ { key: "venue", label: "Venue", maxWidth: 24 }
2147
+ ];
2148
+ fixtureCommand = defineCommand3({
2149
+ meta: {
2150
+ name: "fixture",
2151
+ description: "Fetch fixture/schedule for a season"
374
2152
  },
375
- json: { type: "boolean", description: "Output as JSON" },
376
- csv: { type: "boolean", description: "Output as CSV" },
377
- format: { type: "string", description: "Output format: table, json, csv" },
378
- full: { type: "boolean", description: "Show all columns in table output" }
379
- },
380
- async run({ args }) {
381
- const season = Number(args.season);
382
- const round = args.round ? Number(args.round) : undefined;
383
- const result = await withSpinner("Fetching fixture…", () => fetchFixture({
384
- source: args.source,
385
- season,
386
- round,
387
- competition: args.competition
388
- }));
389
- if (!result.success) {
390
- throw result.error;
391
- }
392
- const data = result.data;
393
- showSummary(`Loaded ${data.length} fixtures for ${season}${round ? ` round ${round}` : ""}`);
394
- const formatOptions = {
395
- json: args.json,
396
- csv: args.csv,
397
- format: args.format,
398
- full: args.full,
399
- columns: DEFAULT_COLUMNS3
400
- };
401
- console.log(formatOutput(data, formatOptions));
402
- }
403
- });
2153
+ args: {
2154
+ season: { type: "string", description: "Season year (e.g. 2025)", required: true },
2155
+ round: { type: "string", description: "Round number" },
2156
+ source: { type: "string", description: "Data source", default: "afl-api" },
2157
+ competition: {
2158
+ type: "string",
2159
+ description: "Competition code (AFLM or AFLW)",
2160
+ default: "AFLM"
2161
+ },
2162
+ json: { type: "boolean", description: "Output as JSON" },
2163
+ csv: { type: "boolean", description: "Output as CSV" },
2164
+ format: { type: "string", description: "Output format: table, json, csv" },
2165
+ full: { type: "boolean", description: "Show all columns in table output" }
2166
+ },
2167
+ async run({ args }) {
2168
+ const season = Number(args.season);
2169
+ const round = args.round ? Number(args.round) : void 0;
2170
+ const result = await withSpinner(
2171
+ "Fetching fixture\u2026",
2172
+ () => fetchFixture({
2173
+ source: args.source,
2174
+ season,
2175
+ round,
2176
+ competition: args.competition
2177
+ })
2178
+ );
2179
+ if (!result.success) {
2180
+ throw result.error;
2181
+ }
2182
+ const data = result.data;
2183
+ showSummary(`Loaded ${data.length} fixtures for ${season}${round ? ` round ${round}` : ""}`);
2184
+ const formatOptions = {
2185
+ json: args.json,
2186
+ csv: args.csv,
2187
+ format: args.format,
2188
+ full: args.full,
2189
+ columns: DEFAULT_COLUMNS3
2190
+ };
2191
+ console.log(formatOutput(data, formatOptions));
2192
+ }
2193
+ });
2194
+ }
404
2195
  });
405
2196
 
406
2197
  // src/cli/commands/ladder.ts
407
- var exports_ladder = {};
408
- __export(exports_ladder, {
2198
+ var ladder_exports = {};
2199
+ __export(ladder_exports, {
409
2200
  ladderCommand: () => ladderCommand
410
2201
  });
411
2202
  import { defineCommand as defineCommand4 } from "citty";
412
2203
  var DEFAULT_COLUMNS4, ladderCommand;
413
- var init_ladder = __esm(() => {
414
- init_src();
415
- init_formatters();
416
- init_ui();
417
- DEFAULT_COLUMNS4 = [
418
- { key: "position", label: "Pos", maxWidth: 4 },
419
- { key: "team", label: "Team", maxWidth: 24 },
420
- { key: "wins", label: "W", maxWidth: 4 },
421
- { key: "losses", label: "L", maxWidth: 4 },
422
- { key: "draws", label: "D", maxWidth: 4 },
423
- { key: "percentage", label: "Pct", maxWidth: 8 },
424
- { key: "premiershipsPoints", label: "Pts", maxWidth: 5 }
425
- ];
426
- ladderCommand = defineCommand4({
427
- meta: {
428
- name: "ladder",
429
- description: "Fetch ladder standings for a season"
430
- },
431
- args: {
432
- season: { type: "string", description: "Season year (e.g. 2025)", required: true },
433
- round: { type: "string", description: "Round number" },
434
- source: { type: "string", description: "Data source", default: "afl-api" },
435
- competition: {
436
- type: "string",
437
- description: "Competition code (AFLM or AFLW)",
438
- default: "AFLM"
2204
+ var init_ladder3 = __esm({
2205
+ "src/cli/commands/ladder.ts"() {
2206
+ "use strict";
2207
+ init_index();
2208
+ init_formatters();
2209
+ init_ui();
2210
+ DEFAULT_COLUMNS4 = [
2211
+ { key: "position", label: "Pos", maxWidth: 4 },
2212
+ { key: "team", label: "Team", maxWidth: 24 },
2213
+ { key: "wins", label: "W", maxWidth: 4 },
2214
+ { key: "losses", label: "L", maxWidth: 4 },
2215
+ { key: "draws", label: "D", maxWidth: 4 },
2216
+ { key: "percentage", label: "Pct", maxWidth: 8 },
2217
+ { key: "premiershipsPoints", label: "Pts", maxWidth: 5 }
2218
+ ];
2219
+ ladderCommand = defineCommand4({
2220
+ meta: {
2221
+ name: "ladder",
2222
+ description: "Fetch ladder standings for a season"
439
2223
  },
440
- json: { type: "boolean", description: "Output as JSON" },
441
- csv: { type: "boolean", description: "Output as CSV" },
442
- format: { type: "string", description: "Output format: table, json, csv" },
443
- full: { type: "boolean", description: "Show all columns in table output" }
444
- },
445
- async run({ args }) {
446
- const season = Number(args.season);
447
- const round = args.round ? Number(args.round) : undefined;
448
- const result = await withSpinner("Fetching ladder…", () => fetchLadder({
449
- source: args.source,
450
- season,
451
- round,
452
- competition: args.competition
453
- }));
454
- if (!result.success) {
455
- throw result.error;
456
- }
457
- const data = result.data;
458
- showSummary(`Loaded ladder for ${season}${round ? ` round ${round}` : ""} (${data.entries.length} teams)`);
459
- const formatOptions = {
460
- json: args.json,
461
- csv: args.csv,
462
- format: args.format,
463
- full: args.full,
464
- columns: DEFAULT_COLUMNS4
465
- };
466
- console.log(formatOutput(data.entries, formatOptions));
467
- }
468
- });
2224
+ args: {
2225
+ season: { type: "string", description: "Season year (e.g. 2025)", required: true },
2226
+ round: { type: "string", description: "Round number" },
2227
+ source: { type: "string", description: "Data source", default: "afl-api" },
2228
+ competition: {
2229
+ type: "string",
2230
+ description: "Competition code (AFLM or AFLW)",
2231
+ default: "AFLM"
2232
+ },
2233
+ json: { type: "boolean", description: "Output as JSON" },
2234
+ csv: { type: "boolean", description: "Output as CSV" },
2235
+ format: { type: "string", description: "Output format: table, json, csv" },
2236
+ full: { type: "boolean", description: "Show all columns in table output" }
2237
+ },
2238
+ async run({ args }) {
2239
+ const season = Number(args.season);
2240
+ const round = args.round ? Number(args.round) : void 0;
2241
+ const result = await withSpinner(
2242
+ "Fetching ladder\u2026",
2243
+ () => fetchLadder({
2244
+ source: args.source,
2245
+ season,
2246
+ round,
2247
+ competition: args.competition
2248
+ })
2249
+ );
2250
+ if (!result.success) {
2251
+ throw result.error;
2252
+ }
2253
+ const data = result.data;
2254
+ showSummary(
2255
+ `Loaded ladder for ${season}${round ? ` round ${round}` : ""} (${data.entries.length} teams)`
2256
+ );
2257
+ const formatOptions = {
2258
+ json: args.json,
2259
+ csv: args.csv,
2260
+ format: args.format,
2261
+ full: args.full,
2262
+ columns: DEFAULT_COLUMNS4
2263
+ };
2264
+ console.log(formatOutput(data.entries, formatOptions));
2265
+ }
2266
+ });
2267
+ }
469
2268
  });
470
2269
 
471
2270
  // src/cli/commands/lineup.ts
472
- var exports_lineup = {};
473
- __export(exports_lineup, {
2271
+ var lineup_exports = {};
2272
+ __export(lineup_exports, {
474
2273
  lineupCommand: () => lineupCommand
475
2274
  });
476
2275
  import { defineCommand as defineCommand5 } from "citty";
477
2276
  var DEFAULT_COLUMNS5, lineupCommand;
478
- var init_lineup = __esm(() => {
479
- init_src();
480
- init_formatters();
481
- init_ui();
482
- DEFAULT_COLUMNS5 = [
483
- { key: "matchId", label: "Match", maxWidth: 12 },
484
- { key: "homeTeam", label: "Home", maxWidth: 20 },
485
- { key: "awayTeam", label: "Away", maxWidth: 20 }
486
- ];
487
- lineupCommand = defineCommand5({
488
- meta: {
489
- name: "lineup",
490
- description: "Fetch match lineups for a round"
491
- },
492
- args: {
493
- season: { type: "string", description: "Season year (e.g. 2025)", required: true },
494
- round: { type: "string", description: "Round number", required: true },
495
- "match-id": { type: "string", description: "Specific match ID" },
496
- source: { type: "string", description: "Data source", default: "afl-api" },
497
- competition: {
498
- type: "string",
499
- description: "Competition code (AFLM or AFLW)",
500
- default: "AFLM"
2277
+ var init_lineup3 = __esm({
2278
+ "src/cli/commands/lineup.ts"() {
2279
+ "use strict";
2280
+ init_index();
2281
+ init_formatters();
2282
+ init_ui();
2283
+ DEFAULT_COLUMNS5 = [
2284
+ { key: "matchId", label: "Match", maxWidth: 12 },
2285
+ { key: "homeTeam", label: "Home", maxWidth: 20 },
2286
+ { key: "awayTeam", label: "Away", maxWidth: 20 }
2287
+ ];
2288
+ lineupCommand = defineCommand5({
2289
+ meta: {
2290
+ name: "lineup",
2291
+ description: "Fetch match lineups for a round"
501
2292
  },
502
- json: { type: "boolean", description: "Output as JSON" },
503
- csv: { type: "boolean", description: "Output as CSV" },
504
- format: { type: "string", description: "Output format: table, json, csv" },
505
- full: { type: "boolean", description: "Show all columns in table output" }
506
- },
507
- async run({ args }) {
508
- const season = Number(args.season);
509
- const round = Number(args.round);
510
- const matchId = args["match-id"];
511
- const result = await withSpinner("Fetching lineups…", () => fetchLineup({
512
- source: args.source,
513
- season,
514
- round,
515
- matchId,
516
- competition: args.competition
517
- }));
518
- if (!result.success) {
519
- throw result.error;
520
- }
521
- const data = result.data;
522
- showSummary(`Loaded ${data.length} lineups for ${season} round ${round}`);
523
- const formatOptions = {
524
- json: args.json,
525
- csv: args.csv,
526
- format: args.format,
527
- full: args.full,
528
- columns: DEFAULT_COLUMNS5
529
- };
530
- console.log(formatOutput(data, formatOptions));
531
- }
532
- });
2293
+ args: {
2294
+ season: { type: "string", description: "Season year (e.g. 2025)", required: true },
2295
+ round: { type: "string", description: "Round number", required: true },
2296
+ "match-id": { type: "string", description: "Specific match ID" },
2297
+ source: { type: "string", description: "Data source", default: "afl-api" },
2298
+ competition: {
2299
+ type: "string",
2300
+ description: "Competition code (AFLM or AFLW)",
2301
+ default: "AFLM"
2302
+ },
2303
+ json: { type: "boolean", description: "Output as JSON" },
2304
+ csv: { type: "boolean", description: "Output as CSV" },
2305
+ format: { type: "string", description: "Output format: table, json, csv" },
2306
+ full: { type: "boolean", description: "Show all columns in table output" }
2307
+ },
2308
+ async run({ args }) {
2309
+ const season = Number(args.season);
2310
+ const round = Number(args.round);
2311
+ const matchId = args["match-id"];
2312
+ const result = await withSpinner(
2313
+ "Fetching lineups\u2026",
2314
+ () => fetchLineup({
2315
+ source: args.source,
2316
+ season,
2317
+ round,
2318
+ matchId,
2319
+ competition: args.competition
2320
+ })
2321
+ );
2322
+ if (!result.success) {
2323
+ throw result.error;
2324
+ }
2325
+ const data = result.data;
2326
+ showSummary(`Loaded ${data.length} lineups for ${season} round ${round}`);
2327
+ const formatOptions = {
2328
+ json: args.json,
2329
+ csv: args.csv,
2330
+ format: args.format,
2331
+ full: args.full,
2332
+ columns: DEFAULT_COLUMNS5
2333
+ };
2334
+ console.log(formatOutput(data, formatOptions));
2335
+ }
2336
+ });
2337
+ }
533
2338
  });
534
2339
 
535
2340
  // src/cli/commands/squad.ts
536
- var exports_squad = {};
537
- __export(exports_squad, {
2341
+ var squad_exports = {};
2342
+ __export(squad_exports, {
538
2343
  squadCommand: () => squadCommand
539
2344
  });
540
2345
  import { defineCommand as defineCommand6 } from "citty";
541
2346
  var DEFAULT_COLUMNS6, squadCommand;
542
- var init_squad = __esm(() => {
543
- init_src();
544
- init_formatters();
545
- init_ui();
546
- DEFAULT_COLUMNS6 = [
547
- { key: "displayName", label: "Player", maxWidth: 24 },
548
- { key: "jumperNumber", label: "#", maxWidth: 4 },
549
- { key: "position", label: "Pos", maxWidth: 12 },
550
- { key: "heightCm", label: "Ht", maxWidth: 5 },
551
- { key: "weightKg", label: "Wt", maxWidth: 5 }
552
- ];
553
- squadCommand = defineCommand6({
554
- meta: {
555
- name: "squad",
556
- description: "Fetch team squad for a season"
557
- },
558
- args: {
559
- "team-id": { type: "string", description: "Team ID", required: true },
560
- season: { type: "string", description: "Season year (e.g. 2025)", required: true },
561
- competition: {
562
- type: "string",
563
- description: "Competition code (AFLM or AFLW)",
564
- default: "AFLM"
2347
+ var init_squad = __esm({
2348
+ "src/cli/commands/squad.ts"() {
2349
+ "use strict";
2350
+ init_index();
2351
+ init_formatters();
2352
+ init_ui();
2353
+ DEFAULT_COLUMNS6 = [
2354
+ { key: "displayName", label: "Player", maxWidth: 24 },
2355
+ { key: "jumperNumber", label: "#", maxWidth: 4 },
2356
+ { key: "position", label: "Pos", maxWidth: 12 },
2357
+ { key: "heightCm", label: "Ht", maxWidth: 5 },
2358
+ { key: "weightKg", label: "Wt", maxWidth: 5 }
2359
+ ];
2360
+ squadCommand = defineCommand6({
2361
+ meta: {
2362
+ name: "squad",
2363
+ description: "Fetch team squad for a season"
565
2364
  },
566
- json: { type: "boolean", description: "Output as JSON" },
567
- csv: { type: "boolean", description: "Output as CSV" },
568
- format: { type: "string", description: "Output format: table, json, csv" },
569
- full: { type: "boolean", description: "Show all columns in table output" }
570
- },
571
- async run({ args }) {
572
- const teamId = args["team-id"];
573
- const season = Number(args.season);
574
- const result = await withSpinner("Fetching squad…", () => fetchSquad({
575
- teamId,
576
- season,
577
- competition: args.competition
578
- }));
579
- if (!result.success) {
580
- throw result.error;
581
- }
582
- const data = result.data;
583
- showSummary(`Loaded ${data.players.length} players for ${data.teamName} ${season}`);
584
- const formatOptions = {
585
- json: args.json,
586
- csv: args.csv,
587
- format: args.format,
588
- full: args.full,
589
- columns: DEFAULT_COLUMNS6
590
- };
591
- console.log(formatOutput(data.players, formatOptions));
592
- }
593
- });
2365
+ args: {
2366
+ "team-id": { type: "string", description: "Team ID", required: true },
2367
+ season: { type: "string", description: "Season year (e.g. 2025)", required: true },
2368
+ competition: {
2369
+ type: "string",
2370
+ description: "Competition code (AFLM or AFLW)",
2371
+ default: "AFLM"
2372
+ },
2373
+ json: { type: "boolean", description: "Output as JSON" },
2374
+ csv: { type: "boolean", description: "Output as CSV" },
2375
+ format: { type: "string", description: "Output format: table, json, csv" },
2376
+ full: { type: "boolean", description: "Show all columns in table output" }
2377
+ },
2378
+ async run({ args }) {
2379
+ const teamId = args["team-id"];
2380
+ const season = Number(args.season);
2381
+ const result = await withSpinner(
2382
+ "Fetching squad\u2026",
2383
+ () => fetchSquad({
2384
+ teamId,
2385
+ season,
2386
+ competition: args.competition
2387
+ })
2388
+ );
2389
+ if (!result.success) {
2390
+ throw result.error;
2391
+ }
2392
+ const data = result.data;
2393
+ showSummary(`Loaded ${data.players.length} players for ${data.teamName} ${season}`);
2394
+ const formatOptions = {
2395
+ json: args.json,
2396
+ csv: args.csv,
2397
+ format: args.format,
2398
+ full: args.full,
2399
+ columns: DEFAULT_COLUMNS6
2400
+ };
2401
+ console.log(formatOutput(data.players, formatOptions));
2402
+ }
2403
+ });
2404
+ }
594
2405
  });
595
2406
 
596
2407
  // src/cli/commands/teams.ts
597
- var exports_teams = {};
598
- __export(exports_teams, {
2408
+ var teams_exports = {};
2409
+ __export(teams_exports, {
599
2410
  teamsCommand: () => teamsCommand
600
2411
  });
601
2412
  import { defineCommand as defineCommand7 } from "citty";
602
2413
  var DEFAULT_COLUMNS7, teamsCommand;
603
- var init_teams = __esm(() => {
604
- init_src();
605
- init_formatters();
606
- init_ui();
607
- DEFAULT_COLUMNS7 = [
608
- { key: "teamId", label: "ID", maxWidth: 8 },
609
- { key: "name", label: "Team", maxWidth: 24 },
610
- { key: "abbreviation", label: "Abbr", maxWidth: 6 },
611
- { key: "competition", label: "Comp", maxWidth: 6 }
612
- ];
613
- teamsCommand = defineCommand7({
614
- meta: {
615
- name: "teams",
616
- description: "Fetch team list"
617
- },
618
- args: {
619
- competition: { type: "string", description: "Competition code (AFLM or AFLW)" },
620
- "team-type": { type: "string", description: "Team type filter" },
621
- json: { type: "boolean", description: "Output as JSON" },
622
- csv: { type: "boolean", description: "Output as CSV" },
623
- format: { type: "string", description: "Output format: table, json, csv" },
624
- full: { type: "boolean", description: "Show all columns in table output" }
625
- },
626
- async run({ args }) {
627
- const result = await withSpinner("Fetching teams…", () => fetchTeams({
628
- competition: args.competition,
629
- teamType: args["team-type"]
630
- }));
631
- if (!result.success) {
632
- throw result.error;
633
- }
634
- const data = result.data;
635
- showSummary(`Loaded ${data.length} teams`);
636
- const formatOptions = {
637
- json: args.json,
638
- csv: args.csv,
639
- format: args.format,
640
- full: args.full,
641
- columns: DEFAULT_COLUMNS7
642
- };
643
- console.log(formatOutput(data, formatOptions));
644
- }
645
- });
2414
+ var init_teams2 = __esm({
2415
+ "src/cli/commands/teams.ts"() {
2416
+ "use strict";
2417
+ init_index();
2418
+ init_formatters();
2419
+ init_ui();
2420
+ DEFAULT_COLUMNS7 = [
2421
+ { key: "teamId", label: "ID", maxWidth: 8 },
2422
+ { key: "name", label: "Team", maxWidth: 24 },
2423
+ { key: "abbreviation", label: "Abbr", maxWidth: 6 },
2424
+ { key: "competition", label: "Comp", maxWidth: 6 }
2425
+ ];
2426
+ teamsCommand = defineCommand7({
2427
+ meta: {
2428
+ name: "teams",
2429
+ description: "Fetch team list"
2430
+ },
2431
+ args: {
2432
+ competition: { type: "string", description: "Competition code (AFLM or AFLW)" },
2433
+ "team-type": { type: "string", description: "Team type filter" },
2434
+ json: { type: "boolean", description: "Output as JSON" },
2435
+ csv: { type: "boolean", description: "Output as CSV" },
2436
+ format: { type: "string", description: "Output format: table, json, csv" },
2437
+ full: { type: "boolean", description: "Show all columns in table output" }
2438
+ },
2439
+ async run({ args }) {
2440
+ const result = await withSpinner(
2441
+ "Fetching teams\u2026",
2442
+ () => fetchTeams({
2443
+ competition: args.competition,
2444
+ teamType: args["team-type"]
2445
+ })
2446
+ );
2447
+ if (!result.success) {
2448
+ throw result.error;
2449
+ }
2450
+ const data = result.data;
2451
+ showSummary(`Loaded ${data.length} teams`);
2452
+ const formatOptions = {
2453
+ json: args.json,
2454
+ csv: args.csv,
2455
+ format: args.format,
2456
+ full: args.full,
2457
+ columns: DEFAULT_COLUMNS7
2458
+ };
2459
+ console.log(formatOutput(data, formatOptions));
2460
+ }
2461
+ });
2462
+ }
646
2463
  });
647
2464
 
648
2465
  // src/cli.ts
2466
+ init_errors();
649
2467
  import { defineCommand as defineCommand8, runMain } from "citty";
650
2468
  import pc2 from "picocolors";
651
-
652
- // src/lib/errors.ts
653
- class AflApiError extends Error {
654
- statusCode;
655
- name = "AflApiError";
656
- constructor(message, statusCode) {
657
- super(message);
658
- this.statusCode = statusCode;
659
- }
660
- }
661
-
662
- class ScrapeError extends Error {
663
- source;
664
- name = "ScrapeError";
665
- constructor(message, source) {
666
- super(message);
667
- this.source = source;
668
- }
669
- }
670
-
671
- class UnsupportedSourceError extends Error {
672
- source;
673
- name = "UnsupportedSourceError";
674
- constructor(message, source) {
675
- super(message);
676
- this.source = source;
677
- }
678
- }
679
-
680
- class ValidationError extends Error {
681
- issues;
682
- name = "ValidationError";
683
- constructor(message, issues) {
684
- super(message);
685
- this.issues = issues;
686
- }
687
- }
688
-
689
- // src/cli.ts
690
2469
  var main = defineCommand8({
691
2470
  meta: {
692
2471
  name: "fitzroy",
693
- version: "1.0.1",
694
- description: "CLI for fetching AFL data match results, player stats, fixtures, ladders, and more"
2472
+ version: "1.0.2",
2473
+ description: "CLI for fetching AFL data \u2014 match results, player stats, fixtures, ladders, and more"
695
2474
  },
696
2475
  subCommands: {
697
- matches: () => Promise.resolve().then(() => (init_matches(), exports_matches)).then((m) => m.matchesCommand),
698
- stats: () => Promise.resolve().then(() => (init_stats(), exports_stats)).then((m) => m.statsCommand),
699
- fixture: () => Promise.resolve().then(() => (init_fixture(), exports_fixture)).then((m) => m.fixtureCommand),
700
- ladder: () => Promise.resolve().then(() => (init_ladder(), exports_ladder)).then((m) => m.ladderCommand),
701
- lineup: () => Promise.resolve().then(() => (init_lineup(), exports_lineup)).then((m) => m.lineupCommand),
702
- squad: () => Promise.resolve().then(() => (init_squad(), exports_squad)).then((m) => m.squadCommand),
703
- teams: () => Promise.resolve().then(() => (init_teams(), exports_teams)).then((m) => m.teamsCommand)
2476
+ matches: () => Promise.resolve().then(() => (init_matches(), matches_exports)).then((m) => m.matchesCommand),
2477
+ stats: () => Promise.resolve().then(() => (init_stats(), stats_exports)).then((m) => m.statsCommand),
2478
+ fixture: () => Promise.resolve().then(() => (init_fixture2(), fixture_exports)).then((m) => m.fixtureCommand),
2479
+ ladder: () => Promise.resolve().then(() => (init_ladder3(), ladder_exports)).then((m) => m.ladderCommand),
2480
+ lineup: () => Promise.resolve().then(() => (init_lineup3(), lineup_exports)).then((m) => m.lineupCommand),
2481
+ squad: () => Promise.resolve().then(() => (init_squad(), squad_exports)).then((m) => m.squadCommand),
2482
+ teams: () => Promise.resolve().then(() => (init_teams2(), teams_exports)).then((m) => m.teamsCommand)
704
2483
  }
705
2484
  });
706
2485
  function formatError(error) {
707
2486
  if (error instanceof ValidationError && error.issues) {
708
2487
  const issueLines = error.issues.map((i) => ` ${pc2.yellow(i.path)}: ${i.message}`);
709
2488
  return `${pc2.red("Validation error:")}
710
- ${issueLines.join(`
711
- `)}`;
2489
+ ${issueLines.join("\n")}`;
712
2490
  }
713
2491
  if (error instanceof AflApiError) {
714
2492
  const status = error.statusCode ? ` (HTTP ${error.statusCode})` : "";