fitzroy 1.1.1 → 1.2.0
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.
- package/dist/cli.js +459 -129
- package/dist/index.d.ts +5 -0
- package/dist/index.js +39 -7
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1301,6 +1301,27 @@ var init_coaches_votes = __esm({
|
|
|
1301
1301
|
}
|
|
1302
1302
|
});
|
|
1303
1303
|
|
|
1304
|
+
// src/lib/concurrency.ts
|
|
1305
|
+
async function batchedMap(items, fn, options) {
|
|
1306
|
+
const batchSize = options?.batchSize ?? 5;
|
|
1307
|
+
const delayMs = options?.delayMs ?? 0;
|
|
1308
|
+
const results = [];
|
|
1309
|
+
for (let i = 0; i < items.length; i += batchSize) {
|
|
1310
|
+
const batch = items.slice(i, i + batchSize);
|
|
1311
|
+
const batchResults = await Promise.all(batch.map(fn));
|
|
1312
|
+
results.push(...batchResults);
|
|
1313
|
+
if (delayMs > 0 && i + batchSize < items.length) {
|
|
1314
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
return results;
|
|
1318
|
+
}
|
|
1319
|
+
var init_concurrency = __esm({
|
|
1320
|
+
"src/lib/concurrency.ts"() {
|
|
1321
|
+
"use strict";
|
|
1322
|
+
}
|
|
1323
|
+
});
|
|
1324
|
+
|
|
1304
1325
|
// src/lib/validation.ts
|
|
1305
1326
|
import { z } from "zod/v4";
|
|
1306
1327
|
var AflApiTokenSchema, CompetitionSchema, CompetitionListSchema, CompseasonSchema, CompseasonListSchema, RoundSchema, RoundListSchema, ScoreSchema, PeriodScoreSchema, TeamScoreSchema, CfsMatchTeamSchema, CfsMatchSchema, CfsScoreSchema, CfsVenueSchema, MatchItemSchema, MatchItemListSchema, CfsPlayerInnerSchema, statNum, PlayerGameStatsSchema, PlayerStatsItemSchema, PlayerStatsListSchema, RosterPlayerSchema, TeamPlayersSchema, MatchRosterSchema, TeamItemSchema, TeamListSchema, SquadPlayerInnerSchema, SquadPlayerItemSchema, SquadSchema, SquadListSchema, WinLossRecordSchema, LadderEntryRawSchema, LadderResponseSchema;
|
|
@@ -1589,6 +1610,7 @@ var TOKEN_URL, API_BASE, CFS_BASE, AflApiClient;
|
|
|
1589
1610
|
var init_afl_api = __esm({
|
|
1590
1611
|
"src/sources/afl-api.ts"() {
|
|
1591
1612
|
"use strict";
|
|
1613
|
+
init_concurrency();
|
|
1592
1614
|
init_errors();
|
|
1593
1615
|
init_result();
|
|
1594
1616
|
init_validation();
|
|
@@ -1599,6 +1621,7 @@ var init_afl_api = __esm({
|
|
|
1599
1621
|
fetchFn;
|
|
1600
1622
|
tokenUrl;
|
|
1601
1623
|
cachedToken = null;
|
|
1624
|
+
pendingAuth = null;
|
|
1602
1625
|
constructor(options) {
|
|
1603
1626
|
this.fetchFn = options?.fetchFn ?? globalThis.fetch;
|
|
1604
1627
|
this.tokenUrl = options?.tokenUrl ?? TOKEN_URL;
|
|
@@ -1606,9 +1629,21 @@ var init_afl_api = __esm({
|
|
|
1606
1629
|
/**
|
|
1607
1630
|
* Authenticate with the WMCTok token endpoint and cache the token.
|
|
1608
1631
|
*
|
|
1632
|
+
* Concurrent callers share the same in-flight request to avoid
|
|
1633
|
+
* redundant token fetches (thundering herd prevention).
|
|
1634
|
+
*
|
|
1609
1635
|
* @returns The access token on success, or an error Result.
|
|
1610
1636
|
*/
|
|
1611
1637
|
async authenticate() {
|
|
1638
|
+
if (this.pendingAuth) {
|
|
1639
|
+
return this.pendingAuth;
|
|
1640
|
+
}
|
|
1641
|
+
this.pendingAuth = this.doAuthenticate().finally(() => {
|
|
1642
|
+
this.pendingAuth = null;
|
|
1643
|
+
});
|
|
1644
|
+
return this.pendingAuth;
|
|
1645
|
+
}
|
|
1646
|
+
async doAuthenticate() {
|
|
1612
1647
|
try {
|
|
1613
1648
|
const response = await this.fetchFn(this.tokenUrl, {
|
|
1614
1649
|
method: "POST",
|
|
@@ -1863,7 +1898,7 @@ var init_afl_api = __esm({
|
|
|
1863
1898
|
return roundsResult;
|
|
1864
1899
|
}
|
|
1865
1900
|
const providerIds = roundsResult.data.flatMap((r) => r.providerId ? [r.providerId] : []);
|
|
1866
|
-
const results = await
|
|
1901
|
+
const results = await batchedMap(providerIds, (id) => this.fetchRoundMatchItems(id));
|
|
1867
1902
|
const allItems = [];
|
|
1868
1903
|
for (const result of results) {
|
|
1869
1904
|
if (!result.success) {
|
|
@@ -2221,8 +2256,9 @@ async function fetchFixture(query) {
|
|
|
2221
2256
|
const roundProviderIds = roundsResult.data.flatMap(
|
|
2222
2257
|
(r) => r.providerId ? [{ providerId: r.providerId, roundNumber: r.roundNumber }] : []
|
|
2223
2258
|
);
|
|
2224
|
-
const roundResults = await
|
|
2225
|
-
roundProviderIds
|
|
2259
|
+
const roundResults = await batchedMap(
|
|
2260
|
+
roundProviderIds,
|
|
2261
|
+
(r) => client.fetchRoundMatchItems(r.providerId)
|
|
2226
2262
|
);
|
|
2227
2263
|
const fixtures = [];
|
|
2228
2264
|
for (let i = 0; i < roundResults.length; i++) {
|
|
@@ -2238,6 +2274,7 @@ async function fetchFixture(query) {
|
|
|
2238
2274
|
var init_fixture = __esm({
|
|
2239
2275
|
"src/api/fixture.ts"() {
|
|
2240
2276
|
"use strict";
|
|
2277
|
+
init_concurrency();
|
|
2241
2278
|
init_errors();
|
|
2242
2279
|
init_result();
|
|
2243
2280
|
init_team_mapping();
|
|
@@ -3034,8 +3071,9 @@ async function fetchLineup(query) {
|
|
|
3034
3071
|
if (matchItems.data.length === 0) {
|
|
3035
3072
|
return err(new AflApiError(`No matches found for round ${query.round}`));
|
|
3036
3073
|
}
|
|
3037
|
-
const rosterResults = await
|
|
3038
|
-
matchItems.data
|
|
3074
|
+
const rosterResults = await batchedMap(
|
|
3075
|
+
matchItems.data,
|
|
3076
|
+
(item) => client.fetchMatchRoster(item.match.matchId)
|
|
3039
3077
|
);
|
|
3040
3078
|
const lineups = [];
|
|
3041
3079
|
for (const rosterResult of rosterResults) {
|
|
@@ -3047,6 +3085,7 @@ async function fetchLineup(query) {
|
|
|
3047
3085
|
var init_lineup2 = __esm({
|
|
3048
3086
|
"src/api/lineup.ts"() {
|
|
3049
3087
|
"use strict";
|
|
3088
|
+
init_concurrency();
|
|
3050
3089
|
init_errors();
|
|
3051
3090
|
init_result();
|
|
3052
3091
|
init_afl_api();
|
|
@@ -3365,8 +3404,9 @@ async function fetchPlayerStats(query) {
|
|
|
3365
3404
|
teamIdMap.set(item.match.homeTeamId, item.match.homeTeam.name);
|
|
3366
3405
|
teamIdMap.set(item.match.awayTeamId, item.match.awayTeam.name);
|
|
3367
3406
|
}
|
|
3368
|
-
const statsResults = await
|
|
3369
|
-
matchItemsResult.data
|
|
3407
|
+
const statsResults = await batchedMap(
|
|
3408
|
+
matchItemsResult.data,
|
|
3409
|
+
(item) => client.fetchPlayerStats(item.match.matchId)
|
|
3370
3410
|
);
|
|
3371
3411
|
const allStats = [];
|
|
3372
3412
|
for (let i = 0; i < statsResults.length; i++) {
|
|
@@ -3434,6 +3474,7 @@ async function fetchPlayerStats(query) {
|
|
|
3434
3474
|
var init_player_stats2 = __esm({
|
|
3435
3475
|
"src/api/player-stats.ts"() {
|
|
3436
3476
|
"use strict";
|
|
3477
|
+
init_concurrency();
|
|
3437
3478
|
init_errors();
|
|
3438
3479
|
init_result();
|
|
3439
3480
|
init_afl_api();
|
|
@@ -3556,6 +3597,94 @@ var init_index = __esm({
|
|
|
3556
3597
|
}
|
|
3557
3598
|
});
|
|
3558
3599
|
|
|
3600
|
+
// src/cli/flags.ts
|
|
3601
|
+
var SEASON_FLAG, OPTIONAL_SEASON_FLAG, ROUND_FLAG, REQUIRED_ROUND_FLAG, SOURCE_FLAG, COMPETITION_FLAG, OPTIONAL_COMPETITION_FLAG, OUTPUT_FLAGS, REQUIRED_TEAM_FLAG, TEAM_FLAG, PLAYER_FLAG;
|
|
3602
|
+
var init_flags = __esm({
|
|
3603
|
+
"src/cli/flags.ts"() {
|
|
3604
|
+
"use strict";
|
|
3605
|
+
SEASON_FLAG = {
|
|
3606
|
+
season: {
|
|
3607
|
+
type: "string",
|
|
3608
|
+
description: "Season year (e.g. 2025)",
|
|
3609
|
+
required: true,
|
|
3610
|
+
alias: "s"
|
|
3611
|
+
}
|
|
3612
|
+
};
|
|
3613
|
+
OPTIONAL_SEASON_FLAG = {
|
|
3614
|
+
season: {
|
|
3615
|
+
type: "string",
|
|
3616
|
+
description: "Season year (e.g. 2025)",
|
|
3617
|
+
alias: "s"
|
|
3618
|
+
}
|
|
3619
|
+
};
|
|
3620
|
+
ROUND_FLAG = {
|
|
3621
|
+
round: {
|
|
3622
|
+
type: "string",
|
|
3623
|
+
description: "Round number",
|
|
3624
|
+
alias: "r"
|
|
3625
|
+
}
|
|
3626
|
+
};
|
|
3627
|
+
REQUIRED_ROUND_FLAG = {
|
|
3628
|
+
round: {
|
|
3629
|
+
type: "string",
|
|
3630
|
+
description: "Round number",
|
|
3631
|
+
required: true,
|
|
3632
|
+
alias: "r"
|
|
3633
|
+
}
|
|
3634
|
+
};
|
|
3635
|
+
SOURCE_FLAG = {
|
|
3636
|
+
source: {
|
|
3637
|
+
type: "string",
|
|
3638
|
+
description: "Data source",
|
|
3639
|
+
default: "afl-api"
|
|
3640
|
+
}
|
|
3641
|
+
};
|
|
3642
|
+
COMPETITION_FLAG = {
|
|
3643
|
+
competition: {
|
|
3644
|
+
type: "string",
|
|
3645
|
+
description: "Competition code (AFLM or AFLW)",
|
|
3646
|
+
default: "AFLM",
|
|
3647
|
+
alias: "c"
|
|
3648
|
+
}
|
|
3649
|
+
};
|
|
3650
|
+
OPTIONAL_COMPETITION_FLAG = {
|
|
3651
|
+
competition: {
|
|
3652
|
+
type: "string",
|
|
3653
|
+
description: "Competition code (AFLM or AFLW)",
|
|
3654
|
+
alias: "c"
|
|
3655
|
+
}
|
|
3656
|
+
};
|
|
3657
|
+
OUTPUT_FLAGS = {
|
|
3658
|
+
json: { type: "boolean", description: "Output as JSON", alias: "j" },
|
|
3659
|
+
csv: { type: "boolean", description: "Output as CSV" },
|
|
3660
|
+
format: { type: "string", description: "Output format: table, json, csv" },
|
|
3661
|
+
full: { type: "boolean", description: "Show all columns in table output" }
|
|
3662
|
+
};
|
|
3663
|
+
REQUIRED_TEAM_FLAG = {
|
|
3664
|
+
team: {
|
|
3665
|
+
type: "string",
|
|
3666
|
+
description: "Team name, abbreviation, or ID (e.g. Carlton, CARL, 5)",
|
|
3667
|
+
required: true,
|
|
3668
|
+
alias: "t"
|
|
3669
|
+
}
|
|
3670
|
+
};
|
|
3671
|
+
TEAM_FLAG = {
|
|
3672
|
+
team: {
|
|
3673
|
+
type: "string",
|
|
3674
|
+
description: "Filter by team name",
|
|
3675
|
+
alias: "t"
|
|
3676
|
+
}
|
|
3677
|
+
};
|
|
3678
|
+
PLAYER_FLAG = {
|
|
3679
|
+
player: {
|
|
3680
|
+
type: "string",
|
|
3681
|
+
description: "Filter by player name",
|
|
3682
|
+
alias: "p"
|
|
3683
|
+
}
|
|
3684
|
+
};
|
|
3685
|
+
}
|
|
3686
|
+
});
|
|
3687
|
+
|
|
3559
3688
|
// src/cli/formatters/csv.ts
|
|
3560
3689
|
function escapeField(value) {
|
|
3561
3690
|
if (value.includes(",") || value.includes('"') || value.includes("\n") || value.includes("\r")) {
|
|
@@ -3822,6 +3951,29 @@ function resolveTeamIdentifier(raw, teams) {
|
|
|
3822
3951
|
const validNames = teams.map((t) => `${t.name} (${t.abbreviation})`).join(", ");
|
|
3823
3952
|
throw new Error(`Unknown team: "${raw}" \u2014 valid teams are: ${validNames}`);
|
|
3824
3953
|
}
|
|
3954
|
+
function resolveMatchByTeam(teamSearch, matchItems) {
|
|
3955
|
+
const normalised = normaliseTeamName(teamSearch);
|
|
3956
|
+
const lower = teamSearch.toLowerCase();
|
|
3957
|
+
const matches = matchItems.filter((item) => {
|
|
3958
|
+
const home = item.match.homeTeam.name;
|
|
3959
|
+
const away = item.match.awayTeam.name;
|
|
3960
|
+
return normaliseTeamName(home) === normalised || normaliseTeamName(away) === normalised || home.toLowerCase().includes(lower) || away.toLowerCase().includes(lower);
|
|
3961
|
+
});
|
|
3962
|
+
const singleMatch = matches[0];
|
|
3963
|
+
if (matches.length === 1 && singleMatch) {
|
|
3964
|
+
return singleMatch.match.matchId;
|
|
3965
|
+
}
|
|
3966
|
+
if (matches.length === 0) {
|
|
3967
|
+
const available = matchItems.map((item) => `${item.match.homeTeam.name} vs ${item.match.awayTeam.name}`).join(", ");
|
|
3968
|
+
throw new Error(
|
|
3969
|
+
`No match found for "${teamSearch}" in this round. Available matches: ${available}`
|
|
3970
|
+
);
|
|
3971
|
+
}
|
|
3972
|
+
const ambiguous = matches.map((item) => `${item.match.homeTeam.name} vs ${item.match.awayTeam.name}`).join(", ");
|
|
3973
|
+
throw new Error(
|
|
3974
|
+
`Multiple matches found for "${teamSearch}": ${ambiguous}. Please be more specific.`
|
|
3975
|
+
);
|
|
3976
|
+
}
|
|
3825
3977
|
var VALID_SOURCES, VALID_COMPETITIONS, VALID_FORMATS;
|
|
3826
3978
|
var init_validation2 = __esm({
|
|
3827
3979
|
"src/cli/validation.ts"() {
|
|
@@ -3844,6 +3996,7 @@ var init_matches = __esm({
|
|
|
3844
3996
|
"src/cli/commands/matches.ts"() {
|
|
3845
3997
|
"use strict";
|
|
3846
3998
|
init_index();
|
|
3999
|
+
init_flags();
|
|
3847
4000
|
init_formatters();
|
|
3848
4001
|
init_ui();
|
|
3849
4002
|
init_validation2();
|
|
@@ -3862,18 +4015,11 @@ var init_matches = __esm({
|
|
|
3862
4015
|
description: "Fetch match results for a season"
|
|
3863
4016
|
},
|
|
3864
4017
|
args: {
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
description: "Competition code (AFLM or AFLW)",
|
|
3871
|
-
default: "AFLM"
|
|
3872
|
-
},
|
|
3873
|
-
json: { type: "boolean", description: "Output as JSON" },
|
|
3874
|
-
csv: { type: "boolean", description: "Output as CSV" },
|
|
3875
|
-
format: { type: "string", description: "Output format: table, json, csv" },
|
|
3876
|
-
full: { type: "boolean", description: "Show all columns in table output" }
|
|
4018
|
+
...SEASON_FLAG,
|
|
4019
|
+
...ROUND_FLAG,
|
|
4020
|
+
...SOURCE_FLAG,
|
|
4021
|
+
...COMPETITION_FLAG,
|
|
4022
|
+
...OUTPUT_FLAGS
|
|
3877
4023
|
},
|
|
3878
4024
|
async run({ args }) {
|
|
3879
4025
|
const season = validateSeason(args.season);
|
|
@@ -3903,6 +4049,203 @@ var init_matches = __esm({
|
|
|
3903
4049
|
}
|
|
3904
4050
|
});
|
|
3905
4051
|
|
|
4052
|
+
// src/lib/fuzzy.ts
|
|
4053
|
+
function levenshteinDistance(a, b) {
|
|
4054
|
+
const la = a.length;
|
|
4055
|
+
const lb = b.length;
|
|
4056
|
+
if (la === 0) return lb;
|
|
4057
|
+
if (lb === 0) return la;
|
|
4058
|
+
if (la < lb) return levenshteinDistance(b, a);
|
|
4059
|
+
const row = Array.from({ length: lb + 1 }, (_, i) => i);
|
|
4060
|
+
for (let i = 1; i <= la; i++) {
|
|
4061
|
+
let prev = i;
|
|
4062
|
+
for (let j = 1; j <= lb; j++) {
|
|
4063
|
+
const current = row[j - 1];
|
|
4064
|
+
const rowJ = row[j];
|
|
4065
|
+
if (current === void 0 || rowJ === void 0) continue;
|
|
4066
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
4067
|
+
const val = Math.min(rowJ + 1, prev + 1, current + cost);
|
|
4068
|
+
row[j - 1] = prev;
|
|
4069
|
+
prev = val;
|
|
4070
|
+
}
|
|
4071
|
+
row[lb] = prev;
|
|
4072
|
+
}
|
|
4073
|
+
return row[lb] ?? 0;
|
|
4074
|
+
}
|
|
4075
|
+
function fuzzySearch(query, candidates, keySelector, options) {
|
|
4076
|
+
const maxResults = options?.maxResults ?? 10;
|
|
4077
|
+
const threshold = options?.threshold ?? 0.4;
|
|
4078
|
+
const lowerQuery = query.toLowerCase();
|
|
4079
|
+
const results = [];
|
|
4080
|
+
for (const item of candidates) {
|
|
4081
|
+
const key = keySelector(item).toLowerCase();
|
|
4082
|
+
if (key === lowerQuery) {
|
|
4083
|
+
results.push({ item, score: 0 });
|
|
4084
|
+
continue;
|
|
4085
|
+
}
|
|
4086
|
+
if (key.startsWith(lowerQuery)) {
|
|
4087
|
+
results.push({ item, score: 0.1 });
|
|
4088
|
+
continue;
|
|
4089
|
+
}
|
|
4090
|
+
if (key.includes(lowerQuery)) {
|
|
4091
|
+
results.push({ item, score: 0.3 });
|
|
4092
|
+
continue;
|
|
4093
|
+
}
|
|
4094
|
+
const maxLen = Math.max(lowerQuery.length, key.length);
|
|
4095
|
+
if (maxLen === 0) continue;
|
|
4096
|
+
const distance = levenshteinDistance(lowerQuery, key);
|
|
4097
|
+
const normalised = distance / maxLen;
|
|
4098
|
+
if (normalised <= threshold) {
|
|
4099
|
+
const score = 0.4 + normalised / threshold * 0.6;
|
|
4100
|
+
results.push({ item, score });
|
|
4101
|
+
}
|
|
4102
|
+
}
|
|
4103
|
+
results.sort((a, b) => {
|
|
4104
|
+
if (a.score !== b.score) return a.score - b.score;
|
|
4105
|
+
return keySelector(a.item).localeCompare(keySelector(b.item));
|
|
4106
|
+
});
|
|
4107
|
+
return results.slice(0, maxResults);
|
|
4108
|
+
}
|
|
4109
|
+
var init_fuzzy = __esm({
|
|
4110
|
+
"src/lib/fuzzy.ts"() {
|
|
4111
|
+
"use strict";
|
|
4112
|
+
}
|
|
4113
|
+
});
|
|
4114
|
+
|
|
4115
|
+
// src/cli/resolvers.ts
|
|
4116
|
+
import { isCancel, select } from "@clack/prompts";
|
|
4117
|
+
async function resolveTeamOrPrompt(query, teams) {
|
|
4118
|
+
try {
|
|
4119
|
+
return resolveTeamIdentifier(query, teams);
|
|
4120
|
+
} catch {
|
|
4121
|
+
}
|
|
4122
|
+
const matches = fuzzySearch(query.trim(), teams, (t) => t.name, {
|
|
4123
|
+
maxResults: 5,
|
|
4124
|
+
threshold: 0.4
|
|
4125
|
+
});
|
|
4126
|
+
const abbrevMatches = fuzzySearch(query.trim(), teams, (t) => t.abbreviation, {
|
|
4127
|
+
maxResults: 5,
|
|
4128
|
+
threshold: 0.4
|
|
4129
|
+
});
|
|
4130
|
+
const seen = new Set(matches.map((m) => m.item.teamId));
|
|
4131
|
+
for (const m of abbrevMatches) {
|
|
4132
|
+
if (!seen.has(m.item.teamId)) {
|
|
4133
|
+
matches.push(m);
|
|
4134
|
+
seen.add(m.item.teamId);
|
|
4135
|
+
}
|
|
4136
|
+
}
|
|
4137
|
+
matches.sort((a, b) => a.score - b.score);
|
|
4138
|
+
return disambiguate(
|
|
4139
|
+
query.trim(),
|
|
4140
|
+
matches.map((m) => ({ value: m.item.teamId, label: m.item.name, score: m.score })),
|
|
4141
|
+
teams.map((t) => `${t.name} (${t.abbreviation})`),
|
|
4142
|
+
"team"
|
|
4143
|
+
);
|
|
4144
|
+
}
|
|
4145
|
+
async function resolveTeamNameOrPrompt(query, teamNames) {
|
|
4146
|
+
const trimmed = query.trim();
|
|
4147
|
+
const canonical = normaliseTeamName(trimmed);
|
|
4148
|
+
const candidates = teamNames ?? [...AFL_SENIOR_TEAMS];
|
|
4149
|
+
if (candidates.includes(canonical)) {
|
|
4150
|
+
return canonical;
|
|
4151
|
+
}
|
|
4152
|
+
const items = candidates.map((name) => ({ name }));
|
|
4153
|
+
const matches = fuzzySearch(trimmed, items, (t) => t.name, {
|
|
4154
|
+
maxResults: 5,
|
|
4155
|
+
threshold: 0.4
|
|
4156
|
+
});
|
|
4157
|
+
return disambiguate(
|
|
4158
|
+
trimmed,
|
|
4159
|
+
matches.map((m) => ({ value: m.item.name, label: m.item.name, score: m.score })),
|
|
4160
|
+
candidates,
|
|
4161
|
+
"team"
|
|
4162
|
+
);
|
|
4163
|
+
}
|
|
4164
|
+
async function resolveMatchOrPrompt(query, matchItems) {
|
|
4165
|
+
try {
|
|
4166
|
+
return resolveMatchByTeam(query, matchItems);
|
|
4167
|
+
} catch {
|
|
4168
|
+
}
|
|
4169
|
+
const labelledItems = matchItems.map((item) => ({
|
|
4170
|
+
item,
|
|
4171
|
+
label: `${item.match.homeTeam.name} vs ${item.match.awayTeam.name}`
|
|
4172
|
+
}));
|
|
4173
|
+
const matches = fuzzySearch(query, labelledItems, (l) => l.label, {
|
|
4174
|
+
maxResults: 5,
|
|
4175
|
+
threshold: 0.5
|
|
4176
|
+
});
|
|
4177
|
+
const homeMatches = fuzzySearch(query, matchItems, (i) => i.match.homeTeam.name, {
|
|
4178
|
+
maxResults: 5,
|
|
4179
|
+
threshold: 0.4
|
|
4180
|
+
});
|
|
4181
|
+
const awayMatches = fuzzySearch(query, matchItems, (i) => i.match.awayTeam.name, {
|
|
4182
|
+
maxResults: 5,
|
|
4183
|
+
threshold: 0.4
|
|
4184
|
+
});
|
|
4185
|
+
const seen = new Set(matches.map((m) => m.item.item.match.matchId));
|
|
4186
|
+
for (const m of homeMatches) {
|
|
4187
|
+
if (!seen.has(m.item.match.matchId)) {
|
|
4188
|
+
const label = `${m.item.match.homeTeam.name} vs ${m.item.match.awayTeam.name}`;
|
|
4189
|
+
matches.push({ item: { item: m.item, label }, score: m.score });
|
|
4190
|
+
seen.add(m.item.match.matchId);
|
|
4191
|
+
}
|
|
4192
|
+
}
|
|
4193
|
+
for (const m of awayMatches) {
|
|
4194
|
+
if (!seen.has(m.item.match.matchId)) {
|
|
4195
|
+
const label = `${m.item.match.homeTeam.name} vs ${m.item.match.awayTeam.name}`;
|
|
4196
|
+
matches.push({ item: { item: m.item, label }, score: m.score });
|
|
4197
|
+
seen.add(m.item.match.matchId);
|
|
4198
|
+
}
|
|
4199
|
+
}
|
|
4200
|
+
matches.sort((a, b) => a.score - b.score);
|
|
4201
|
+
const available = matchItems.map(
|
|
4202
|
+
(item) => `${item.match.homeTeam.name} vs ${item.match.awayTeam.name}`
|
|
4203
|
+
);
|
|
4204
|
+
return disambiguate(
|
|
4205
|
+
query,
|
|
4206
|
+
matches.map((m) => ({
|
|
4207
|
+
value: m.item.item.match.matchId,
|
|
4208
|
+
label: m.item.label,
|
|
4209
|
+
score: m.score
|
|
4210
|
+
})),
|
|
4211
|
+
available,
|
|
4212
|
+
"match"
|
|
4213
|
+
);
|
|
4214
|
+
}
|
|
4215
|
+
async function disambiguate(query, options, allLabels, entityName) {
|
|
4216
|
+
const best = options[0];
|
|
4217
|
+
if (!best) {
|
|
4218
|
+
throw new Error(
|
|
4219
|
+
`No ${entityName} found for "${query}". Valid options: ${allLabels.join(", ")}`
|
|
4220
|
+
);
|
|
4221
|
+
}
|
|
4222
|
+
if (best.score < 0.2 || options.length === 1) {
|
|
4223
|
+
return best.value;
|
|
4224
|
+
}
|
|
4225
|
+
if (isTTY2) {
|
|
4226
|
+
const choice = await select({
|
|
4227
|
+
message: `Multiple ${entityName}s matched "${query}". Which did you mean?`,
|
|
4228
|
+
options: options.map((o) => ({ value: o.value, label: o.label }))
|
|
4229
|
+
});
|
|
4230
|
+
if (isCancel(choice)) {
|
|
4231
|
+
process.exit(0);
|
|
4232
|
+
}
|
|
4233
|
+
return choice;
|
|
4234
|
+
}
|
|
4235
|
+
console.error(`Matched "${query}" \u2192 ${best.label}`);
|
|
4236
|
+
return best.value;
|
|
4237
|
+
}
|
|
4238
|
+
var isTTY2;
|
|
4239
|
+
var init_resolvers = __esm({
|
|
4240
|
+
"src/cli/resolvers.ts"() {
|
|
4241
|
+
"use strict";
|
|
4242
|
+
init_fuzzy();
|
|
4243
|
+
init_team_mapping();
|
|
4244
|
+
init_validation2();
|
|
4245
|
+
isTTY2 = process.stdout.isTTY === true;
|
|
4246
|
+
}
|
|
4247
|
+
});
|
|
4248
|
+
|
|
3906
4249
|
// src/cli/commands/stats.ts
|
|
3907
4250
|
var stats_exports = {};
|
|
3908
4251
|
__export(stats_exports, {
|
|
@@ -3914,7 +4257,11 @@ var init_stats = __esm({
|
|
|
3914
4257
|
"src/cli/commands/stats.ts"() {
|
|
3915
4258
|
"use strict";
|
|
3916
4259
|
init_index();
|
|
4260
|
+
init_fuzzy();
|
|
4261
|
+
init_afl_api();
|
|
4262
|
+
init_flags();
|
|
3917
4263
|
init_formatters();
|
|
4264
|
+
init_resolvers();
|
|
3918
4265
|
init_ui();
|
|
3919
4266
|
init_validation2();
|
|
3920
4267
|
DEFAULT_COLUMNS2 = [
|
|
@@ -3932,27 +4279,32 @@ var init_stats = __esm({
|
|
|
3932
4279
|
description: "Fetch player statistics for a season"
|
|
3933
4280
|
},
|
|
3934
4281
|
args: {
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
},
|
|
3944
|
-
json: { type: "boolean", description: "Output as JSON" },
|
|
3945
|
-
csv: { type: "boolean", description: "Output as CSV" },
|
|
3946
|
-
format: { type: "string", description: "Output format: table, json, csv" },
|
|
3947
|
-
full: { type: "boolean", description: "Show all columns in table output" }
|
|
4282
|
+
...SEASON_FLAG,
|
|
4283
|
+
...ROUND_FLAG,
|
|
4284
|
+
match: { type: "string", description: "Filter by team name to find a specific match" },
|
|
4285
|
+
"match-id": { type: "string", description: "Specific match provider ID (advanced)" },
|
|
4286
|
+
...SOURCE_FLAG,
|
|
4287
|
+
...COMPETITION_FLAG,
|
|
4288
|
+
...PLAYER_FLAG,
|
|
4289
|
+
...OUTPUT_FLAGS
|
|
3948
4290
|
},
|
|
3949
4291
|
async run({ args }) {
|
|
3950
4292
|
const season = validateSeason(args.season);
|
|
3951
4293
|
const round = args.round ? validateRound(args.round) : void 0;
|
|
3952
|
-
const matchId = args["match-id"];
|
|
3953
4294
|
const source = validateSource(args.source);
|
|
3954
4295
|
const competition = validateCompetition(args.competition);
|
|
3955
4296
|
const format = validateFormat(args.format);
|
|
4297
|
+
let matchId = args["match-id"];
|
|
4298
|
+
if (!matchId && args.match && round != null) {
|
|
4299
|
+
const client = new AflApiClient();
|
|
4300
|
+
const seasonResult = await client.resolveCompSeason(competition, season);
|
|
4301
|
+
if (!seasonResult.success) throw seasonResult.error;
|
|
4302
|
+
const itemsResult = await client.fetchRoundMatchItemsByNumber(seasonResult.data, round);
|
|
4303
|
+
if (!itemsResult.success) throw itemsResult.error;
|
|
4304
|
+
matchId = await resolveMatchOrPrompt(args.match, itemsResult.data);
|
|
4305
|
+
} else if (args.match && round == null) {
|
|
4306
|
+
throw new Error("--match requires --round (-r) to identify which round to search.");
|
|
4307
|
+
}
|
|
3956
4308
|
const result = await withSpinner(
|
|
3957
4309
|
"Fetching player stats\u2026",
|
|
3958
4310
|
() => fetchPlayerStats({ source, season, round, matchId, competition })
|
|
@@ -3960,7 +4312,14 @@ var init_stats = __esm({
|
|
|
3960
4312
|
if (!result.success) {
|
|
3961
4313
|
throw result.error;
|
|
3962
4314
|
}
|
|
3963
|
-
|
|
4315
|
+
let data = result.data;
|
|
4316
|
+
if (args.player) {
|
|
4317
|
+
const playerMatches = fuzzySearch(args.player, data, (p) => p.displayName, {
|
|
4318
|
+
maxResults: 50,
|
|
4319
|
+
threshold: 0.4
|
|
4320
|
+
});
|
|
4321
|
+
data = playerMatches.map((m) => m.item);
|
|
4322
|
+
}
|
|
3964
4323
|
showSummary(
|
|
3965
4324
|
`Loaded ${data.length} player stat lines for ${season}${round ? ` round ${round}` : ""}`
|
|
3966
4325
|
);
|
|
@@ -3988,6 +4347,7 @@ var init_fixture2 = __esm({
|
|
|
3988
4347
|
"src/cli/commands/fixture.ts"() {
|
|
3989
4348
|
"use strict";
|
|
3990
4349
|
init_index();
|
|
4350
|
+
init_flags();
|
|
3991
4351
|
init_formatters();
|
|
3992
4352
|
init_ui();
|
|
3993
4353
|
init_validation2();
|
|
@@ -4004,18 +4364,11 @@ var init_fixture2 = __esm({
|
|
|
4004
4364
|
description: "Fetch fixture/schedule for a season"
|
|
4005
4365
|
},
|
|
4006
4366
|
args: {
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
description: "Competition code (AFLM or AFLW)",
|
|
4013
|
-
default: "AFLM"
|
|
4014
|
-
},
|
|
4015
|
-
json: { type: "boolean", description: "Output as JSON" },
|
|
4016
|
-
csv: { type: "boolean", description: "Output as CSV" },
|
|
4017
|
-
format: { type: "string", description: "Output format: table, json, csv" },
|
|
4018
|
-
full: { type: "boolean", description: "Show all columns in table output" }
|
|
4367
|
+
...SEASON_FLAG,
|
|
4368
|
+
...ROUND_FLAG,
|
|
4369
|
+
...SOURCE_FLAG,
|
|
4370
|
+
...COMPETITION_FLAG,
|
|
4371
|
+
...OUTPUT_FLAGS
|
|
4019
4372
|
},
|
|
4020
4373
|
async run({ args }) {
|
|
4021
4374
|
const season = validateSeason(args.season);
|
|
@@ -4056,6 +4409,7 @@ var init_ladder3 = __esm({
|
|
|
4056
4409
|
"src/cli/commands/ladder.ts"() {
|
|
4057
4410
|
"use strict";
|
|
4058
4411
|
init_index();
|
|
4412
|
+
init_flags();
|
|
4059
4413
|
init_formatters();
|
|
4060
4414
|
init_ui();
|
|
4061
4415
|
init_validation2();
|
|
@@ -4074,18 +4428,11 @@ var init_ladder3 = __esm({
|
|
|
4074
4428
|
description: "Fetch ladder standings for a season"
|
|
4075
4429
|
},
|
|
4076
4430
|
args: {
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
description: "Competition code (AFLM or AFLW)",
|
|
4083
|
-
default: "AFLM"
|
|
4084
|
-
},
|
|
4085
|
-
json: { type: "boolean", description: "Output as JSON" },
|
|
4086
|
-
csv: { type: "boolean", description: "Output as CSV" },
|
|
4087
|
-
format: { type: "string", description: "Output format: table, json, csv" },
|
|
4088
|
-
full: { type: "boolean", description: "Show all columns in table output" }
|
|
4431
|
+
...SEASON_FLAG,
|
|
4432
|
+
...ROUND_FLAG,
|
|
4433
|
+
...SOURCE_FLAG,
|
|
4434
|
+
...COMPETITION_FLAG,
|
|
4435
|
+
...OUTPUT_FLAGS
|
|
4089
4436
|
},
|
|
4090
4437
|
async run({ args }) {
|
|
4091
4438
|
const season = validateSeason(args.season);
|
|
@@ -4150,7 +4497,10 @@ var init_lineup3 = __esm({
|
|
|
4150
4497
|
"src/cli/commands/lineup.ts"() {
|
|
4151
4498
|
"use strict";
|
|
4152
4499
|
init_index();
|
|
4500
|
+
init_afl_api();
|
|
4501
|
+
init_flags();
|
|
4153
4502
|
init_formatters();
|
|
4503
|
+
init_resolvers();
|
|
4154
4504
|
init_ui();
|
|
4155
4505
|
init_validation2();
|
|
4156
4506
|
DEFAULT_COLUMNS5 = [
|
|
@@ -4166,27 +4516,29 @@ var init_lineup3 = __esm({
|
|
|
4166
4516
|
description: "Fetch match lineups for a round"
|
|
4167
4517
|
},
|
|
4168
4518
|
args: {
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
default: "AFLM"
|
|
4177
|
-
},
|
|
4178
|
-
json: { type: "boolean", description: "Output as JSON" },
|
|
4179
|
-
csv: { type: "boolean", description: "Output as CSV" },
|
|
4180
|
-
format: { type: "string", description: "Output format: table, json, csv" },
|
|
4181
|
-
full: { type: "boolean", description: "Show all columns in table output" }
|
|
4519
|
+
...SEASON_FLAG,
|
|
4520
|
+
...REQUIRED_ROUND_FLAG,
|
|
4521
|
+
match: { type: "string", description: "Filter by team name to find a specific match" },
|
|
4522
|
+
"match-id": { type: "string", description: "Specific match provider ID (advanced)" },
|
|
4523
|
+
...SOURCE_FLAG,
|
|
4524
|
+
...COMPETITION_FLAG,
|
|
4525
|
+
...OUTPUT_FLAGS
|
|
4182
4526
|
},
|
|
4183
4527
|
async run({ args }) {
|
|
4184
4528
|
const season = validateSeason(args.season);
|
|
4185
4529
|
const round = validateRound(args.round);
|
|
4186
|
-
const matchId = args["match-id"];
|
|
4187
4530
|
const source = validateSource(args.source);
|
|
4188
4531
|
const competition = validateCompetition(args.competition);
|
|
4189
4532
|
const format = validateFormat(args.format);
|
|
4533
|
+
let matchId = args["match-id"];
|
|
4534
|
+
if (!matchId && args.match) {
|
|
4535
|
+
const client = new AflApiClient();
|
|
4536
|
+
const seasonResult = await client.resolveCompSeason(competition, season);
|
|
4537
|
+
if (!seasonResult.success) throw seasonResult.error;
|
|
4538
|
+
const itemsResult = await client.fetchRoundMatchItemsByNumber(seasonResult.data, round);
|
|
4539
|
+
if (!itemsResult.success) throw itemsResult.error;
|
|
4540
|
+
matchId = await resolveMatchOrPrompt(args.match, itemsResult.data);
|
|
4541
|
+
}
|
|
4190
4542
|
const result = await withSpinner(
|
|
4191
4543
|
"Fetching lineups\u2026",
|
|
4192
4544
|
() => fetchLineup({ source, season, round, matchId, competition })
|
|
@@ -4225,7 +4577,9 @@ var init_squad = __esm({
|
|
|
4225
4577
|
"src/cli/commands/squad.ts"() {
|
|
4226
4578
|
"use strict";
|
|
4227
4579
|
init_index();
|
|
4580
|
+
init_flags();
|
|
4228
4581
|
init_formatters();
|
|
4582
|
+
init_resolvers();
|
|
4229
4583
|
init_ui();
|
|
4230
4584
|
init_validation2();
|
|
4231
4585
|
DEFAULT_COLUMNS6 = [
|
|
@@ -4241,34 +4595,22 @@ var init_squad = __esm({
|
|
|
4241
4595
|
description: "Fetch team squad for a season"
|
|
4242
4596
|
},
|
|
4243
4597
|
args: {
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
},
|
|
4249
|
-
season: { type: "string", description: "Season year (e.g. 2025)", required: true },
|
|
4250
|
-
competition: {
|
|
4251
|
-
type: "string",
|
|
4252
|
-
description: "Competition code (AFLM or AFLW)",
|
|
4253
|
-
default: "AFLM"
|
|
4254
|
-
},
|
|
4255
|
-
json: { type: "boolean", description: "Output as JSON" },
|
|
4256
|
-
csv: { type: "boolean", description: "Output as CSV" },
|
|
4257
|
-
format: { type: "string", description: "Output format: table, json, csv" },
|
|
4258
|
-
full: { type: "boolean", description: "Show all columns in table output" }
|
|
4598
|
+
...REQUIRED_TEAM_FLAG,
|
|
4599
|
+
...SEASON_FLAG,
|
|
4600
|
+
...COMPETITION_FLAG,
|
|
4601
|
+
...OUTPUT_FLAGS
|
|
4259
4602
|
},
|
|
4260
4603
|
async run({ args }) {
|
|
4261
4604
|
const season = validateSeason(args.season);
|
|
4262
4605
|
const competition = validateCompetition(args.competition);
|
|
4263
4606
|
const format = validateFormat(args.format);
|
|
4264
|
-
let teamId = args
|
|
4265
|
-
|
|
4266
|
-
if (!isNumeric) {
|
|
4607
|
+
let teamId = args.team.trim();
|
|
4608
|
+
if (!/^\d+$/.test(teamId)) {
|
|
4267
4609
|
const teamsResult = await withSpinner("Resolving team\u2026", () => fetchTeams({ competition }));
|
|
4268
4610
|
if (!teamsResult.success) {
|
|
4269
4611
|
throw teamsResult.error;
|
|
4270
4612
|
}
|
|
4271
|
-
teamId =
|
|
4613
|
+
teamId = await resolveTeamOrPrompt(args.team, teamsResult.data);
|
|
4272
4614
|
}
|
|
4273
4615
|
const result = await withSpinner(
|
|
4274
4616
|
"Fetching squad\u2026",
|
|
@@ -4303,6 +4645,7 @@ var init_teams2 = __esm({
|
|
|
4303
4645
|
"src/cli/commands/teams.ts"() {
|
|
4304
4646
|
"use strict";
|
|
4305
4647
|
init_index();
|
|
4648
|
+
init_flags();
|
|
4306
4649
|
init_formatters();
|
|
4307
4650
|
init_ui();
|
|
4308
4651
|
init_validation2();
|
|
@@ -4318,12 +4661,9 @@ var init_teams2 = __esm({
|
|
|
4318
4661
|
description: "Fetch team list"
|
|
4319
4662
|
},
|
|
4320
4663
|
args: {
|
|
4321
|
-
|
|
4664
|
+
...OPTIONAL_COMPETITION_FLAG,
|
|
4322
4665
|
"team-type": { type: "string", description: "Team type filter" },
|
|
4323
|
-
|
|
4324
|
-
csv: { type: "boolean", description: "Output as CSV" },
|
|
4325
|
-
format: { type: "string", description: "Output format: table, json, csv" },
|
|
4326
|
-
full: { type: "boolean", description: "Show all columns in table output" }
|
|
4666
|
+
...OUTPUT_FLAGS
|
|
4327
4667
|
},
|
|
4328
4668
|
async run({ args }) {
|
|
4329
4669
|
const competition = validateOptionalCompetition(args.competition);
|
|
@@ -4372,6 +4712,7 @@ var init_team_stats2 = __esm({
|
|
|
4372
4712
|
"src/cli/commands/team-stats.ts"() {
|
|
4373
4713
|
"use strict";
|
|
4374
4714
|
init_index();
|
|
4715
|
+
init_flags();
|
|
4375
4716
|
init_formatters();
|
|
4376
4717
|
init_ui();
|
|
4377
4718
|
init_validation2();
|
|
@@ -4393,17 +4734,14 @@ var init_team_stats2 = __esm({
|
|
|
4393
4734
|
description: "Fetch team aggregate statistics for a season"
|
|
4394
4735
|
},
|
|
4395
4736
|
args: {
|
|
4396
|
-
|
|
4737
|
+
...SEASON_FLAG,
|
|
4397
4738
|
source: {
|
|
4398
4739
|
type: "string",
|
|
4399
4740
|
description: "Data source (footywire, afl-tables)",
|
|
4400
4741
|
default: "footywire"
|
|
4401
4742
|
},
|
|
4402
4743
|
summary: { type: "string", description: "Summary type: totals or averages", default: "totals" },
|
|
4403
|
-
|
|
4404
|
-
csv: { type: "boolean", description: "Output as CSV" },
|
|
4405
|
-
format: { type: "string", description: "Output format: table, json, csv" },
|
|
4406
|
-
full: { type: "boolean", description: "Show all columns in table output" }
|
|
4744
|
+
...OUTPUT_FLAGS
|
|
4407
4745
|
},
|
|
4408
4746
|
async run({ args }) {
|
|
4409
4747
|
const season = validateSeason(args.season);
|
|
@@ -4444,7 +4782,9 @@ var init_player_details2 = __esm({
|
|
|
4444
4782
|
"src/cli/commands/player-details.ts"() {
|
|
4445
4783
|
"use strict";
|
|
4446
4784
|
init_index();
|
|
4785
|
+
init_flags();
|
|
4447
4786
|
init_formatters();
|
|
4787
|
+
init_resolvers();
|
|
4448
4788
|
init_ui();
|
|
4449
4789
|
init_validation2();
|
|
4450
4790
|
DEFAULT_COLUMNS9 = [
|
|
@@ -4462,37 +4802,31 @@ var init_player_details2 = __esm({
|
|
|
4462
4802
|
description: "Fetch player biographical details for a team"
|
|
4463
4803
|
},
|
|
4464
4804
|
args: {
|
|
4465
|
-
|
|
4805
|
+
...REQUIRED_TEAM_FLAG,
|
|
4466
4806
|
source: {
|
|
4467
4807
|
type: "string",
|
|
4468
4808
|
description: "Data source: afl-api, footywire, afl-tables",
|
|
4469
4809
|
default: "afl-api"
|
|
4470
4810
|
},
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
description: "Competition code (AFLM or AFLW)",
|
|
4475
|
-
default: "AFLM"
|
|
4476
|
-
},
|
|
4477
|
-
json: { type: "boolean", description: "Output as JSON" },
|
|
4478
|
-
csv: { type: "boolean", description: "Output as CSV" },
|
|
4479
|
-
format: { type: "string", description: "Output format: table, json, csv" },
|
|
4480
|
-
full: { type: "boolean", description: "Show all columns in table output" }
|
|
4811
|
+
...OPTIONAL_SEASON_FLAG,
|
|
4812
|
+
...COMPETITION_FLAG,
|
|
4813
|
+
...OUTPUT_FLAGS
|
|
4481
4814
|
},
|
|
4482
4815
|
async run({ args }) {
|
|
4483
4816
|
const source = validateSource(args.source);
|
|
4484
4817
|
const competition = validateCompetition(args.competition);
|
|
4485
4818
|
const format = validateFormat(args.format);
|
|
4486
4819
|
const season = validateOptionalSeason(args.season) ?? resolveDefaultSeason(competition);
|
|
4820
|
+
const team = await resolveTeamNameOrPrompt(args.team);
|
|
4487
4821
|
const result = await withSpinner(
|
|
4488
4822
|
"Fetching player details\u2026",
|
|
4489
|
-
() => fetchPlayerDetails({ source, team
|
|
4823
|
+
() => fetchPlayerDetails({ source, team, season, competition })
|
|
4490
4824
|
);
|
|
4491
4825
|
if (!result.success) {
|
|
4492
4826
|
throw result.error;
|
|
4493
4827
|
}
|
|
4494
4828
|
const data = result.data;
|
|
4495
|
-
showSummary(`Loaded ${data.length} players for ${
|
|
4829
|
+
showSummary(`Loaded ${data.length} players for ${team} (${source})`);
|
|
4496
4830
|
const formatOptions = {
|
|
4497
4831
|
json: args.json,
|
|
4498
4832
|
csv: args.csv,
|
|
@@ -4517,7 +4851,9 @@ var init_coaches_votes2 = __esm({
|
|
|
4517
4851
|
"src/cli/commands/coaches-votes.ts"() {
|
|
4518
4852
|
"use strict";
|
|
4519
4853
|
init_index();
|
|
4854
|
+
init_flags();
|
|
4520
4855
|
init_formatters();
|
|
4856
|
+
init_resolvers();
|
|
4521
4857
|
init_ui();
|
|
4522
4858
|
init_validation2();
|
|
4523
4859
|
DEFAULT_COLUMNS10 = [
|
|
@@ -4534,33 +4870,27 @@ var init_coaches_votes2 = __esm({
|
|
|
4534
4870
|
description: "Fetch AFLCA coaches votes for a season"
|
|
4535
4871
|
},
|
|
4536
4872
|
args: {
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
default: "AFLM"
|
|
4543
|
-
},
|
|
4544
|
-
team: { type: "string", description: "Filter by team name" },
|
|
4545
|
-
json: { type: "boolean", description: "Output as JSON" },
|
|
4546
|
-
csv: { type: "boolean", description: "Output as CSV" },
|
|
4547
|
-
format: { type: "string", description: "Output format: table, json, csv" },
|
|
4548
|
-
full: { type: "boolean", description: "Show all columns in table output" }
|
|
4873
|
+
...SEASON_FLAG,
|
|
4874
|
+
...ROUND_FLAG,
|
|
4875
|
+
...COMPETITION_FLAG,
|
|
4876
|
+
...TEAM_FLAG,
|
|
4877
|
+
...OUTPUT_FLAGS
|
|
4549
4878
|
},
|
|
4550
4879
|
async run({ args }) {
|
|
4551
4880
|
const season = validateSeason(args.season);
|
|
4552
4881
|
const round = args.round ? validateRound(args.round) : void 0;
|
|
4553
4882
|
const competition = validateCompetition(args.competition);
|
|
4554
4883
|
const format = validateFormat(args.format);
|
|
4884
|
+
const team = args.team ? await resolveTeamNameOrPrompt(args.team) : void 0;
|
|
4555
4885
|
const result = await withSpinner(
|
|
4556
4886
|
"Fetching coaches votes\u2026",
|
|
4557
|
-
() => fetchCoachesVotes({ season, round, competition, team
|
|
4887
|
+
() => fetchCoachesVotes({ season, round, competition, team })
|
|
4558
4888
|
);
|
|
4559
4889
|
if (!result.success) {
|
|
4560
4890
|
throw result.error;
|
|
4561
4891
|
}
|
|
4562
4892
|
const data = result.data;
|
|
4563
|
-
const teamSuffix =
|
|
4893
|
+
const teamSuffix = team ? ` for ${team}` : "";
|
|
4564
4894
|
const roundSuffix = round ? ` round ${round}` : "";
|
|
4565
4895
|
showSummary(`Loaded ${data.length} vote records for ${season}${roundSuffix}${teamSuffix}`);
|
|
4566
4896
|
const formatOptions = {
|
|
@@ -4609,7 +4939,7 @@ ${issueLines.join("\n")}`;
|
|
|
4609
4939
|
var main = defineCommand11({
|
|
4610
4940
|
meta: {
|
|
4611
4941
|
name: "fitzroy",
|
|
4612
|
-
version: "1.
|
|
4942
|
+
version: "1.2.0",
|
|
4613
4943
|
description: "CLI for fetching AFL data \u2014 match results, player stats, fixtures, ladders, and more"
|
|
4614
4944
|
},
|
|
4615
4945
|
subCommands: {
|
package/dist/index.d.ts
CHANGED
|
@@ -1777,13 +1777,18 @@ declare class AflApiClient {
|
|
|
1777
1777
|
private readonly fetchFn;
|
|
1778
1778
|
private readonly tokenUrl;
|
|
1779
1779
|
private cachedToken;
|
|
1780
|
+
private pendingAuth;
|
|
1780
1781
|
constructor(options?: AflApiClientOptions);
|
|
1781
1782
|
/**
|
|
1782
1783
|
* Authenticate with the WMCTok token endpoint and cache the token.
|
|
1783
1784
|
*
|
|
1785
|
+
* Concurrent callers share the same in-flight request to avoid
|
|
1786
|
+
* redundant token fetches (thundering herd prevention).
|
|
1787
|
+
*
|
|
1784
1788
|
* @returns The access token on success, or an error Result.
|
|
1785
1789
|
*/
|
|
1786
1790
|
authenticate(): Promise<Result<string, AflApiError>>;
|
|
1791
|
+
private doAuthenticate;
|
|
1787
1792
|
/**
|
|
1788
1793
|
* Whether the cached token is still valid (not expired).
|
|
1789
1794
|
*/
|
package/dist/index.js
CHANGED
|
@@ -1407,6 +1407,22 @@ async function fetchCoachesVotes(query) {
|
|
|
1407
1407
|
return ok(votes);
|
|
1408
1408
|
}
|
|
1409
1409
|
|
|
1410
|
+
// src/lib/concurrency.ts
|
|
1411
|
+
async function batchedMap(items, fn, options) {
|
|
1412
|
+
const batchSize = options?.batchSize ?? 5;
|
|
1413
|
+
const delayMs = options?.delayMs ?? 0;
|
|
1414
|
+
const results = [];
|
|
1415
|
+
for (let i = 0; i < items.length; i += batchSize) {
|
|
1416
|
+
const batch = items.slice(i, i + batchSize);
|
|
1417
|
+
const batchResults = await Promise.all(batch.map(fn));
|
|
1418
|
+
results.push(...batchResults);
|
|
1419
|
+
if (delayMs > 0 && i + batchSize < items.length) {
|
|
1420
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
return results;
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1410
1426
|
// src/lib/validation.ts
|
|
1411
1427
|
import { z } from "zod/v4";
|
|
1412
1428
|
var AflApiTokenSchema = z.object({
|
|
@@ -1692,6 +1708,7 @@ var AflApiClient = class {
|
|
|
1692
1708
|
fetchFn;
|
|
1693
1709
|
tokenUrl;
|
|
1694
1710
|
cachedToken = null;
|
|
1711
|
+
pendingAuth = null;
|
|
1695
1712
|
constructor(options) {
|
|
1696
1713
|
this.fetchFn = options?.fetchFn ?? globalThis.fetch;
|
|
1697
1714
|
this.tokenUrl = options?.tokenUrl ?? TOKEN_URL;
|
|
@@ -1699,9 +1716,21 @@ var AflApiClient = class {
|
|
|
1699
1716
|
/**
|
|
1700
1717
|
* Authenticate with the WMCTok token endpoint and cache the token.
|
|
1701
1718
|
*
|
|
1719
|
+
* Concurrent callers share the same in-flight request to avoid
|
|
1720
|
+
* redundant token fetches (thundering herd prevention).
|
|
1721
|
+
*
|
|
1702
1722
|
* @returns The access token on success, or an error Result.
|
|
1703
1723
|
*/
|
|
1704
1724
|
async authenticate() {
|
|
1725
|
+
if (this.pendingAuth) {
|
|
1726
|
+
return this.pendingAuth;
|
|
1727
|
+
}
|
|
1728
|
+
this.pendingAuth = this.doAuthenticate().finally(() => {
|
|
1729
|
+
this.pendingAuth = null;
|
|
1730
|
+
});
|
|
1731
|
+
return this.pendingAuth;
|
|
1732
|
+
}
|
|
1733
|
+
async doAuthenticate() {
|
|
1705
1734
|
try {
|
|
1706
1735
|
const response = await this.fetchFn(this.tokenUrl, {
|
|
1707
1736
|
method: "POST",
|
|
@@ -1956,7 +1985,7 @@ var AflApiClient = class {
|
|
|
1956
1985
|
return roundsResult;
|
|
1957
1986
|
}
|
|
1958
1987
|
const providerIds = roundsResult.data.flatMap((r) => r.providerId ? [r.providerId] : []);
|
|
1959
|
-
const results = await
|
|
1988
|
+
const results = await batchedMap(providerIds, (id) => this.fetchRoundMatchItems(id));
|
|
1960
1989
|
const allItems = [];
|
|
1961
1990
|
for (const result of results) {
|
|
1962
1991
|
if (!result.success) {
|
|
@@ -2290,8 +2319,9 @@ async function fetchFixture(query) {
|
|
|
2290
2319
|
const roundProviderIds = roundsResult.data.flatMap(
|
|
2291
2320
|
(r) => r.providerId ? [{ providerId: r.providerId, roundNumber: r.roundNumber }] : []
|
|
2292
2321
|
);
|
|
2293
|
-
const roundResults = await
|
|
2294
|
-
roundProviderIds
|
|
2322
|
+
const roundResults = await batchedMap(
|
|
2323
|
+
roundProviderIds,
|
|
2324
|
+
(r) => client.fetchRoundMatchItems(r.providerId)
|
|
2295
2325
|
);
|
|
2296
2326
|
const fixtures = [];
|
|
2297
2327
|
for (let i = 0; i < roundResults.length; i++) {
|
|
@@ -3042,8 +3072,9 @@ async function fetchLineup(query) {
|
|
|
3042
3072
|
if (matchItems.data.length === 0) {
|
|
3043
3073
|
return err(new AflApiError(`No matches found for round ${query.round}`));
|
|
3044
3074
|
}
|
|
3045
|
-
const rosterResults = await
|
|
3046
|
-
matchItems.data
|
|
3075
|
+
const rosterResults = await batchedMap(
|
|
3076
|
+
matchItems.data,
|
|
3077
|
+
(item) => client.fetchMatchRoster(item.match.matchId)
|
|
3047
3078
|
);
|
|
3048
3079
|
const lineups = [];
|
|
3049
3080
|
for (const rosterResult of rosterResults) {
|
|
@@ -3334,8 +3365,9 @@ async function fetchPlayerStats(query) {
|
|
|
3334
3365
|
teamIdMap.set(item.match.homeTeamId, item.match.homeTeam.name);
|
|
3335
3366
|
teamIdMap.set(item.match.awayTeamId, item.match.awayTeam.name);
|
|
3336
3367
|
}
|
|
3337
|
-
const statsResults = await
|
|
3338
|
-
matchItemsResult.data
|
|
3368
|
+
const statsResults = await batchedMap(
|
|
3369
|
+
matchItemsResult.data,
|
|
3370
|
+
(item) => client.fetchPlayerStats(item.match.matchId)
|
|
3339
3371
|
);
|
|
3340
3372
|
const allStats = [];
|
|
3341
3373
|
for (let i = 0; i < statsResults.length; i++) {
|