@sideboard-ai/core 0.1.45 → 0.1.49
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/agents/cursor-runner.cjs +11 -4
- package/dist/agents/cursor-runner.js +11 -4
- package/dist/{agents-JSHCAZUZ.js → agents-3P4N6KHC.js} +12 -2
- package/dist/agents-LUB3Q773.js +82 -0
- package/dist/app-settings-7XVDQJ7F.js +80 -0
- package/dist/{app-settings-LYGVDGZY.js → app-settings-LZP632KI.js} +1 -1
- package/dist/{chunk-U3EQKJHA.js → chunk-3NAS4MWH.js} +1 -1
- package/dist/{chunk-ZNSM2DDD.js → chunk-5WYEJ3F3.js} +6 -2
- package/dist/chunk-7MV3RXSC.js +277 -0
- package/dist/chunk-DF5VQQKA.js +191 -0
- package/dist/chunk-E35APBHV.js +269 -0
- package/dist/chunk-EOYDCKQC.js +172 -0
- package/dist/chunk-GI7E5733.js +30 -0
- package/dist/chunk-GZXBYHJC.js +148 -0
- package/dist/chunk-HBJSHRY2.js +494 -0
- package/dist/chunk-ISY4EGF6.js +77 -0
- package/dist/chunk-KGZBYWZ3.js +23 -0
- package/dist/chunk-RQI4TOXK.js +2232 -0
- package/dist/chunk-S2LL5HA4.js +33 -0
- package/dist/{chunk-ANZ566Z5.js → chunk-SS34TVSY.js} +66 -7
- package/dist/{chunk-WYY3J7GR.js → chunk-T5QQVXK3.js} +6 -3
- package/dist/chunk-V4JRXYYU.js +122 -0
- package/dist/{chunk-6NAPN2N5.js → chunk-ZQCQIWIP.js} +1 -1
- package/dist/chunk-ZVV5EEQN.js +1892 -0
- package/dist/connected-teams-UBXHZGO4.js +24 -0
- package/dist/{coordinator-prompt-2XFYG3C5.js → coordinator-prompt-SPGDT7J5.js} +1 -1
- package/dist/coordinator-prompt-VG4BZ5JL.js +25 -0
- package/dist/cursor-recover-NNK7JPQM.js +44 -0
- package/dist/{global-workspace-YFOQUGWD.js → global-workspace-KSR3E63K.js} +3 -2
- package/dist/global-workspace-OJF4ENH4.js +40 -0
- package/dist/index.cjs +377 -30
- package/dist/index.d.cts +70 -11
- package/dist/index.d.ts +70 -11
- package/dist/index.js +5779 -193
- package/dist/mcp/run-stdio.cjs +331 -32
- package/dist/mcp/run-stdio.js +5618 -14
- package/dist/paths-2OFB7UJG.js +28 -0
- package/dist/run-HMRSRG3U.js +12 -0
- package/dist/thread-store-XICUWFNM.js +32 -0
- package/dist/title-4WAKWZRF.js +27 -0
- package/dist/workspaces-OZE7LRDO.js +24 -0
- package/dist/{workspaces-TIKLNDW3.js → workspaces-UJX7JIGH.js} +4 -3
- package/dist/worktree-XG5PCLZY.js +94 -0
- package/package.json +2 -2
- package/dist/chunk-I6QGZOOS.js +0 -5667
|
@@ -0,0 +1,1892 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
listThreads
|
|
5
|
+
} from "./chunk-EOYDCKQC.js";
|
|
6
|
+
import {
|
|
7
|
+
gh,
|
|
8
|
+
git
|
|
9
|
+
} from "./chunk-ISY4EGF6.js";
|
|
10
|
+
import {
|
|
11
|
+
worktreesRoot
|
|
12
|
+
} from "./chunk-7MV3RXSC.js";
|
|
13
|
+
|
|
14
|
+
// src/git/worktree.ts
|
|
15
|
+
import { existsSync, readdirSync } from "fs";
|
|
16
|
+
import { join } from "path";
|
|
17
|
+
|
|
18
|
+
// src/git/team-meta.ts
|
|
19
|
+
var SOCCER_TEAM_META = {
|
|
20
|
+
"aalborg": { location: "Aalborg, Denmark", league: "Danish Superliga" },
|
|
21
|
+
"aalesund": { location: "\xC5lesund, Norway", league: "Norwegian First Division" },
|
|
22
|
+
"aarhus": { location: "Aarhus, Denmark", league: "Danish Superliga" },
|
|
23
|
+
"aberdeen": { location: "Aberdeen, Scotland", league: "Scottish Premiership" },
|
|
24
|
+
"ac-milan": { location: "Milan, Italy", league: "Serie A" },
|
|
25
|
+
"ado-den-haag": { location: "The Hague, Netherlands", league: "Eerste Divisie" },
|
|
26
|
+
"aik": { location: "Stockholm, Sweden", league: "Allsvenskan" },
|
|
27
|
+
"ajaccio": { location: "Ajaccio, France", league: "Ligue 2" },
|
|
28
|
+
"ajax": { location: "Amsterdam, Netherlands", league: "Eredivisie" },
|
|
29
|
+
"al-ahly": { location: "Cairo, Egypt", league: "Egyptian Premier League" },
|
|
30
|
+
"al-hilal": { location: "Riyadh, Saudi Arabia", league: "Saudi Pro League" },
|
|
31
|
+
"alajuelense": { location: "Alajuela, Costa Rica", league: "Liga FPD" },
|
|
32
|
+
"almere-city": { location: "Almere, Netherlands", league: "Eredivisie" },
|
|
33
|
+
"amiens": { location: "Amiens, France", league: "Ligue 2" },
|
|
34
|
+
"angers": { location: "Angers, France", league: "Ligue 1" },
|
|
35
|
+
"anyang": { location: "Anyang, South Korea", league: "K League 1" },
|
|
36
|
+
"argentinos-juniors": { location: "Buenos Aires, Argentina", league: "Argentine Primera Divisi\xF3n" },
|
|
37
|
+
"arsenal": { location: "London, England", league: "Premier League" },
|
|
38
|
+
"aston-villa": { location: "Birmingham, England", league: "Premier League" },
|
|
39
|
+
"athletico-paranaense": { location: "Curitiba, Brazil", league: "Brasileir\xE3o S\xE9rie A" },
|
|
40
|
+
"atlanta-united": { location: "Atlanta, USA", league: "MLS" },
|
|
41
|
+
"atlas": { location: "Guadalajara, Mexico", league: "Liga MX" },
|
|
42
|
+
"atletico-madrid": { location: "Madrid, Spain", league: "La Liga" },
|
|
43
|
+
"atletico-mineiro": { location: "Belo Horizonte, Brazil", league: "Brasileir\xE3o S\xE9rie A" },
|
|
44
|
+
"atletico-san-luis": { location: "San Luis Potos\xED, Mexico", league: "Liga MX" },
|
|
45
|
+
"augsburg": { location: "Augsburg, Germany", league: "Bundesliga" },
|
|
46
|
+
"austin-fc": { location: "Austin, USA", league: "MLS" },
|
|
47
|
+
"auxerre": { location: "Auxerre, France", league: "Ligue 1" },
|
|
48
|
+
"avispa-fukuoka": { location: "Fukuoka, Japan", league: "J1 League" },
|
|
49
|
+
"az": { location: "Alkmaar, Netherlands", league: "Eredivisie" },
|
|
50
|
+
"bahia": { location: "Salvador, Brazil", league: "Brasileir\xE3o S\xE9rie A" },
|
|
51
|
+
"banfield": { location: "Banfield, Argentina", league: "Argentine Primera Divisi\xF3n" },
|
|
52
|
+
"barcelona": { location: "Barcelona, Spain", league: "La Liga" },
|
|
53
|
+
"bayer-leverkusen": { location: "Leverkusen, Germany", league: "Bundesliga" },
|
|
54
|
+
"bayern-munich": { location: "Munich, Germany", league: "Bundesliga" },
|
|
55
|
+
"belgrano": { location: "C\xF3rdoba, Argentina", league: "Argentine Primera Divisi\xF3n" },
|
|
56
|
+
"benfica": { location: "Lisbon, Portugal", league: "Primeira Liga" },
|
|
57
|
+
"birmingham-legion": { location: "Birmingham, USA", league: "USL Championship" },
|
|
58
|
+
"boca-juniors": { location: "Buenos Aires, Argentina", league: "Argentine Primera Divisi\xF3n" },
|
|
59
|
+
"bodo-glimt": { location: "Bod\xF8, Norway", league: "Eliteserien" },
|
|
60
|
+
"bordeaux": { location: "Bordeaux, France", league: "Championnat National" },
|
|
61
|
+
"borussia-dortmund": { location: "Dortmund, Germany", league: "Bundesliga" },
|
|
62
|
+
"botafogo": { location: "Rio de Janeiro, Brazil", league: "Brasileir\xE3o S\xE9rie A" },
|
|
63
|
+
"bragantino": { location: "Bragan\xE7a Paulista, Brazil", league: "Brasileir\xE3o S\xE9rie A" },
|
|
64
|
+
"brann": { location: "Bergen, Norway", league: "Eliteserien" },
|
|
65
|
+
"brest": { location: "Brest, France", league: "Ligue 1" },
|
|
66
|
+
"brommapojkarna": { location: "Stockholm, Sweden", league: "Allsvenskan" },
|
|
67
|
+
"brondby": { location: "Br\xF8ndby, Denmark", league: "Danish Superliga" },
|
|
68
|
+
"brooklyn-fc": { location: "Brooklyn, USA", league: "USL Championship" },
|
|
69
|
+
"bucheon": { location: "Bucheon, South Korea", league: "K League 2" },
|
|
70
|
+
"caen": { location: "Caen, France", league: "Ligue 2" },
|
|
71
|
+
"cambuur": { location: "Leeuwarden, Netherlands", league: "Eerste Divisie" },
|
|
72
|
+
"carmelita": { location: "Alajuela, Costa Rica", league: "Liga de Ascenso" },
|
|
73
|
+
"cartagines": { location: "Cartago, Costa Rica", league: "Liga FPD" },
|
|
74
|
+
"ceara": { location: "Fortaleza, Brazil", league: "Brasileir\xE3o S\xE9rie A" },
|
|
75
|
+
"celtic": { location: "Glasgow, Scotland", league: "Scottish Premiership" },
|
|
76
|
+
"central-cordoba": { location: "Santiago del Estero, Argentina", league: "Argentine Primera Divisi\xF3n" },
|
|
77
|
+
"cerezo-osaka": { location: "Osaka, Japan", league: "J1 League" },
|
|
78
|
+
"cf-montreal": { location: "Montreal, Canada", league: "MLS" },
|
|
79
|
+
"charleston-battery": { location: "Charleston, USA", league: "USL Championship" },
|
|
80
|
+
"charlotte-fc": { location: "Charlotte, USA", league: "MLS" },
|
|
81
|
+
"chelsea": { location: "London, England", league: "Premier League" },
|
|
82
|
+
"chicago-fire": { location: "Chicago, USA", league: "MLS" },
|
|
83
|
+
"chivas": { location: "Guadalajara, Mexico", league: "Liga MX" },
|
|
84
|
+
"clermont": { location: "Clermont-Ferrand, France", league: "Ligue 2" },
|
|
85
|
+
"club-america": { location: "Mexico City, Mexico", league: "Liga MX" },
|
|
86
|
+
"cologne": { location: "Cologne, Germany", league: "Bundesliga" },
|
|
87
|
+
"colorado-rapids": { location: "Commerce City, USA", league: "MLS" },
|
|
88
|
+
"columbus-crew": { location: "Columbus, USA", league: "MLS" },
|
|
89
|
+
"corinthians": { location: "S\xE3o Paulo, Brazil", league: "Brasileir\xE3o S\xE9rie A" },
|
|
90
|
+
"cruz-azul": { location: "Mexico City, Mexico", league: "Liga MX" },
|
|
91
|
+
"cruzeiro": { location: "Belo Horizonte, Brazil", league: "Brasileir\xE3o S\xE9rie A" },
|
|
92
|
+
"daejeon": { location: "Daejeon, South Korea", league: "K League 1" },
|
|
93
|
+
"dc-united": { location: "Washington, D.C., USA", league: "MLS" },
|
|
94
|
+
"defensa-y-justicia": { location: "Florencio Varela, Argentina", league: "Argentine Primera Divisi\xF3n" },
|
|
95
|
+
"degerfors": { location: "Degerfors, Sweden", league: "Allsvenskan" },
|
|
96
|
+
"detroit-city": { location: "Detroit, USA", league: "USL Championship" },
|
|
97
|
+
"dijon": { location: "Dijon, France", league: "Championnat National" },
|
|
98
|
+
"djurgarden": { location: "Stockholm, Sweden", league: "Allsvenskan" },
|
|
99
|
+
"dundee": { location: "Dundee, Scotland", league: "Scottish Premiership" },
|
|
100
|
+
"dundee-united": { location: "Dundee, Scotland", league: "Scottish Premiership" },
|
|
101
|
+
"dynamo-kyiv": { location: "Kyiv, Ukraine", league: "Ukrainian Premier League" },
|
|
102
|
+
"eintracht-frankfurt": { location: "Frankfurt, Germany", league: "Bundesliga" },
|
|
103
|
+
"el-paso-locomotive": { location: "El Paso, USA", league: "USL Championship" },
|
|
104
|
+
"elfsborg": { location: "Bor\xE5s, Sweden", league: "Allsvenskan" },
|
|
105
|
+
"emmen": { location: "Emmen, Netherlands", league: "Eerste Divisie" },
|
|
106
|
+
"esbjerg": { location: "Esbjerg, Denmark", league: "Danish 1st Division" },
|
|
107
|
+
"estudiantes": { location: "La Plata, Argentina", league: "Argentine Primera Divisi\xF3n" },
|
|
108
|
+
"everton": { location: "Liverpool, England", league: "Premier League" },
|
|
109
|
+
"excelsior": { location: "Rotterdam, Netherlands", league: "Eerste Divisie" },
|
|
110
|
+
"fagiano-okayama": { location: "Okayama, Japan", league: "J1 League" },
|
|
111
|
+
"falkirk": { location: "Falkirk, Scotland", league: "Scottish Championship" },
|
|
112
|
+
"fc-cincinnati": { location: "Cincinnati, USA", league: "MLS" },
|
|
113
|
+
"fc-copenhagen": { location: "Copenhagen, Denmark", league: "Danish Superliga" },
|
|
114
|
+
"fc-dallas": { location: "Frisco, USA", league: "MLS" },
|
|
115
|
+
"fc-seoul": { location: "Seoul, South Korea", league: "K League 1" },
|
|
116
|
+
"fc-tokyo": { location: "Tokyo, Japan", league: "J1 League" },
|
|
117
|
+
"fc-tulsa": { location: "Tulsa, USA", league: "USL Championship" },
|
|
118
|
+
"feyenoord": { location: "Rotterdam, Netherlands", league: "Eredivisie" },
|
|
119
|
+
"flamengo": { location: "Rio de Janeiro, Brazil", league: "Brasileir\xE3o S\xE9rie A" },
|
|
120
|
+
"fluminense": { location: "Rio de Janeiro, Brazil", league: "Brasileir\xE3o S\xE9rie A" },
|
|
121
|
+
"fortaleza": { location: "Fortaleza, Brazil", league: "Brasileir\xE3o S\xE9rie A" },
|
|
122
|
+
"fortuna-sittard": { location: "Sittard, Netherlands", league: "Eredivisie" },
|
|
123
|
+
"fredrikstad": { location: "Fredrikstad, Norway", league: "Eliteserien" },
|
|
124
|
+
"freiburg": { location: "Freiburg, Germany", league: "Bundesliga" },
|
|
125
|
+
"gais": { location: "Gothenburg, Sweden", league: "Allsvenskan" },
|
|
126
|
+
"galatasaray": { location: "Istanbul, Turkey", league: "S\xFCper Lig" },
|
|
127
|
+
"gamba-osaka": { location: "Osaka, Japan", league: "J1 League" },
|
|
128
|
+
"gangwon": { location: "Gangneung, South Korea", league: "K League 1" },
|
|
129
|
+
"gimcheon-sangmu": { location: "Gimcheon, South Korea", league: "K League 1" },
|
|
130
|
+
"gimnasia": { location: "La Plata, Argentina", league: "Argentine Primera Divisi\xF3n" },
|
|
131
|
+
"gladbach": { location: "M\xF6nchengladbach, Germany", league: "Bundesliga" },
|
|
132
|
+
"go-ahead-eagles": { location: "Deventer, Netherlands", league: "Eredivisie" },
|
|
133
|
+
"godoy-cruz": { location: "Mendoza, Argentina", league: "Argentine Primera Divisi\xF3n" },
|
|
134
|
+
"gremio": { location: "Porto Alegre, Brazil", league: "Brasileir\xE3o S\xE9rie A" },
|
|
135
|
+
"groningen": { location: "Groningen, Netherlands", league: "Eredivisie" },
|
|
136
|
+
"guadalupe": { location: "Goicoechea, Costa Rica", league: "Liga de Ascenso" },
|
|
137
|
+
"guanacasteca": { location: "Nicoya, Costa Rica", league: "Liga FPD" },
|
|
138
|
+
"guingamp": { location: "Guingamp, France", league: "Ligue 2" },
|
|
139
|
+
"gwangju": { location: "Gwangju, South Korea", league: "K League 1" },
|
|
140
|
+
"hacken": { location: "Gothenburg, Sweden", league: "Allsvenskan" },
|
|
141
|
+
"halmstad": { location: "Halmstad, Sweden", league: "Allsvenskan" },
|
|
142
|
+
"hamburg": { location: "Hamburg, Germany", league: "2. Bundesliga" },
|
|
143
|
+
"hammarby": { location: "Stockholm, Sweden", league: "Allsvenskan" },
|
|
144
|
+
"hartford-athletic": { location: "Hartford, USA", league: "USL Championship" },
|
|
145
|
+
"haugesund": { location: "Haugesund, Norway", league: "Eliteserien" },
|
|
146
|
+
"hearts": { location: "Edinburgh, Scotland", league: "Scottish Premiership" },
|
|
147
|
+
"heerenveen": { location: "Heerenveen, Netherlands", league: "Eredivisie" },
|
|
148
|
+
"heidenheim": { location: "Heidenheim, Germany", league: "Bundesliga" },
|
|
149
|
+
"helsingborg": { location: "Helsingborg, Sweden", league: "Superettan" },
|
|
150
|
+
"heracles": { location: "Almelo, Netherlands", league: "Eredivisie" },
|
|
151
|
+
"herediano": { location: "Heredia, Costa Rica", league: "Liga FPD" },
|
|
152
|
+
"hibernian": { location: "Edinburgh, Scotland", league: "Scottish Premiership" },
|
|
153
|
+
"hoffenheim": { location: "Sinsheim, Germany", league: "Bundesliga" },
|
|
154
|
+
"horsens": { location: "Horsens, Denmark", league: "Danish 1st Division" },
|
|
155
|
+
"houston-dynamo": { location: "Houston, USA", league: "MLS" },
|
|
156
|
+
"huracan": { location: "Buenos Aires, Argentina", league: "Argentine Primera Divisi\xF3n" },
|
|
157
|
+
"ifk-goteborg": { location: "Gothenburg, Sweden", league: "Allsvenskan" },
|
|
158
|
+
"norrkoping": { location: "Norrk\xF6ping, Sweden", league: "Allsvenskan" },
|
|
159
|
+
"incheon-united": { location: "Incheon, South Korea", league: "K League 1" },
|
|
160
|
+
"independiente": { location: "Avellaneda, Argentina", league: "Argentine Primera Divisi\xF3n" },
|
|
161
|
+
"indy-eleven": { location: "Indianapolis, USA", league: "USL Championship" },
|
|
162
|
+
"instituto": { location: "C\xF3rdoba, Argentina", league: "Argentine Primera Divisi\xF3n" },
|
|
163
|
+
"inter-miami": { location: "Fort Lauderdale, USA", league: "MLS" },
|
|
164
|
+
"inter-milan": { location: "Milan, Italy", league: "Serie A" },
|
|
165
|
+
"internacional": { location: "Porto Alegre, Brazil", league: "Brasileir\xE3o S\xE9rie A" },
|
|
166
|
+
"jef-united": { location: "Chiba, Japan", league: "J2 League" },
|
|
167
|
+
"jeju-sk": { location: "Jeju, South Korea", league: "K League 1" },
|
|
168
|
+
"jeonbuk": { location: "Jeonju, South Korea", league: "K League 1" },
|
|
169
|
+
"juarez": { location: "Ciudad Ju\xE1rez, Mexico", league: "Liga MX" },
|
|
170
|
+
"juventude": { location: "Caxias do Sul, Brazil", league: "Brasileir\xE3o S\xE9rie A" },
|
|
171
|
+
"juventus": { location: "Turin, Italy", league: "Serie A" },
|
|
172
|
+
"kalmar": { location: "Kalmar, Sweden", league: "Superettan" },
|
|
173
|
+
"kashima-antlers": { location: "Kashima, Japan", league: "J1 League" },
|
|
174
|
+
"kashiwa-reysol": { location: "Kashiwa, Japan", league: "J1 League" },
|
|
175
|
+
"kawasaki-frontale": { location: "Kawasaki, Japan", league: "J1 League" },
|
|
176
|
+
"kfum-oslo": { location: "Oslo, Norway", league: "Eliteserien" },
|
|
177
|
+
"kilmarnock": { location: "Kilmarnock, Scotland", league: "Scottish Premiership" },
|
|
178
|
+
"kristiansund": { location: "Kristiansund, Norway", league: "Eliteserien" },
|
|
179
|
+
"kyoto-sanga": { location: "Kyoto, Japan", league: "J1 League" },
|
|
180
|
+
"la-galaxy": { location: "Carson, USA", league: "MLS" },
|
|
181
|
+
"lafc": { location: "Los Angeles, USA", league: "MLS" },
|
|
182
|
+
"lanus": { location: "Lan\xFAs, Argentina", league: "Argentine Primera Divisi\xF3n" },
|
|
183
|
+
"las-vegas-lights": { location: "Las Vegas, USA", league: "USL Championship" },
|
|
184
|
+
"lazio": { location: "Rome, Italy", league: "Serie A" },
|
|
185
|
+
"le-havre": { location: "Le Havre, France", league: "Ligue 1" },
|
|
186
|
+
"le-mans": { location: "Le Mans, France", league: "Ligue 2" },
|
|
187
|
+
"leeds-united": { location: "Leeds, England", league: "Premier League" },
|
|
188
|
+
"leicester-city": { location: "Leicester, England", league: "Championship" },
|
|
189
|
+
"lens": { location: "Lens, France", league: "Ligue 1" },
|
|
190
|
+
"leon": { location: "Le\xF3n, Mexico", league: "Liga MX" },
|
|
191
|
+
"lexington-sc": { location: "Lexington, USA", league: "USL Championship" },
|
|
192
|
+
"liberia": { location: "Liberia, Costa Rica", league: "Liga FPD" },
|
|
193
|
+
"lille": { location: "Lille, France", league: "Ligue 1" },
|
|
194
|
+
"lillestrom": { location: "Lillestr\xF8m, Norway", league: "Eliteserien" },
|
|
195
|
+
"limon": { location: "Lim\xF3n, Costa Rica", league: "Liga de Ascenso" },
|
|
196
|
+
"liverpool": { location: "Liverpool, England", league: "Premier League" },
|
|
197
|
+
"livingston": { location: "Livingston, Scotland", league: "Scottish Championship" },
|
|
198
|
+
"lorient": { location: "Lorient, France", league: "Ligue 1" },
|
|
199
|
+
"loudoun-united": { location: "Leesburg, USA", league: "USL Championship" },
|
|
200
|
+
"louisville-city": { location: "Louisville, USA", league: "USL Championship" },
|
|
201
|
+
"lyngby": { location: "Kongens Lyngby, Denmark", league: "Danish Superliga" },
|
|
202
|
+
"lyon": { location: "Lyon, France", league: "Ligue 1" },
|
|
203
|
+
"machida-zelvia": { location: "Machida, Japan", league: "J1 League" },
|
|
204
|
+
"mainz": { location: "Mainz, Germany", league: "Bundesliga" },
|
|
205
|
+
"malmo": { location: "Malm\xF6, Sweden", league: "Allsvenskan" },
|
|
206
|
+
"manchester-city": { location: "Manchester, England", league: "Premier League" },
|
|
207
|
+
"manchester-united": { location: "Manchester, England", league: "Premier League" },
|
|
208
|
+
"marseille": { location: "Marseille, France", league: "Ligue 1" },
|
|
209
|
+
"mazatlan": { location: "Mazatl\xE1n, Mexico", league: "Liga MX" },
|
|
210
|
+
"metz": { location: "Metz, France", league: "Ligue 1" },
|
|
211
|
+
"miami-fc": { location: "Miami, USA", league: "USL Championship" },
|
|
212
|
+
"midtjylland": { location: "Herning, Denmark", league: "Danish Superliga" },
|
|
213
|
+
"minnesota-united": { location: "Saint Paul, USA", league: "MLS" },
|
|
214
|
+
"mirassol": { location: "Mirassol, Brazil", league: "Brasileir\xE3o S\xE9rie A" },
|
|
215
|
+
"mito-hollyhock": { location: "Mito, Japan", league: "J2 League" },
|
|
216
|
+
"mjallby": { location: "H\xE4llevik, Sweden", league: "Allsvenskan" },
|
|
217
|
+
"molde": { location: "Molde, Norway", league: "Eliteserien" },
|
|
218
|
+
"monaco": { location: "Monaco", league: "Ligue 1" },
|
|
219
|
+
"monterey-bay": { location: "Monterey, USA", league: "USL Championship" },
|
|
220
|
+
"monterrey": { location: "Monterrey, Mexico", league: "Liga MX" },
|
|
221
|
+
"montpellier": { location: "Montpellier, France", league: "Ligue 2" },
|
|
222
|
+
"motherwell": { location: "Motherwell, Scotland", league: "Scottish Premiership" },
|
|
223
|
+
"grecia": { location: "Grecia, Costa Rica", league: "Liga de Ascenso" },
|
|
224
|
+
"nac-breda": { location: "Breda, Netherlands", league: "Eredivisie" },
|
|
225
|
+
"nagoya-grampus": { location: "Nagoya, Japan", league: "J1 League" },
|
|
226
|
+
"nancy": { location: "Nancy, France", league: "Ligue 2" },
|
|
227
|
+
"nantes": { location: "Nantes, France", league: "Ligue 1" },
|
|
228
|
+
"napoli": { location: "Naples, Italy", league: "Serie A" },
|
|
229
|
+
"nashville-sc": { location: "Nashville, USA", league: "MLS" },
|
|
230
|
+
"nec": { location: "Nijmegen, Netherlands", league: "Eredivisie" },
|
|
231
|
+
"necaxa": { location: "Aguascalientes, Mexico", league: "Liga MX" },
|
|
232
|
+
"new-england": { location: "Foxborough, USA", league: "MLS" },
|
|
233
|
+
"new-mexico-united": { location: "Albuquerque, USA", league: "USL Championship" },
|
|
234
|
+
"newcastle": { location: "Newcastle, England", league: "Premier League" },
|
|
235
|
+
"newells": { location: "Rosario, Argentina", league: "Argentine Primera Divisi\xF3n" },
|
|
236
|
+
"nice": { location: "Nice, France", league: "Ligue 1" },
|
|
237
|
+
"nimes": { location: "N\xEEmes, France", league: "Championnat National" },
|
|
238
|
+
"nordsjaelland": { location: "Farum, Denmark", league: "Danish Superliga" },
|
|
239
|
+
"ny-red-bulls": { location: "Harrison, USA", league: "MLS" },
|
|
240
|
+
"nycfc": { location: "New York City, USA", league: "MLS" },
|
|
241
|
+
"oakland-roots": { location: "Oakland, USA", league: "USL Championship" },
|
|
242
|
+
"ob-odense": { location: "Odense, Denmark", league: "Danish Superliga" },
|
|
243
|
+
"odd": { location: "Skien, Norway", league: "Norwegian First Division" },
|
|
244
|
+
"olympiacos": { location: "Piraeus, Greece", league: "Super League Greece" },
|
|
245
|
+
"orange-county-sc": { location: "Irvine, USA", league: "USL Championship" },
|
|
246
|
+
"orebro": { location: "\xD6rebro, Sweden", league: "Superettan" },
|
|
247
|
+
"orlando-city": { location: "Orlando, USA", league: "MLS" },
|
|
248
|
+
"pachuca": { location: "Pachuca, Mexico", league: "Liga MX" },
|
|
249
|
+
"palmeiras": { location: "S\xE3o Paulo, Brazil", league: "Brasileir\xE3o S\xE9rie A" },
|
|
250
|
+
"paris-fc": { location: "Paris, France", league: "Ligue 1" },
|
|
251
|
+
"psg": { location: "Paris, France", league: "Ligue 1" },
|
|
252
|
+
"pec-zwolle": { location: "Zwolle, Netherlands", league: "Eredivisie" },
|
|
253
|
+
"perez-zeledon": { location: "San Isidro, Costa Rica", league: "Liga FPD" },
|
|
254
|
+
"philadelphia-union": { location: "Chester, USA", league: "MLS" },
|
|
255
|
+
"phoenix-rising": { location: "Phoenix, USA", league: "USL Championship" },
|
|
256
|
+
"pittsburgh-riverhounds": { location: "Pittsburgh, USA", league: "USL Championship" },
|
|
257
|
+
"platense": { location: "Florida, Argentina", league: "Argentine Primera Divisi\xF3n" },
|
|
258
|
+
"pohang-steelers": { location: "Pohang, South Korea", league: "K League 1" },
|
|
259
|
+
"portland-timbers": { location: "Portland, USA", league: "MLS" },
|
|
260
|
+
"porto": { location: "Porto, Portugal", league: "Primeira Liga" },
|
|
261
|
+
"psv": { location: "Eindhoven, Netherlands", league: "Eredivisie" },
|
|
262
|
+
"puebla": { location: "Puebla, Mexico", league: "Liga MX" },
|
|
263
|
+
"pumas": { location: "Mexico City, Mexico", league: "Liga MX" },
|
|
264
|
+
"puntarenas": { location: "Puntarenas, Costa Rica", league: "Liga FPD" },
|
|
265
|
+
"queretaro": { location: "Quer\xE9taro, Mexico", league: "Liga MX" },
|
|
266
|
+
"racing": { location: "Avellaneda, Argentina", league: "Argentine Primera Divisi\xF3n" },
|
|
267
|
+
"randers": { location: "Randers, Denmark", league: "Danish Superliga" },
|
|
268
|
+
"rangers": { location: "Glasgow, Scotland", league: "Scottish Premiership" },
|
|
269
|
+
"rb-leipzig": { location: "Leipzig, Germany", league: "Bundesliga" },
|
|
270
|
+
"real-madrid": { location: "Madrid, Spain", league: "La Liga" },
|
|
271
|
+
"real-salt-lake": { location: "Sandy, USA", league: "MLS" },
|
|
272
|
+
"red-star": { location: "Saint-Ouen, France", league: "Ligue 2" },
|
|
273
|
+
"reims": { location: "Reims, France", league: "Ligue 2" },
|
|
274
|
+
"rennes": { location: "Rennes, France", league: "Ligue 1" },
|
|
275
|
+
"rhode-island-fc": { location: "Pawtucket, USA", league: "USL Championship" },
|
|
276
|
+
"river-plate": { location: "Buenos Aires, Argentina", league: "Argentine Primera Divisi\xF3n" },
|
|
277
|
+
"rkc": { location: "Waalwijk, Netherlands", league: "Eredivisie" },
|
|
278
|
+
"roma": { location: "Rome, Italy", league: "Serie A" },
|
|
279
|
+
"rosario-central": { location: "Rosario, Argentina", league: "Argentine Primera Divisi\xF3n" },
|
|
280
|
+
"rosenborg": { location: "Trondheim, Norway", league: "Eliteserien" },
|
|
281
|
+
"sacramento-republic": { location: "Sacramento, USA", league: "USL Championship" },
|
|
282
|
+
"saint-etienne": { location: "Saint-\xC9tienne, France", league: "Ligue 2" },
|
|
283
|
+
"san-antonio-fc": { location: "San Antonio, USA", league: "USL Championship" },
|
|
284
|
+
"san-carlos": { location: "Quesada, Costa Rica", league: "Liga FPD" },
|
|
285
|
+
"san-diego-fc": { location: "San Diego, USA", league: "MLS" },
|
|
286
|
+
"san-jose-earthquakes": { location: "San Jose, USA", league: "MLS" },
|
|
287
|
+
"san-lorenzo": { location: "Buenos Aires, Argentina", league: "Argentine Primera Divisi\xF3n" },
|
|
288
|
+
"sanfrecce-hiroshima": { location: "Hiroshima, Japan", league: "J1 League" },
|
|
289
|
+
"santos": { location: "Santos, Brazil", league: "Brasileir\xE3o S\xE9rie A" },
|
|
290
|
+
"santos-guapiles": { location: "Gu\xE1piles, Costa Rica", league: "Liga FPD" },
|
|
291
|
+
"santos-laguna": { location: "Torre\xF3n, Mexico", league: "Liga MX" },
|
|
292
|
+
"sao-paulo": { location: "S\xE3o Paulo, Brazil", league: "Brasileir\xE3o S\xE9rie A" },
|
|
293
|
+
"saprissa": { location: "San Jos\xE9, Costa Rica", league: "Liga FPD" },
|
|
294
|
+
"sarpsborg": { location: "Sarpsborg, Norway", league: "Eliteserien" },
|
|
295
|
+
"schalke": { location: "Gelsenkirchen, Germany", league: "2. Bundesliga" },
|
|
296
|
+
"seattle-sounders": { location: "Seattle, USA", league: "MLS" },
|
|
297
|
+
"sevilla": { location: "Seville, Spain", league: "La Liga" },
|
|
298
|
+
"shakhtar": { location: "Donetsk, Ukraine", league: "Ukrainian Premier League" },
|
|
299
|
+
"shimizu-s-pulse": { location: "Shizuoka, Japan", league: "J1 League" },
|
|
300
|
+
"silkeborg": { location: "Silkeborg, Denmark", league: "Danish Superliga" },
|
|
301
|
+
"sirius": { location: "Uppsala, Sweden", league: "Allsvenskan" },
|
|
302
|
+
"sochaux": { location: "Montb\xE9liard, France", league: "Championnat National" },
|
|
303
|
+
"sonderjyske": { location: "Haderslev, Denmark", league: "Danish Superliga" },
|
|
304
|
+
"sparta-rotterdam": { location: "Rotterdam, Netherlands", league: "Eredivisie" },
|
|
305
|
+
"sport-recife": { location: "Recife, Brazil", league: "Brasileir\xE3o S\xE9rie A" },
|
|
306
|
+
"sporting": { location: "Lisbon, Portugal", league: "Primeira Liga" },
|
|
307
|
+
"sporting-jax": { location: "Jacksonville, USA", league: "MLS Next Pro" },
|
|
308
|
+
"sporting-kc": { location: "Kansas City, USA", league: "MLS" },
|
|
309
|
+
"sporting-san-jose": { location: "San Jos\xE9, Costa Rica", league: "Liga FPD" },
|
|
310
|
+
"st-mirren": { location: "Paisley, Scotland", league: "Scottish Premiership" },
|
|
311
|
+
"st-louis-city": { location: "St. Louis, USA", league: "MLS" },
|
|
312
|
+
"st-pauli": { location: "Hamburg, Germany", league: "Bundesliga" },
|
|
313
|
+
"stabaek": { location: "B\xE6rum, Norway", league: "Norwegian First Division" },
|
|
314
|
+
"strasbourg": { location: "Strasbourg, France", league: "Ligue 1" },
|
|
315
|
+
"stromsgodset": { location: "Drammen, Norway", league: "Eliteserien" },
|
|
316
|
+
"stuttgart": { location: "Stuttgart, Germany", league: "Bundesliga" },
|
|
317
|
+
"switchbacks": { location: "Colorado Springs, USA", league: "USL Championship" },
|
|
318
|
+
"talleres": { location: "C\xF3rdoba, Argentina", league: "Argentine Primera Divisi\xF3n" },
|
|
319
|
+
"tampa-bay-rowdies": { location: "St. Petersburg, USA", league: "USL Championship" },
|
|
320
|
+
"tigre": { location: "Victoria, Argentina", league: "Argentine Primera Divisi\xF3n" },
|
|
321
|
+
"tigres": { location: "San Nicol\xE1s de los Garza, Mexico", league: "Liga MX" },
|
|
322
|
+
"tijuana": { location: "Tijuana, Mexico", league: "Liga MX" },
|
|
323
|
+
"tokyo-verdy": { location: "Tokyo, Japan", league: "J1 League" },
|
|
324
|
+
"toluca": { location: "Toluca, Mexico", league: "Liga MX" },
|
|
325
|
+
"toronto-fc": { location: "Toronto, Canada", league: "MLS" },
|
|
326
|
+
"tottenham": { location: "London, England", league: "Premier League" },
|
|
327
|
+
"toulouse": { location: "Toulouse, France", league: "Ligue 1" },
|
|
328
|
+
"tromso": { location: "Troms\xF8, Norway", league: "Eliteserien" },
|
|
329
|
+
"troyes": { location: "Troyes, France", league: "Ligue 2" },
|
|
330
|
+
"twente": { location: "Enschede, Netherlands", league: "Eredivisie" },
|
|
331
|
+
"ulsan-hd": { location: "Ulsan, South Korea", league: "K League 1" },
|
|
332
|
+
"union": { location: "Santa Fe, Argentina", league: "Argentine Primera Divisi\xF3n" },
|
|
333
|
+
"union-berlin": { location: "Berlin, Germany", league: "Bundesliga" },
|
|
334
|
+
"urawa-reds": { location: "Saitama, Japan", league: "J1 League" },
|
|
335
|
+
"utrecht": { location: "Utrecht, Netherlands", league: "Eredivisie" },
|
|
336
|
+
"v-varen-nagasaki": { location: "Nagasaki, Japan", league: "J2 League" },
|
|
337
|
+
"valencia": { location: "Valencia, Spain", league: "La Liga" },
|
|
338
|
+
"valerenga": { location: "Oslo, Norway", league: "Eliteserien" },
|
|
339
|
+
"vancouver-whitecaps": { location: "Vancouver, Canada", league: "MLS" },
|
|
340
|
+
"varnamo": { location: "V\xE4rnamo, Sweden", league: "Allsvenskan" },
|
|
341
|
+
"vasco": { location: "Rio de Janeiro, Brazil", league: "Brasileir\xE3o S\xE9rie A" },
|
|
342
|
+
"vejle": { location: "Vejle, Denmark", league: "Danish Superliga" },
|
|
343
|
+
"velez": { location: "Buenos Aires, Argentina", league: "Argentine Primera Divisi\xF3n" },
|
|
344
|
+
"viborg": { location: "Viborg, Denmark", league: "Danish Superliga" },
|
|
345
|
+
"viking": { location: "Stavanger, Norway", league: "Eliteserien" },
|
|
346
|
+
"vissel-kobe": { location: "Kobe, Japan", league: "J1 League" },
|
|
347
|
+
"vitesse": { location: "Arnhem, Netherlands", league: "Eerste Divisie" },
|
|
348
|
+
"vitoria": { location: "Salvador, Brazil", league: "Brasileir\xE3o S\xE9rie A" },
|
|
349
|
+
"volendam": { location: "Volendam, Netherlands", league: "Eredivisie" },
|
|
350
|
+
"werder-bremen": { location: "Bremen, Germany", league: "Bundesliga" },
|
|
351
|
+
"west-ham": { location: "London, England", league: "Premier League" },
|
|
352
|
+
"willem-ii": { location: "Tilburg, Netherlands", league: "Eerste Divisie" },
|
|
353
|
+
"wolfsburg": { location: "Wolfsburg, Germany", league: "Bundesliga" },
|
|
354
|
+
"yokohama-marinos": { location: "Yokohama, Japan", league: "J1 League" }
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
// src/git/teams.ts
|
|
358
|
+
function withMeta(seed) {
|
|
359
|
+
const baseSlug = seed.slug.replace(/-\d+$/, "");
|
|
360
|
+
const meta = SOCCER_TEAM_META[baseSlug];
|
|
361
|
+
return {
|
|
362
|
+
name: seed.name,
|
|
363
|
+
slug: seed.slug,
|
|
364
|
+
location: meta?.location ?? "Unknown",
|
|
365
|
+
league: meta?.league ?? "Unknown league"
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
var FAMOUS_SOCCER_TEAM_SEEDS = [
|
|
369
|
+
{ name: "Aalborg", slug: "aalborg" },
|
|
370
|
+
{ name: "Aalesund", slug: "aalesund" },
|
|
371
|
+
{ name: "Aarhus", slug: "aarhus" },
|
|
372
|
+
{ name: "Aberdeen", slug: "aberdeen" },
|
|
373
|
+
{ name: "AC Milan", slug: "ac-milan" },
|
|
374
|
+
{ name: "ADO Den Haag", slug: "ado-den-haag" },
|
|
375
|
+
{ name: "AIK", slug: "aik" },
|
|
376
|
+
{ name: "Ajaccio", slug: "ajaccio" },
|
|
377
|
+
{ name: "Ajax", slug: "ajax" },
|
|
378
|
+
{ name: "Al Ahly", slug: "al-ahly" },
|
|
379
|
+
{ name: "Al Hilal", slug: "al-hilal" },
|
|
380
|
+
{ name: "Alajuelense", slug: "alajuelense" },
|
|
381
|
+
{ name: "Almere City", slug: "almere-city" },
|
|
382
|
+
{ name: "Amiens", slug: "amiens" },
|
|
383
|
+
{ name: "Angers", slug: "angers" },
|
|
384
|
+
{ name: "Anyang", slug: "anyang" },
|
|
385
|
+
{ name: "Argentinos Juniors", slug: "argentinos-juniors" },
|
|
386
|
+
{ name: "Arsenal", slug: "arsenal" },
|
|
387
|
+
{ name: "Aston Villa", slug: "aston-villa" },
|
|
388
|
+
{ name: "Athletico Paranaense", slug: "athletico-paranaense" },
|
|
389
|
+
{ name: "Atlanta United", slug: "atlanta-united" },
|
|
390
|
+
{ name: "Atlas", slug: "atlas" },
|
|
391
|
+
{ name: "Atl\xE9tico Madrid", slug: "atletico-madrid" },
|
|
392
|
+
{ name: "Atl\xE9tico Mineiro", slug: "atletico-mineiro" },
|
|
393
|
+
{ name: "Atl\xE9tico San Luis", slug: "atletico-san-luis" },
|
|
394
|
+
{ name: "Augsburg", slug: "augsburg" },
|
|
395
|
+
{ name: "Austin FC", slug: "austin-fc" },
|
|
396
|
+
{ name: "Auxerre", slug: "auxerre" },
|
|
397
|
+
{ name: "Avispa Fukuoka", slug: "avispa-fukuoka" },
|
|
398
|
+
{ name: "AZ Alkmaar", slug: "az" },
|
|
399
|
+
{ name: "Bahia", slug: "bahia" },
|
|
400
|
+
{ name: "Banfield", slug: "banfield" },
|
|
401
|
+
{ name: "Barcelona", slug: "barcelona" },
|
|
402
|
+
{ name: "Bayer Leverkusen", slug: "bayer-leverkusen" },
|
|
403
|
+
{ name: "Bayern Munich", slug: "bayern-munich" },
|
|
404
|
+
{ name: "Belgrano", slug: "belgrano" },
|
|
405
|
+
{ name: "Benfica", slug: "benfica" },
|
|
406
|
+
{ name: "Birmingham Legion", slug: "birmingham-legion" },
|
|
407
|
+
{ name: "Boca Juniors", slug: "boca-juniors" },
|
|
408
|
+
{ name: "Bod\xF8/Glimt", slug: "bodo-glimt" },
|
|
409
|
+
{ name: "Bordeaux", slug: "bordeaux" },
|
|
410
|
+
{ name: "Borussia Dortmund", slug: "borussia-dortmund" },
|
|
411
|
+
{ name: "Botafogo", slug: "botafogo" },
|
|
412
|
+
{ name: "Bragantino", slug: "bragantino" },
|
|
413
|
+
{ name: "Brann", slug: "brann" },
|
|
414
|
+
{ name: "Brest", slug: "brest" },
|
|
415
|
+
{ name: "Brommapojkarna", slug: "brommapojkarna" },
|
|
416
|
+
{ name: "Br\xF8ndby", slug: "brondby" },
|
|
417
|
+
{ name: "Brooklyn FC", slug: "brooklyn-fc" },
|
|
418
|
+
{ name: "Bucheon", slug: "bucheon" },
|
|
419
|
+
{ name: "Caen", slug: "caen" },
|
|
420
|
+
{ name: "Cambuur", slug: "cambuur" },
|
|
421
|
+
{ name: "Carmelita", slug: "carmelita" },
|
|
422
|
+
{ name: "Cartagin\xE9s", slug: "cartagines" },
|
|
423
|
+
{ name: "Cear\xE1", slug: "ceara" },
|
|
424
|
+
{ name: "Celtic", slug: "celtic" },
|
|
425
|
+
{ name: "Central C\xF3rdoba", slug: "central-cordoba" },
|
|
426
|
+
{ name: "Cerezo Osaka", slug: "cerezo-osaka" },
|
|
427
|
+
{ name: "CF Montr\xE9al", slug: "cf-montreal" },
|
|
428
|
+
{ name: "Charleston Battery", slug: "charleston-battery" },
|
|
429
|
+
{ name: "Charlotte FC", slug: "charlotte-fc" },
|
|
430
|
+
{ name: "Chelsea", slug: "chelsea" },
|
|
431
|
+
{ name: "Chicago Fire", slug: "chicago-fire" },
|
|
432
|
+
{ name: "Chivas", slug: "chivas" },
|
|
433
|
+
{ name: "Clermont", slug: "clermont" },
|
|
434
|
+
{ name: "Club Am\xE9rica", slug: "club-america" },
|
|
435
|
+
{ name: "Cologne", slug: "cologne" },
|
|
436
|
+
{ name: "Colorado Rapids", slug: "colorado-rapids" },
|
|
437
|
+
{ name: "Columbus Crew", slug: "columbus-crew" },
|
|
438
|
+
{ name: "Corinthians", slug: "corinthians" },
|
|
439
|
+
{ name: "Cruz Azul", slug: "cruz-azul" },
|
|
440
|
+
{ name: "Cruzeiro", slug: "cruzeiro" },
|
|
441
|
+
{ name: "Daejeon", slug: "daejeon" },
|
|
442
|
+
{ name: "DC United", slug: "dc-united" },
|
|
443
|
+
{ name: "Defensa y Justicia", slug: "defensa-y-justicia" },
|
|
444
|
+
{ name: "Degerfors", slug: "degerfors" },
|
|
445
|
+
{ name: "Detroit City", slug: "detroit-city" },
|
|
446
|
+
{ name: "Dijon", slug: "dijon" },
|
|
447
|
+
{ name: "Djurg\xE5rden", slug: "djurgarden" },
|
|
448
|
+
{ name: "Dundee", slug: "dundee" },
|
|
449
|
+
{ name: "Dundee United", slug: "dundee-united" },
|
|
450
|
+
{ name: "Dynamo Kyiv", slug: "dynamo-kyiv" },
|
|
451
|
+
{ name: "Eintracht Frankfurt", slug: "eintracht-frankfurt" },
|
|
452
|
+
{ name: "El Paso Locomotive", slug: "el-paso-locomotive" },
|
|
453
|
+
{ name: "Elfsborg", slug: "elfsborg" },
|
|
454
|
+
{ name: "Emmen", slug: "emmen" },
|
|
455
|
+
{ name: "Esbjerg", slug: "esbjerg" },
|
|
456
|
+
{ name: "Estudiantes", slug: "estudiantes" },
|
|
457
|
+
{ name: "Everton", slug: "everton" },
|
|
458
|
+
{ name: "Excelsior", slug: "excelsior" },
|
|
459
|
+
{ name: "Fagiano Okayama", slug: "fagiano-okayama" },
|
|
460
|
+
{ name: "Falkirk", slug: "falkirk" },
|
|
461
|
+
{ name: "FC Cincinnati", slug: "fc-cincinnati" },
|
|
462
|
+
{ name: "FC Copenhagen", slug: "fc-copenhagen" },
|
|
463
|
+
{ name: "FC Dallas", slug: "fc-dallas" },
|
|
464
|
+
{ name: "FC Seoul", slug: "fc-seoul" },
|
|
465
|
+
{ name: "FC Tokyo", slug: "fc-tokyo" },
|
|
466
|
+
{ name: "FC Tulsa", slug: "fc-tulsa" },
|
|
467
|
+
{ name: "Feyenoord", slug: "feyenoord" },
|
|
468
|
+
{ name: "Flamengo", slug: "flamengo" },
|
|
469
|
+
{ name: "Fluminense", slug: "fluminense" },
|
|
470
|
+
{ name: "Fortaleza", slug: "fortaleza" },
|
|
471
|
+
{ name: "Fortuna Sittard", slug: "fortuna-sittard" },
|
|
472
|
+
{ name: "Fredrikstad", slug: "fredrikstad" },
|
|
473
|
+
{ name: "Freiburg", slug: "freiburg" },
|
|
474
|
+
{ name: "GAIS", slug: "gais" },
|
|
475
|
+
{ name: "Galatasaray", slug: "galatasaray" },
|
|
476
|
+
{ name: "Gamba Osaka", slug: "gamba-osaka" },
|
|
477
|
+
{ name: "Gangwon", slug: "gangwon" },
|
|
478
|
+
{ name: "Gimcheon Sangmu", slug: "gimcheon-sangmu" },
|
|
479
|
+
{ name: "Gimnasia", slug: "gimnasia" },
|
|
480
|
+
{ name: "Gladbach", slug: "gladbach" },
|
|
481
|
+
{ name: "Go Ahead Eagles", slug: "go-ahead-eagles" },
|
|
482
|
+
{ name: "Godoy Cruz", slug: "godoy-cruz" },
|
|
483
|
+
{ name: "Gr\xEAmio", slug: "gremio" },
|
|
484
|
+
{ name: "Groningen", slug: "groningen" },
|
|
485
|
+
{ name: "Guadalupe", slug: "guadalupe" },
|
|
486
|
+
{ name: "Guanacasteca", slug: "guanacasteca" },
|
|
487
|
+
{ name: "Guingamp", slug: "guingamp" },
|
|
488
|
+
{ name: "Gwangju", slug: "gwangju" },
|
|
489
|
+
{ name: "H\xE4cken", slug: "hacken" },
|
|
490
|
+
{ name: "Halmstad", slug: "halmstad" },
|
|
491
|
+
{ name: "Hamburg", slug: "hamburg" },
|
|
492
|
+
{ name: "Hammarby", slug: "hammarby" },
|
|
493
|
+
{ name: "Hartford Athletic", slug: "hartford-athletic" },
|
|
494
|
+
{ name: "Haugesund", slug: "haugesund" },
|
|
495
|
+
{ name: "Hearts", slug: "hearts" },
|
|
496
|
+
{ name: "Heerenveen", slug: "heerenveen" },
|
|
497
|
+
{ name: "Heidenheim", slug: "heidenheim" },
|
|
498
|
+
{ name: "Helsingborg", slug: "helsingborg" },
|
|
499
|
+
{ name: "Heracles", slug: "heracles" },
|
|
500
|
+
{ name: "Herediano", slug: "herediano" },
|
|
501
|
+
{ name: "Hibernian", slug: "hibernian" },
|
|
502
|
+
{ name: "Hoffenheim", slug: "hoffenheim" },
|
|
503
|
+
{ name: "Horsens", slug: "horsens" },
|
|
504
|
+
{ name: "Houston Dynamo", slug: "houston-dynamo" },
|
|
505
|
+
{ name: "Hurac\xE1n", slug: "huracan" },
|
|
506
|
+
{ name: "IFK G\xF6teborg", slug: "ifk-goteborg" },
|
|
507
|
+
{ name: "IFK Norrk\xF6ping", slug: "norrkoping" },
|
|
508
|
+
{ name: "Incheon United", slug: "incheon-united" },
|
|
509
|
+
{ name: "Independiente", slug: "independiente" },
|
|
510
|
+
{ name: "Indy Eleven", slug: "indy-eleven" },
|
|
511
|
+
{ name: "Instituto", slug: "instituto" },
|
|
512
|
+
{ name: "Inter Miami", slug: "inter-miami" },
|
|
513
|
+
{ name: "Inter Milan", slug: "inter-milan" },
|
|
514
|
+
{ name: "Internacional", slug: "internacional" },
|
|
515
|
+
{ name: "JEF United", slug: "jef-united" },
|
|
516
|
+
{ name: "Jeju SK", slug: "jeju-sk" },
|
|
517
|
+
{ name: "Jeonbuk", slug: "jeonbuk" },
|
|
518
|
+
{ name: "Ju\xE1rez", slug: "juarez" },
|
|
519
|
+
{ name: "Juventude", slug: "juventude" },
|
|
520
|
+
{ name: "Juventus", slug: "juventus" },
|
|
521
|
+
{ name: "Kalmar FF", slug: "kalmar" },
|
|
522
|
+
{ name: "Kashima Antlers", slug: "kashima-antlers" },
|
|
523
|
+
{ name: "Kashiwa Reysol", slug: "kashiwa-reysol" },
|
|
524
|
+
{ name: "Kawasaki Frontale", slug: "kawasaki-frontale" },
|
|
525
|
+
{ name: "KFUM Oslo", slug: "kfum-oslo" },
|
|
526
|
+
{ name: "Kilmarnock", slug: "kilmarnock" },
|
|
527
|
+
{ name: "Kristiansund", slug: "kristiansund" },
|
|
528
|
+
{ name: "Kyoto Sanga", slug: "kyoto-sanga" },
|
|
529
|
+
{ name: "LA Galaxy", slug: "la-galaxy" },
|
|
530
|
+
{ name: "LAFC", slug: "lafc" },
|
|
531
|
+
{ name: "Lan\xFAs", slug: "lanus" },
|
|
532
|
+
{ name: "Las Vegas Lights", slug: "las-vegas-lights" },
|
|
533
|
+
{ name: "Lazio", slug: "lazio" },
|
|
534
|
+
{ name: "Le Havre", slug: "le-havre" },
|
|
535
|
+
{ name: "Le Mans", slug: "le-mans" },
|
|
536
|
+
{ name: "Leeds United", slug: "leeds-united" },
|
|
537
|
+
{ name: "Leicester City", slug: "leicester-city" },
|
|
538
|
+
{ name: "Lens", slug: "lens" },
|
|
539
|
+
{ name: "Le\xF3n", slug: "leon" },
|
|
540
|
+
{ name: "Lexington SC", slug: "lexington-sc" },
|
|
541
|
+
{ name: "Liberia", slug: "liberia" },
|
|
542
|
+
{ name: "Lille", slug: "lille" },
|
|
543
|
+
{ name: "Lillestr\xF8m", slug: "lillestrom" },
|
|
544
|
+
{ name: "Lim\xF3n", slug: "limon" },
|
|
545
|
+
{ name: "Liverpool", slug: "liverpool" },
|
|
546
|
+
{ name: "Livingston", slug: "livingston" },
|
|
547
|
+
{ name: "Lorient", slug: "lorient" },
|
|
548
|
+
{ name: "Loudoun United", slug: "loudoun-united" },
|
|
549
|
+
{ name: "Louisville City", slug: "louisville-city" },
|
|
550
|
+
{ name: "Lyngby", slug: "lyngby" },
|
|
551
|
+
{ name: "Lyon", slug: "lyon" },
|
|
552
|
+
{ name: "Machida Zelvia", slug: "machida-zelvia" },
|
|
553
|
+
{ name: "Mainz", slug: "mainz" },
|
|
554
|
+
{ name: "Malm\xF6 FF", slug: "malmo" },
|
|
555
|
+
{ name: "Manchester City", slug: "manchester-city" },
|
|
556
|
+
{ name: "Manchester United", slug: "manchester-united" },
|
|
557
|
+
{ name: "Marseille", slug: "marseille" },
|
|
558
|
+
{ name: "Mazatl\xE1n", slug: "mazatlan" },
|
|
559
|
+
{ name: "Metz", slug: "metz" },
|
|
560
|
+
{ name: "Miami FC", slug: "miami-fc" },
|
|
561
|
+
{ name: "Midtjylland", slug: "midtjylland" },
|
|
562
|
+
{ name: "Minnesota United", slug: "minnesota-united" },
|
|
563
|
+
{ name: "Mirassol", slug: "mirassol" },
|
|
564
|
+
{ name: "Mito Hollyhock", slug: "mito-hollyhock" },
|
|
565
|
+
{ name: "Mj\xE4llby", slug: "mjallby" },
|
|
566
|
+
{ name: "Molde", slug: "molde" },
|
|
567
|
+
{ name: "Monaco", slug: "monaco" },
|
|
568
|
+
{ name: "Monterey Bay", slug: "monterey-bay" },
|
|
569
|
+
{ name: "Monterrey", slug: "monterrey" },
|
|
570
|
+
{ name: "Montpellier", slug: "montpellier" },
|
|
571
|
+
{ name: "Motherwell", slug: "motherwell" },
|
|
572
|
+
{ name: "Municipal Grecia", slug: "grecia" },
|
|
573
|
+
{ name: "NAC Breda", slug: "nac-breda" },
|
|
574
|
+
{ name: "Nagoya Grampus", slug: "nagoya-grampus" },
|
|
575
|
+
{ name: "Nancy", slug: "nancy" },
|
|
576
|
+
{ name: "Nantes", slug: "nantes" },
|
|
577
|
+
{ name: "Napoli", slug: "napoli" },
|
|
578
|
+
{ name: "Nashville SC", slug: "nashville-sc" },
|
|
579
|
+
{ name: "NEC Nijmegen", slug: "nec" },
|
|
580
|
+
{ name: "Necaxa", slug: "necaxa" },
|
|
581
|
+
{ name: "New England", slug: "new-england" },
|
|
582
|
+
{ name: "New Mexico United", slug: "new-mexico-united" },
|
|
583
|
+
{ name: "Newcastle", slug: "newcastle" },
|
|
584
|
+
{ name: "Newell's Old Boys", slug: "newells" },
|
|
585
|
+
{ name: "Nice", slug: "nice" },
|
|
586
|
+
{ name: "N\xEEmes", slug: "nimes" },
|
|
587
|
+
{ name: "Nordsj\xE6lland", slug: "nordsjaelland" },
|
|
588
|
+
{ name: "NY Red Bulls", slug: "ny-red-bulls" },
|
|
589
|
+
{ name: "NYCFC", slug: "nycfc" },
|
|
590
|
+
{ name: "Oakland Roots", slug: "oakland-roots" },
|
|
591
|
+
{ name: "OB Odense", slug: "ob-odense" },
|
|
592
|
+
{ name: "Odd", slug: "odd" },
|
|
593
|
+
{ name: "Olympiacos", slug: "olympiacos" },
|
|
594
|
+
{ name: "Orange County SC", slug: "orange-county-sc" },
|
|
595
|
+
{ name: "\xD6rebro", slug: "orebro" },
|
|
596
|
+
{ name: "Orlando City", slug: "orlando-city" },
|
|
597
|
+
{ name: "Pachuca", slug: "pachuca" },
|
|
598
|
+
{ name: "Palmeiras", slug: "palmeiras" },
|
|
599
|
+
{ name: "Paris FC", slug: "paris-fc" },
|
|
600
|
+
{ name: "Paris Saint-Germain", slug: "psg" },
|
|
601
|
+
{ name: "PEC Zwolle", slug: "pec-zwolle" },
|
|
602
|
+
{ name: "P\xE9rez Zeled\xF3n", slug: "perez-zeledon" },
|
|
603
|
+
{ name: "Philadelphia Union", slug: "philadelphia-union" },
|
|
604
|
+
{ name: "Phoenix Rising", slug: "phoenix-rising" },
|
|
605
|
+
{ name: "Pittsburgh Riverhounds", slug: "pittsburgh-riverhounds" },
|
|
606
|
+
{ name: "Platense", slug: "platense" },
|
|
607
|
+
{ name: "Pohang Steelers", slug: "pohang-steelers" },
|
|
608
|
+
{ name: "Portland Timbers", slug: "portland-timbers" },
|
|
609
|
+
{ name: "Porto", slug: "porto" },
|
|
610
|
+
{ name: "PSV", slug: "psv" },
|
|
611
|
+
{ name: "Puebla", slug: "puebla" },
|
|
612
|
+
{ name: "Pumas", slug: "pumas" },
|
|
613
|
+
{ name: "Puntarenas", slug: "puntarenas" },
|
|
614
|
+
{ name: "Quer\xE9taro", slug: "queretaro" },
|
|
615
|
+
{ name: "Racing", slug: "racing" },
|
|
616
|
+
{ name: "Randers", slug: "randers" },
|
|
617
|
+
{ name: "Rangers", slug: "rangers" },
|
|
618
|
+
{ name: "RB Leipzig", slug: "rb-leipzig" },
|
|
619
|
+
{ name: "Real Madrid", slug: "real-madrid" },
|
|
620
|
+
{ name: "Real Salt Lake", slug: "real-salt-lake" },
|
|
621
|
+
{ name: "Red Star Belgrade", slug: "red-star" },
|
|
622
|
+
{ name: "Reims", slug: "reims" },
|
|
623
|
+
{ name: "Rennes", slug: "rennes" },
|
|
624
|
+
{ name: "Rhode Island FC", slug: "rhode-island-fc" },
|
|
625
|
+
{ name: "River Plate", slug: "river-plate" },
|
|
626
|
+
{ name: "RKC Waalwijk", slug: "rkc" },
|
|
627
|
+
{ name: "Roma", slug: "roma" },
|
|
628
|
+
{ name: "Rosario Central", slug: "rosario-central" },
|
|
629
|
+
{ name: "Rosenborg", slug: "rosenborg" },
|
|
630
|
+
{ name: "Sacramento Republic", slug: "sacramento-republic" },
|
|
631
|
+
{ name: "Saint-\xC9tienne", slug: "saint-etienne" },
|
|
632
|
+
{ name: "San Antonio FC", slug: "san-antonio-fc" },
|
|
633
|
+
{ name: "San Carlos", slug: "san-carlos" },
|
|
634
|
+
{ name: "San Diego FC", slug: "san-diego-fc" },
|
|
635
|
+
{ name: "San Jose Earthquakes", slug: "san-jose-earthquakes" },
|
|
636
|
+
{ name: "San Lorenzo", slug: "san-lorenzo" },
|
|
637
|
+
{ name: "Sanfrecce Hiroshima", slug: "sanfrecce-hiroshima" },
|
|
638
|
+
{ name: "Santos", slug: "santos" },
|
|
639
|
+
{ name: "Santos de Gu\xE1piles", slug: "santos-guapiles" },
|
|
640
|
+
{ name: "Santos Laguna", slug: "santos-laguna" },
|
|
641
|
+
{ name: "S\xE3o Paulo", slug: "sao-paulo" },
|
|
642
|
+
{ name: "Saprissa", slug: "saprissa" },
|
|
643
|
+
{ name: "Sarpsborg", slug: "sarpsborg" },
|
|
644
|
+
{ name: "Schalke", slug: "schalke" },
|
|
645
|
+
{ name: "Seattle Sounders", slug: "seattle-sounders" },
|
|
646
|
+
{ name: "Sevilla", slug: "sevilla" },
|
|
647
|
+
{ name: "Shakhtar", slug: "shakhtar" },
|
|
648
|
+
{ name: "Shimizu S-Pulse", slug: "shimizu-s-pulse" },
|
|
649
|
+
{ name: "Silkeborg", slug: "silkeborg" },
|
|
650
|
+
{ name: "Sirius", slug: "sirius" },
|
|
651
|
+
{ name: "Sochaux", slug: "sochaux" },
|
|
652
|
+
{ name: "S\xF8nderjyske", slug: "sonderjyske" },
|
|
653
|
+
{ name: "Sparta Rotterdam", slug: "sparta-rotterdam" },
|
|
654
|
+
{ name: "Sport Recife", slug: "sport-recife" },
|
|
655
|
+
{ name: "Sporting", slug: "sporting" },
|
|
656
|
+
{ name: "Sporting Jax", slug: "sporting-jax" },
|
|
657
|
+
{ name: "Sporting KC", slug: "sporting-kc" },
|
|
658
|
+
{ name: "Sporting San Jos\xE9", slug: "sporting-san-jose" },
|
|
659
|
+
{ name: "St Mirren", slug: "st-mirren" },
|
|
660
|
+
{ name: "St. Louis City", slug: "st-louis-city" },
|
|
661
|
+
{ name: "St. Pauli", slug: "st-pauli" },
|
|
662
|
+
{ name: "Stab\xE6k", slug: "stabaek" },
|
|
663
|
+
{ name: "Strasbourg", slug: "strasbourg" },
|
|
664
|
+
{ name: "Str\xF8msgodset", slug: "stromsgodset" },
|
|
665
|
+
{ name: "Stuttgart", slug: "stuttgart" },
|
|
666
|
+
{ name: "Switchbacks", slug: "switchbacks" },
|
|
667
|
+
{ name: "Talleres", slug: "talleres" },
|
|
668
|
+
{ name: "Tampa Bay Rowdies", slug: "tampa-bay-rowdies" },
|
|
669
|
+
{ name: "Tigre", slug: "tigre" },
|
|
670
|
+
{ name: "Tigres", slug: "tigres" },
|
|
671
|
+
{ name: "Tijuana", slug: "tijuana" },
|
|
672
|
+
{ name: "Tokyo Verdy", slug: "tokyo-verdy" },
|
|
673
|
+
{ name: "Toluca", slug: "toluca" },
|
|
674
|
+
{ name: "Toronto FC", slug: "toronto-fc" },
|
|
675
|
+
{ name: "Tottenham", slug: "tottenham" },
|
|
676
|
+
{ name: "Toulouse", slug: "toulouse" },
|
|
677
|
+
{ name: "Troms\xF8", slug: "tromso" },
|
|
678
|
+
{ name: "Troyes", slug: "troyes" },
|
|
679
|
+
{ name: "Twente", slug: "twente" },
|
|
680
|
+
{ name: "Ulsan HD", slug: "ulsan-hd" },
|
|
681
|
+
{ name: "Uni\xF3n", slug: "union" },
|
|
682
|
+
{ name: "Union Berlin", slug: "union-berlin" },
|
|
683
|
+
{ name: "Urawa Reds", slug: "urawa-reds" },
|
|
684
|
+
{ name: "Utrecht", slug: "utrecht" },
|
|
685
|
+
{ name: "V-Varen Nagasaki", slug: "v-varen-nagasaki" },
|
|
686
|
+
{ name: "Valencia", slug: "valencia" },
|
|
687
|
+
{ name: "V\xE5lerenga", slug: "valerenga" },
|
|
688
|
+
{ name: "Vancouver Whitecaps", slug: "vancouver-whitecaps" },
|
|
689
|
+
{ name: "V\xE4rnamo", slug: "varnamo" },
|
|
690
|
+
{ name: "Vasco da Gama", slug: "vasco" },
|
|
691
|
+
{ name: "Vejle", slug: "vejle" },
|
|
692
|
+
{ name: "V\xE9lez Sarsfield", slug: "velez" },
|
|
693
|
+
{ name: "Viborg", slug: "viborg" },
|
|
694
|
+
{ name: "Viking", slug: "viking" },
|
|
695
|
+
{ name: "Vissel Kobe", slug: "vissel-kobe" },
|
|
696
|
+
{ name: "Vitesse", slug: "vitesse" },
|
|
697
|
+
{ name: "Vit\xF3ria", slug: "vitoria" },
|
|
698
|
+
{ name: "Volendam", slug: "volendam" },
|
|
699
|
+
{ name: "Werder Bremen", slug: "werder-bremen" },
|
|
700
|
+
{ name: "West Ham", slug: "west-ham" },
|
|
701
|
+
{ name: "Willem II", slug: "willem-ii" },
|
|
702
|
+
{ name: "Wolfsburg", slug: "wolfsburg" },
|
|
703
|
+
{ name: "Yokohama Marinos", slug: "yokohama-marinos" }
|
|
704
|
+
];
|
|
705
|
+
var FAMOUS_SOCCER_TEAMS = FAMOUS_SOCCER_TEAM_SEEDS.map(withMeta);
|
|
706
|
+
function lookupSoccerTeam(titleOrSlug) {
|
|
707
|
+
const trimmed = titleOrSlug.trim();
|
|
708
|
+
if (!trimmed) return null;
|
|
709
|
+
const fromName = teamSlugFromName(trimmed);
|
|
710
|
+
const slug = fromName ?? normalizeTakenSlug(trimmed);
|
|
711
|
+
const baseSlug = slug.replace(/-\d+$/, "");
|
|
712
|
+
const seed = FAMOUS_SOCCER_TEAM_SEEDS.find((t) => t.slug === baseSlug);
|
|
713
|
+
if (!seed) return null;
|
|
714
|
+
const suffixMatch = slug.match(/-(\d+)$/) || trimmed.match(/\s+(\d+)$/);
|
|
715
|
+
const suffix = suffixMatch?.[1];
|
|
716
|
+
return withMeta({
|
|
717
|
+
name: suffix ? `${seed.name} ${suffix}` : seed.name,
|
|
718
|
+
slug: suffix ? `${seed.slug}-${suffix}` : seed.slug
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
function teamNameFromSlug(slug) {
|
|
722
|
+
const normalized = slug.toLowerCase().replace(/^thread\//, "");
|
|
723
|
+
const suffixMatch = normalized.match(/^(.+)-(\d+)$/);
|
|
724
|
+
const base = suffixMatch?.[1] ?? normalized;
|
|
725
|
+
const suffix = suffixMatch?.[2];
|
|
726
|
+
const team = FAMOUS_SOCCER_TEAM_SEEDS.find((t) => t.slug === base);
|
|
727
|
+
if (team) return suffix ? `${team.name} ${suffix}` : team.name;
|
|
728
|
+
return normalized.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
|
|
729
|
+
}
|
|
730
|
+
function teamSlugFromName(name) {
|
|
731
|
+
const trimmed = name.trim();
|
|
732
|
+
if (!trimmed) return null;
|
|
733
|
+
const suffixMatch = trimmed.match(/^(.+?)\s+(\d+)$/);
|
|
734
|
+
const baseName = suffixMatch?.[1] ?? trimmed;
|
|
735
|
+
const suffix = suffixMatch?.[2];
|
|
736
|
+
const team = FAMOUS_SOCCER_TEAM_SEEDS.find(
|
|
737
|
+
(t) => t.name.toLowerCase() === baseName.toLowerCase()
|
|
738
|
+
);
|
|
739
|
+
if (!team) return null;
|
|
740
|
+
return suffix ? `${team.slug}-${suffix}` : team.slug;
|
|
741
|
+
}
|
|
742
|
+
function normalizeTakenSlug(raw) {
|
|
743
|
+
return raw.toLowerCase().replace(/^thread\//, "").replace(/\/+$/, "");
|
|
744
|
+
}
|
|
745
|
+
function takenSlugsFromThread(thread) {
|
|
746
|
+
const out = /* @__PURE__ */ new Set();
|
|
747
|
+
const titleSlug = thread.title ? teamSlugFromName(thread.title) : null;
|
|
748
|
+
if (titleSlug) out.add(normalizeTakenSlug(titleSlug));
|
|
749
|
+
if (thread.branchName?.trim()) {
|
|
750
|
+
const branch = normalizeTakenSlug(thread.branchName.trim());
|
|
751
|
+
if (branch) out.add(branch);
|
|
752
|
+
}
|
|
753
|
+
if (thread.worktreePath) {
|
|
754
|
+
const parts = thread.worktreePath.replace(/\/+$/, "").split("/");
|
|
755
|
+
const dir = parts[parts.length - 1];
|
|
756
|
+
if (dir) out.add(normalizeTakenSlug(dir));
|
|
757
|
+
}
|
|
758
|
+
return [...out];
|
|
759
|
+
}
|
|
760
|
+
function allocateTeamName(taken, random = Math.random) {
|
|
761
|
+
const takenSet = new Set(
|
|
762
|
+
[...taken].map((s) => normalizeTakenSlug(s)).filter(Boolean)
|
|
763
|
+
);
|
|
764
|
+
const available = FAMOUS_SOCCER_TEAM_SEEDS.filter((t) => !takenSet.has(t.slug));
|
|
765
|
+
if (available.length > 0) {
|
|
766
|
+
const idx = Math.floor(random() * available.length);
|
|
767
|
+
return withMeta(available[idx]);
|
|
768
|
+
}
|
|
769
|
+
for (let n = 2; n < 1e3; n++) {
|
|
770
|
+
for (const t of FAMOUS_SOCCER_TEAM_SEEDS) {
|
|
771
|
+
const slug = `${t.slug}-${n}`;
|
|
772
|
+
if (!takenSet.has(slug)) {
|
|
773
|
+
return withMeta({ name: `${t.name} ${n}`, slug });
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
throw new Error("No available soccer team names left");
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// src/git/worktree-labels.ts
|
|
781
|
+
function normalizeWorktreePath(worktreePath) {
|
|
782
|
+
const trimmed = worktreePath.replace(/\/+$/, "") || "/";
|
|
783
|
+
const absolute = trimmed.startsWith("/");
|
|
784
|
+
const parts = trimmed.split("/").filter((p) => p && p !== ".");
|
|
785
|
+
const out = [];
|
|
786
|
+
for (const p of parts) {
|
|
787
|
+
if (p === "..") {
|
|
788
|
+
out.pop();
|
|
789
|
+
continue;
|
|
790
|
+
}
|
|
791
|
+
out.push(p);
|
|
792
|
+
}
|
|
793
|
+
return (absolute ? "/" : "") + out.join("/");
|
|
794
|
+
}
|
|
795
|
+
function worktreeNameFromPath(worktreePath) {
|
|
796
|
+
const normalized = worktreePath.replace(/\/+$/, "");
|
|
797
|
+
const parts = normalized.split("/");
|
|
798
|
+
return parts[parts.length - 1] || worktreePath;
|
|
799
|
+
}
|
|
800
|
+
function isPlaceholderBranch(branchName, worktreePath) {
|
|
801
|
+
const branch = branchName.trim();
|
|
802
|
+
if (!branch || branch === "HEAD") return true;
|
|
803
|
+
if (branch.startsWith("thread/")) return true;
|
|
804
|
+
const dir = worktreeNameFromPath(worktreePath).toLowerCase();
|
|
805
|
+
return branch.toLowerCase() === dir;
|
|
806
|
+
}
|
|
807
|
+
function branchDisplayLabel(branchName, worktreePath) {
|
|
808
|
+
const dir = worktreeNameFromPath(worktreePath);
|
|
809
|
+
if (isPlaceholderBranch(branchName, worktreePath)) {
|
|
810
|
+
const slug = branchName.replace(/^thread\//, "") || dir;
|
|
811
|
+
return teamNameFromSlug(slug);
|
|
812
|
+
}
|
|
813
|
+
return branchName.trim();
|
|
814
|
+
}
|
|
815
|
+
function threadDisplayLabel(thread) {
|
|
816
|
+
if (thread.userSetTitle && thread.title?.trim()) return thread.title.trim();
|
|
817
|
+
if (thread.prTitle?.trim()) return thread.prTitle.trim();
|
|
818
|
+
return branchDisplayLabel(thread.branchName, thread.worktreePath);
|
|
819
|
+
}
|
|
820
|
+
function worktreeDisplayLabel(thread) {
|
|
821
|
+
return threadDisplayLabel(thread);
|
|
822
|
+
}
|
|
823
|
+
function worktreeDisplayLabelForGroup(threads) {
|
|
824
|
+
if (threads.length === 0) return "Worktree";
|
|
825
|
+
const canonical = [...threads].sort((a, b) => a.createdAt.localeCompare(b.createdAt))[0];
|
|
826
|
+
return threadDisplayLabel(canonical);
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// src/git/gh-errors.ts
|
|
830
|
+
function isGhRateLimitError(text) {
|
|
831
|
+
return /API rate limit (already )?exceeded/i.test(text) || /rate limit exceeded/i.test(text);
|
|
832
|
+
}
|
|
833
|
+
function formatRateLimitResetHint(resetEpochSec, nowMs = Date.now()) {
|
|
834
|
+
const ms = resetEpochSec * 1e3 - nowMs;
|
|
835
|
+
if (ms <= 0) return "soon";
|
|
836
|
+
const mins = Math.max(1, Math.ceil(ms / 6e4));
|
|
837
|
+
if (mins < 60) {
|
|
838
|
+
return `in about ${mins} minute${mins === 1 ? "" : "s"}`;
|
|
839
|
+
}
|
|
840
|
+
const hours = Math.ceil(mins / 60);
|
|
841
|
+
return `in about ${hours} hour${hours === 1 ? "" : "s"}`;
|
|
842
|
+
}
|
|
843
|
+
function extractGhErrorDetail(text) {
|
|
844
|
+
const trimmed = text.trim();
|
|
845
|
+
if (!trimmed) return "";
|
|
846
|
+
const graphql = trimmed.match(/\bGraphQL:\s*(.+)$/im);
|
|
847
|
+
if (graphql?.[1]) return `GraphQL: ${graphql[1].trim()}`;
|
|
848
|
+
const http = trimmed.match(/\bHTTP\s+\d{3}:\s*(.+)$/im);
|
|
849
|
+
if (http?.[1]) return `HTTP: ${http[1].trim()}`;
|
|
850
|
+
const lines = trimmed.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
|
|
851
|
+
if (lines.length > 1 && /^Command failed with exit code/i.test(lines[0])) {
|
|
852
|
+
return lines.slice(1).join(" ").trim() || lines[0];
|
|
853
|
+
}
|
|
854
|
+
return trimmed;
|
|
855
|
+
}
|
|
856
|
+
function formatGhLandError(raw, opts) {
|
|
857
|
+
const trimmed = raw.trim();
|
|
858
|
+
if (trimmed.startsWith("GitHub API rate limit exceeded.")) {
|
|
859
|
+
return trimmed;
|
|
860
|
+
}
|
|
861
|
+
const detail = extractGhErrorDetail(raw);
|
|
862
|
+
if (/Head ref must be a branch|No commits between|Head sha can't be blank/i.test(
|
|
863
|
+
raw
|
|
864
|
+
) || /Head ref must be a branch|No commits between|Head sha can't be blank/i.test(
|
|
865
|
+
detail
|
|
866
|
+
)) {
|
|
867
|
+
const target = opts?.targetedRepo ? ` Targeted ${opts.targetedRepo}` : "";
|
|
868
|
+
const head = opts?.headRef ? ` with head ${opts.headRef}` : "";
|
|
869
|
+
const hint = opts?.targetedRepo ? ` Branch was pushed to origin \u2014 confirm it exists on GitHub and differs from the base branch.${target}${head}.` : " Often `gh` targeted upstream instead of origin. Branch was pushed \u2014 retry in the latest Sideboard, or run: gh pr create -R <owner/name> --base main --head <branch>.";
|
|
870
|
+
return `Could not create the pull request.${hint}`;
|
|
871
|
+
}
|
|
872
|
+
if (isGhRateLimitError(raw) || isGhRateLimitError(detail)) {
|
|
873
|
+
const when = opts?.resetAt ? ` Try again ${formatRateLimitResetHint(opts.resetAt, opts.nowMs)}.` : " Wait a few minutes and try again.";
|
|
874
|
+
const pushNote = opts?.pushed === false ? "" : " Your branch was already pushed.";
|
|
875
|
+
return `GitHub API rate limit exceeded.${pushNote}${when} Or create the pull request in the browser (Push & open on GitHub).`;
|
|
876
|
+
}
|
|
877
|
+
return detail || "Failed to create or update pull request";
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// src/git/pr-gates.ts
|
|
881
|
+
function buildMergeGateChecks(gate, opts = {}) {
|
|
882
|
+
const rows = [];
|
|
883
|
+
const base = gate.baseRefName?.trim() || "the base branch";
|
|
884
|
+
const link = gate.url?.trim() || null;
|
|
885
|
+
const mergeable = (gate.mergeable ?? "").toUpperCase();
|
|
886
|
+
const mergeState = (gate.mergeStateStatus ?? "").toUpperCase();
|
|
887
|
+
const review = (gate.reviewDecision ?? "").toUpperCase();
|
|
888
|
+
const suppressBlocked = opts.suppressGenericBlocked !== false;
|
|
889
|
+
const conflicting = mergeable === "CONFLICTING" || mergeState === "DIRTY";
|
|
890
|
+
if (conflicting) {
|
|
891
|
+
rows.push({
|
|
892
|
+
name: "Merge conflicts",
|
|
893
|
+
state: mergeable === "CONFLICTING" ? "CONFLICTING" : mergeState || "DIRTY",
|
|
894
|
+
bucket: "fail",
|
|
895
|
+
startedAt: null,
|
|
896
|
+
completedAt: null,
|
|
897
|
+
link,
|
|
898
|
+
description: `This branch has conflicts that must be resolved before it can merge into ${base}.`,
|
|
899
|
+
workflow: "mergeability",
|
|
900
|
+
kind: "mergeability"
|
|
901
|
+
});
|
|
902
|
+
} else if (mergeState === "BEHIND") {
|
|
903
|
+
rows.push({
|
|
904
|
+
name: "Branch behind",
|
|
905
|
+
state: "BEHIND",
|
|
906
|
+
bucket: "fail",
|
|
907
|
+
startedAt: null,
|
|
908
|
+
completedAt: null,
|
|
909
|
+
link,
|
|
910
|
+
description: `Head is behind ${base}. Update this branch (merge or rebase) before merging.`,
|
|
911
|
+
workflow: "mergeability",
|
|
912
|
+
kind: "mergeability"
|
|
913
|
+
});
|
|
914
|
+
} else if (mergeState === "BLOCKED" && !suppressBlocked) {
|
|
915
|
+
rows.push({
|
|
916
|
+
name: "Merge blocked",
|
|
917
|
+
state: "BLOCKED",
|
|
918
|
+
bucket: "fail",
|
|
919
|
+
startedAt: null,
|
|
920
|
+
completedAt: null,
|
|
921
|
+
link,
|
|
922
|
+
description: "GitHub reports this PR as blocked (branch protection \u2014 required reviews and/or status checks).",
|
|
923
|
+
workflow: "mergeability",
|
|
924
|
+
kind: "mergeability"
|
|
925
|
+
});
|
|
926
|
+
} else if (mergeable === "UNKNOWN" || mergeState === "UNKNOWN") {
|
|
927
|
+
rows.push({
|
|
928
|
+
name: "Mergeability",
|
|
929
|
+
state: "UNKNOWN",
|
|
930
|
+
bucket: "pending",
|
|
931
|
+
startedAt: null,
|
|
932
|
+
completedAt: null,
|
|
933
|
+
link,
|
|
934
|
+
description: "GitHub is still computing whether this branch can merge.",
|
|
935
|
+
workflow: "mergeability",
|
|
936
|
+
kind: "mergeability"
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
if (review === "CHANGES_REQUESTED") {
|
|
940
|
+
rows.push({
|
|
941
|
+
name: "Code review",
|
|
942
|
+
state: "CHANGES_REQUESTED",
|
|
943
|
+
bucket: "fail",
|
|
944
|
+
startedAt: null,
|
|
945
|
+
completedAt: null,
|
|
946
|
+
link,
|
|
947
|
+
description: "A reviewer requested changes. Address their feedback before merging.",
|
|
948
|
+
workflow: "review",
|
|
949
|
+
kind: "review"
|
|
950
|
+
});
|
|
951
|
+
} else if (review === "REVIEW_REQUIRED") {
|
|
952
|
+
rows.push({
|
|
953
|
+
name: "Code review",
|
|
954
|
+
state: "REVIEW_REQUIRED",
|
|
955
|
+
bucket: "pending",
|
|
956
|
+
startedAt: null,
|
|
957
|
+
completedAt: null,
|
|
958
|
+
link,
|
|
959
|
+
description: "A required approving review is still outstanding.",
|
|
960
|
+
workflow: "review",
|
|
961
|
+
kind: "review"
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
return rows;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
// src/git/worktree.ts
|
|
968
|
+
async function lookupGithubGraphqlReset(cwd) {
|
|
969
|
+
const result = await gh(["api", "rate_limit"], cwd, { reject: false });
|
|
970
|
+
if (result.exitCode !== 0 || !result.stdout.trim()) return void 0;
|
|
971
|
+
try {
|
|
972
|
+
const data = JSON.parse(result.stdout);
|
|
973
|
+
const reset = data.resources?.graphql?.reset;
|
|
974
|
+
return typeof reset === "number" ? reset : void 0;
|
|
975
|
+
} catch {
|
|
976
|
+
return void 0;
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
async function formatPrCreateFailure(raw, cwd, ctx) {
|
|
980
|
+
if (!isGhRateLimitError(raw)) {
|
|
981
|
+
return formatGhLandError(raw, {
|
|
982
|
+
targetedRepo: ctx?.slug,
|
|
983
|
+
headRef: ctx?.head
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
const resetAt = await lookupGithubGraphqlReset(cwd);
|
|
987
|
+
return formatGhLandError(raw, {
|
|
988
|
+
resetAt,
|
|
989
|
+
targetedRepo: ctx?.slug,
|
|
990
|
+
headRef: ctx?.head
|
|
991
|
+
});
|
|
992
|
+
}
|
|
993
|
+
function slugify(input) {
|
|
994
|
+
return input.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48);
|
|
995
|
+
}
|
|
996
|
+
async function resolveRepoRoot(cwd) {
|
|
997
|
+
const { stdout } = await git(["rev-parse", "--show-toplevel"], cwd);
|
|
998
|
+
return stdout.trim();
|
|
999
|
+
}
|
|
1000
|
+
function parseGithubSlugFromRemoteUrl(url) {
|
|
1001
|
+
const trimmed = url.trim().replace(/\.git$/i, "");
|
|
1002
|
+
if (!trimmed) return null;
|
|
1003
|
+
const github = trimmed.match(/github\.com[:/]([^/]+)\/([^/]+)$/i);
|
|
1004
|
+
if (github?.[1] && github[2]) return `${github[1]}/${github[2]}`;
|
|
1005
|
+
const sshAlias = trimmed.match(/^git@[^:]+:([^/]+)\/([^/]+)$/i);
|
|
1006
|
+
if (sshAlias?.[1] && sshAlias[2]) return `${sshAlias[1]}/${sshAlias[2]}`;
|
|
1007
|
+
return null;
|
|
1008
|
+
}
|
|
1009
|
+
async function slugFromGitRemote(repoPath, remote) {
|
|
1010
|
+
const result = await git(["remote", "get-url", remote], repoPath, {
|
|
1011
|
+
reject: false
|
|
1012
|
+
});
|
|
1013
|
+
if (result.exitCode !== 0 || !result.stdout.trim()) return null;
|
|
1014
|
+
return parseGithubSlugFromRemoteUrl(result.stdout);
|
|
1015
|
+
}
|
|
1016
|
+
async function resolveGithubRepoSlug(repoPath) {
|
|
1017
|
+
const fromOrigin = await slugFromGitRemote(repoPath, "origin");
|
|
1018
|
+
if (fromOrigin) return fromOrigin;
|
|
1019
|
+
const hasAlt = Boolean(await slugFromGitRemote(repoPath, "upstream")) || Boolean(await slugFromGitRemote(repoPath, "github"));
|
|
1020
|
+
if (!hasAlt) {
|
|
1021
|
+
const viaGh = await gh(
|
|
1022
|
+
["repo", "view", "--json", "nameWithOwner", "--jq", ".nameWithOwner"],
|
|
1023
|
+
repoPath,
|
|
1024
|
+
{ reject: false }
|
|
1025
|
+
);
|
|
1026
|
+
if (viaGh.exitCode === 0 && viaGh.stdout.trim()) {
|
|
1027
|
+
return viaGh.stdout.trim();
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
for (const remote of ["upstream", "github"]) {
|
|
1031
|
+
const slug = await slugFromGitRemote(repoPath, remote);
|
|
1032
|
+
if (slug) return slug;
|
|
1033
|
+
}
|
|
1034
|
+
return null;
|
|
1035
|
+
}
|
|
1036
|
+
function ghRepoSelectArgs(slug) {
|
|
1037
|
+
return ["-R", slug];
|
|
1038
|
+
}
|
|
1039
|
+
function ghHeadRef(slug, branch) {
|
|
1040
|
+
const owner = slug.split("/")[0];
|
|
1041
|
+
const head = branch.trim().replace(/^refs\/heads\//, "");
|
|
1042
|
+
if (!owner || !head) return head || branch;
|
|
1043
|
+
if (head.includes(":")) return head;
|
|
1044
|
+
return `${owner}:${head}`;
|
|
1045
|
+
}
|
|
1046
|
+
async function ensureGhPreferOrigin(cwd) {
|
|
1047
|
+
const originSlug = await slugFromGitRemote(cwd, "origin");
|
|
1048
|
+
if (!originSlug) return;
|
|
1049
|
+
const hasAlt = Boolean(await slugFromGitRemote(cwd, "upstream")) || Boolean(await slugFromGitRemote(cwd, "github"));
|
|
1050
|
+
if (!hasAlt) return;
|
|
1051
|
+
const view = await gh(["repo", "set-default", "--view"], cwd, {
|
|
1052
|
+
reject: false
|
|
1053
|
+
});
|
|
1054
|
+
const current = `${view.stdout}
|
|
1055
|
+
${view.stderr}`;
|
|
1056
|
+
if (view.exitCode === 0 && current.includes(originSlug)) return;
|
|
1057
|
+
await gh(["repo", "set-default", "origin"], cwd, { reject: false });
|
|
1058
|
+
}
|
|
1059
|
+
async function originGhRepoEnv(cwd) {
|
|
1060
|
+
await ensureGhPreferOrigin(cwd);
|
|
1061
|
+
const slug = await resolveGithubRepoSlug(cwd);
|
|
1062
|
+
return slug ? { GH_REPO: slug } : {};
|
|
1063
|
+
}
|
|
1064
|
+
async function resolveDefaultBranch(repoPath) {
|
|
1065
|
+
const viaOrigin = await git(
|
|
1066
|
+
["symbolic-ref", "refs/remotes/origin/HEAD"],
|
|
1067
|
+
repoPath,
|
|
1068
|
+
{ reject: false }
|
|
1069
|
+
);
|
|
1070
|
+
if (viaOrigin.exitCode === 0 && viaOrigin.stdout.trim()) {
|
|
1071
|
+
return viaOrigin.stdout.trim().replace(/^refs\/remotes\/origin\//, "");
|
|
1072
|
+
}
|
|
1073
|
+
const slug = await resolveGithubRepoSlug(repoPath);
|
|
1074
|
+
const viaGh = await gh(
|
|
1075
|
+
[
|
|
1076
|
+
"repo",
|
|
1077
|
+
"view",
|
|
1078
|
+
...slug ? ["--repo", slug] : [],
|
|
1079
|
+
"--json",
|
|
1080
|
+
"defaultBranchRef",
|
|
1081
|
+
"--jq",
|
|
1082
|
+
".defaultBranchRef.name"
|
|
1083
|
+
],
|
|
1084
|
+
repoPath,
|
|
1085
|
+
{ reject: false }
|
|
1086
|
+
);
|
|
1087
|
+
if (viaGh.exitCode === 0 && viaGh.stdout.trim()) {
|
|
1088
|
+
return viaGh.stdout.trim();
|
|
1089
|
+
}
|
|
1090
|
+
for (const candidate of ["main", "master"]) {
|
|
1091
|
+
const check = await git(
|
|
1092
|
+
["show-ref", "--verify", "--quiet", `refs/remotes/origin/${candidate}`],
|
|
1093
|
+
repoPath,
|
|
1094
|
+
{ reject: false }
|
|
1095
|
+
);
|
|
1096
|
+
if (check.exitCode === 0) return candidate;
|
|
1097
|
+
}
|
|
1098
|
+
for (const candidate of ["main", "master"]) {
|
|
1099
|
+
const check = await git(
|
|
1100
|
+
["show-ref", "--verify", "--quiet", `refs/heads/${candidate}`],
|
|
1101
|
+
repoPath,
|
|
1102
|
+
{ reject: false }
|
|
1103
|
+
);
|
|
1104
|
+
if (check.exitCode === 0) return candidate;
|
|
1105
|
+
}
|
|
1106
|
+
return "main";
|
|
1107
|
+
}
|
|
1108
|
+
async function resolveDiffBaseRef(cwd, branchOrRef, fallbackCwd) {
|
|
1109
|
+
const ref = branchOrRef.trim();
|
|
1110
|
+
if (!ref) return ref;
|
|
1111
|
+
if (ref.startsWith("origin/") || ref.startsWith("refs/")) return ref;
|
|
1112
|
+
if (ref.includes("/")) return ref;
|
|
1113
|
+
const remote = `origin/${ref}`;
|
|
1114
|
+
for (const dir of [cwd, fallbackCwd].filter(Boolean)) {
|
|
1115
|
+
const ok = await git(["rev-parse", "--verify", remote], dir, { reject: false });
|
|
1116
|
+
if (ok.exitCode === 0) return remote;
|
|
1117
|
+
}
|
|
1118
|
+
return ref;
|
|
1119
|
+
}
|
|
1120
|
+
async function listBranches(repoPath, opts) {
|
|
1121
|
+
const { stdout } = await git(
|
|
1122
|
+
["for-each-ref", "--format=%(refname:short)|%(HEAD)|%(upstream:short)", "refs/heads", "refs/remotes"],
|
|
1123
|
+
repoPath
|
|
1124
|
+
);
|
|
1125
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1126
|
+
const branches = [];
|
|
1127
|
+
for (const line of stdout.split("\n").filter(Boolean)) {
|
|
1128
|
+
const [name, head] = line.split("|");
|
|
1129
|
+
if (!name || name.endsWith("/HEAD")) continue;
|
|
1130
|
+
const remote = name.startsWith("origin/");
|
|
1131
|
+
const short = remote ? name.replace(/^origin\//, "") : name;
|
|
1132
|
+
if (seen.has(short) && !remote) continue;
|
|
1133
|
+
if (!seen.has(short)) {
|
|
1134
|
+
seen.add(short);
|
|
1135
|
+
branches.push({ name: short, remote, current: head === "*" });
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
const sorted = branches.sort((a, b) => a.name.localeCompare(b.name));
|
|
1139
|
+
if (!opts?.unmergedOnly) return sorted;
|
|
1140
|
+
const defaultBranch = await resolveDefaultBranch(repoPath);
|
|
1141
|
+
const originDefault = `origin/${defaultBranch}`;
|
|
1142
|
+
const originCheck = await git(
|
|
1143
|
+
["rev-parse", "--verify", "--quiet", originDefault],
|
|
1144
|
+
repoPath,
|
|
1145
|
+
{ reject: false }
|
|
1146
|
+
);
|
|
1147
|
+
const mergeBase = originCheck.exitCode === 0 ? originDefault : defaultBranch;
|
|
1148
|
+
const noMerged = await git(
|
|
1149
|
+
[
|
|
1150
|
+
"for-each-ref",
|
|
1151
|
+
`--no-merged=${mergeBase}`,
|
|
1152
|
+
"--format=%(refname:short)",
|
|
1153
|
+
"refs/heads",
|
|
1154
|
+
"refs/remotes/origin"
|
|
1155
|
+
],
|
|
1156
|
+
repoPath,
|
|
1157
|
+
{ reject: false }
|
|
1158
|
+
);
|
|
1159
|
+
const keep = /* @__PURE__ */ new Set();
|
|
1160
|
+
keep.add(defaultBranch);
|
|
1161
|
+
for (const line of noMerged.stdout.split("\n").filter(Boolean)) {
|
|
1162
|
+
const short = line.startsWith("origin/") ? line.replace(/^origin\//, "") : line;
|
|
1163
|
+
if (short && short !== "HEAD") keep.add(short);
|
|
1164
|
+
}
|
|
1165
|
+
return sorted.filter((b) => keep.has(b.name));
|
|
1166
|
+
}
|
|
1167
|
+
async function listPrs(repoPath) {
|
|
1168
|
+
const slug = await resolveGithubRepoSlug(repoPath);
|
|
1169
|
+
const args = [
|
|
1170
|
+
"pr",
|
|
1171
|
+
"list",
|
|
1172
|
+
"--json",
|
|
1173
|
+
"number,title,headRefName,url,isCrossRepository",
|
|
1174
|
+
"--limit",
|
|
1175
|
+
"50"
|
|
1176
|
+
];
|
|
1177
|
+
if (slug) args.push("--repo", slug);
|
|
1178
|
+
const { stdout, exitCode } = await gh(args, repoPath, { reject: false });
|
|
1179
|
+
if (exitCode !== 0 || !stdout.trim()) return [];
|
|
1180
|
+
return JSON.parse(stdout);
|
|
1181
|
+
}
|
|
1182
|
+
async function getPr(repoPath, number) {
|
|
1183
|
+
const slug = await resolveGithubRepoSlug(repoPath);
|
|
1184
|
+
const args = [
|
|
1185
|
+
"pr",
|
|
1186
|
+
"view",
|
|
1187
|
+
String(number),
|
|
1188
|
+
"--json",
|
|
1189
|
+
"number,title,headRefName,url,isCrossRepository"
|
|
1190
|
+
];
|
|
1191
|
+
if (slug) args.push("--repo", slug);
|
|
1192
|
+
const { stdout, exitCode } = await gh(args, repoPath, { reject: false });
|
|
1193
|
+
if (exitCode !== 0 || !stdout.trim()) return null;
|
|
1194
|
+
return JSON.parse(stdout);
|
|
1195
|
+
}
|
|
1196
|
+
function resolvePrSelector(thread) {
|
|
1197
|
+
if (thread.prUrl?.trim()) return thread.prUrl.trim();
|
|
1198
|
+
if (thread.sourceType === "pr" && thread.sourceRef?.trim()) {
|
|
1199
|
+
return thread.sourceRef.replace(/^#/, "").trim();
|
|
1200
|
+
}
|
|
1201
|
+
if (thread.branchName?.trim()) return thread.branchName.trim();
|
|
1202
|
+
return null;
|
|
1203
|
+
}
|
|
1204
|
+
function normalizeGhTime(value) {
|
|
1205
|
+
if (typeof value !== "string" || !value.trim()) return null;
|
|
1206
|
+
if (value.startsWith("0001-01-01")) return null;
|
|
1207
|
+
const ms = Date.parse(value);
|
|
1208
|
+
return Number.isFinite(ms) ? value : null;
|
|
1209
|
+
}
|
|
1210
|
+
function normalizeCheck(raw) {
|
|
1211
|
+
return {
|
|
1212
|
+
name: String(raw.name ?? "check"),
|
|
1213
|
+
state: String(raw.state ?? ""),
|
|
1214
|
+
bucket: String(raw.bucket ?? "pending"),
|
|
1215
|
+
startedAt: normalizeGhTime(raw.startedAt),
|
|
1216
|
+
completedAt: normalizeGhTime(raw.completedAt),
|
|
1217
|
+
link: typeof raw.link === "string" && raw.link ? raw.link : null,
|
|
1218
|
+
description: typeof raw.description === "string" && raw.description ? raw.description : null,
|
|
1219
|
+
workflow: typeof raw.workflow === "string" && raw.workflow ? raw.workflow : null,
|
|
1220
|
+
kind: "ci"
|
|
1221
|
+
};
|
|
1222
|
+
}
|
|
1223
|
+
async function fetchPrMergeGate(cwd, selector, slug) {
|
|
1224
|
+
const args = [
|
|
1225
|
+
"pr",
|
|
1226
|
+
"view",
|
|
1227
|
+
selector,
|
|
1228
|
+
"--json",
|
|
1229
|
+
"mergeable,mergeStateStatus,reviewDecision,baseRefName,url"
|
|
1230
|
+
];
|
|
1231
|
+
if (slug) args.push("--repo", slug);
|
|
1232
|
+
const { stdout, exitCode, stderr } = await gh(args, cwd, { reject: false });
|
|
1233
|
+
if (exitCode !== 0 || !stdout.trim()) {
|
|
1234
|
+
if (/no pull requests found/i.test(stderr)) return null;
|
|
1235
|
+
return null;
|
|
1236
|
+
}
|
|
1237
|
+
try {
|
|
1238
|
+
const view = JSON.parse(stdout);
|
|
1239
|
+
return {
|
|
1240
|
+
mergeable: typeof view.mergeable === "string" && view.mergeable ? view.mergeable : null,
|
|
1241
|
+
mergeStateStatus: typeof view.mergeStateStatus === "string" && view.mergeStateStatus ? view.mergeStateStatus : null,
|
|
1242
|
+
reviewDecision: typeof view.reviewDecision === "string" && view.reviewDecision ? view.reviewDecision : null,
|
|
1243
|
+
baseRefName: typeof view.baseRefName === "string" && view.baseRefName ? view.baseRefName : null,
|
|
1244
|
+
url: typeof view.url === "string" && view.url ? view.url : null
|
|
1245
|
+
};
|
|
1246
|
+
} catch {
|
|
1247
|
+
return null;
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
async function detectLocalMergeConflicts(cwd, baseRefName) {
|
|
1251
|
+
const baseName = (baseRefName?.trim() || await resolveDefaultBranch(cwd)).replace(
|
|
1252
|
+
/^origin\//,
|
|
1253
|
+
""
|
|
1254
|
+
);
|
|
1255
|
+
const baseRef = await resolveDiffBaseRef(cwd, baseName);
|
|
1256
|
+
await git(["fetch", "origin", baseName], cwd, { reject: false });
|
|
1257
|
+
const result = await git(
|
|
1258
|
+
["merge-tree", "--write-tree", "--name-only", baseRef, "HEAD"],
|
|
1259
|
+
cwd,
|
|
1260
|
+
{ reject: false }
|
|
1261
|
+
);
|
|
1262
|
+
const out = `${result.stdout}
|
|
1263
|
+
${result.stderr}`;
|
|
1264
|
+
const conflicting = result.exitCode !== 0 || /\bCONFLICT\b/i.test(out);
|
|
1265
|
+
const files = conflicting ? [
|
|
1266
|
+
...new Set(
|
|
1267
|
+
out.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("merge-tree:") && !/\bCONFLICT\b/i.test(l))
|
|
1268
|
+
)
|
|
1269
|
+
].slice(0, 20) : [];
|
|
1270
|
+
return { conflicting, base: baseName, files };
|
|
1271
|
+
}
|
|
1272
|
+
async function getPrChecks(cwd, selector) {
|
|
1273
|
+
const slug = await resolveGithubRepoSlug(cwd);
|
|
1274
|
+
const args = [
|
|
1275
|
+
"pr",
|
|
1276
|
+
"checks",
|
|
1277
|
+
selector,
|
|
1278
|
+
"--json",
|
|
1279
|
+
"name,state,bucket,startedAt,completedAt,link,description,workflow"
|
|
1280
|
+
];
|
|
1281
|
+
if (slug) args.push("--repo", slug);
|
|
1282
|
+
const { stdout, exitCode, stderr } = await gh(args, cwd, { reject: false });
|
|
1283
|
+
const errText = stderr.trim();
|
|
1284
|
+
let ciChecks;
|
|
1285
|
+
if (!stdout.trim()) {
|
|
1286
|
+
if (/no pull requests found/i.test(errText)) return null;
|
|
1287
|
+
if (/auth|login|HTTP\s*401|credentials|token/i.test(errText)) {
|
|
1288
|
+
throw new Error(errText);
|
|
1289
|
+
}
|
|
1290
|
+
if (exitCode === 0 || exitCode === 1 || exitCode === 8) {
|
|
1291
|
+
if (exitCode === 1 && errText && !/fail|check/i.test(errText)) {
|
|
1292
|
+
throw new Error(errText);
|
|
1293
|
+
}
|
|
1294
|
+
ciChecks = [];
|
|
1295
|
+
} else {
|
|
1296
|
+
throw new Error(errText || `gh pr checks failed (${exitCode})`);
|
|
1297
|
+
}
|
|
1298
|
+
} else {
|
|
1299
|
+
try {
|
|
1300
|
+
const parsed = JSON.parse(stdout);
|
|
1301
|
+
ciChecks = parsed.map(normalizeCheck);
|
|
1302
|
+
} catch {
|
|
1303
|
+
throw new Error(errText || "gh pr checks returned invalid JSON");
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
let gate = await fetchPrMergeGate(cwd, selector, slug);
|
|
1307
|
+
const mergeable = (gate?.mergeable ?? "").toUpperCase();
|
|
1308
|
+
const mergeState = (gate?.mergeStateStatus ?? "").toUpperCase();
|
|
1309
|
+
const alreadyConflicting = mergeable === "CONFLICTING" || mergeState === "DIRTY";
|
|
1310
|
+
const needsLocalProbe = !alreadyConflicting;
|
|
1311
|
+
if (needsLocalProbe) {
|
|
1312
|
+
try {
|
|
1313
|
+
const local = await detectLocalMergeConflicts(cwd, gate?.baseRefName ?? null);
|
|
1314
|
+
if (local.conflicting) {
|
|
1315
|
+
const fileHint = local.files.length > 0 ? ` Conflicting paths: ${local.files.slice(0, 8).join(", ")}${local.files.length > 8 ? "\u2026" : ""}.` : "";
|
|
1316
|
+
gate = {
|
|
1317
|
+
mergeable: "CONFLICTING",
|
|
1318
|
+
mergeStateStatus: "DIRTY",
|
|
1319
|
+
reviewDecision: gate?.reviewDecision ?? null,
|
|
1320
|
+
baseRefName: local.base,
|
|
1321
|
+
url: gate?.url ?? null
|
|
1322
|
+
};
|
|
1323
|
+
const ciFailed2 = ciChecks.some((c) => c.bucket === "fail");
|
|
1324
|
+
const review2 = (gate.reviewDecision ?? "").toUpperCase();
|
|
1325
|
+
const hasReviewRow2 = review2 === "CHANGES_REQUESTED" || review2 === "REVIEW_REQUIRED";
|
|
1326
|
+
const gateRows2 = buildMergeGateChecks(gate, {
|
|
1327
|
+
suppressGenericBlocked: ciFailed2 || hasReviewRow2
|
|
1328
|
+
}).map(
|
|
1329
|
+
(row) => row.kind === "mergeability" && row.name === "Merge conflicts" ? {
|
|
1330
|
+
...row,
|
|
1331
|
+
description: `${row.description ?? ""}${fileHint}`.trim()
|
|
1332
|
+
} : row
|
|
1333
|
+
);
|
|
1334
|
+
return [...gateRows2, ...ciChecks];
|
|
1335
|
+
}
|
|
1336
|
+
if (gate && (mergeable === "UNKNOWN" || mergeState === "UNKNOWN")) {
|
|
1337
|
+
gate = {
|
|
1338
|
+
...gate,
|
|
1339
|
+
mergeable: gate.mergeable === "UNKNOWN" ? "MERGEABLE" : gate.mergeable,
|
|
1340
|
+
mergeStateStatus: gate.mergeStateStatus === "UNKNOWN" ? "CLEAN" : gate.mergeStateStatus
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
} catch {
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
if (!gate) {
|
|
1347
|
+
return ciChecks;
|
|
1348
|
+
}
|
|
1349
|
+
const ciFailed = ciChecks.some((c) => c.bucket === "fail");
|
|
1350
|
+
const review = (gate.reviewDecision ?? "").toUpperCase();
|
|
1351
|
+
const hasReviewRow = review === "CHANGES_REQUESTED" || review === "REVIEW_REQUIRED";
|
|
1352
|
+
const gateRows = buildMergeGateChecks(gate, {
|
|
1353
|
+
// Avoid duplicating "blocked" when CI failures or review rows already explain it.
|
|
1354
|
+
suppressGenericBlocked: ciFailed || hasReviewRow
|
|
1355
|
+
});
|
|
1356
|
+
return [...gateRows, ...ciChecks];
|
|
1357
|
+
}
|
|
1358
|
+
async function getPrMeta(cwd, selector) {
|
|
1359
|
+
const slug = await resolveGithubRepoSlug(cwd);
|
|
1360
|
+
const viewArgs = [
|
|
1361
|
+
"pr",
|
|
1362
|
+
"view",
|
|
1363
|
+
selector,
|
|
1364
|
+
"--json",
|
|
1365
|
+
"number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName"
|
|
1366
|
+
];
|
|
1367
|
+
if (slug) viewArgs.push("--repo", slug);
|
|
1368
|
+
const { stdout, exitCode, stderr } = await gh(viewArgs, cwd, { reject: false });
|
|
1369
|
+
if (exitCode !== 0 || !stdout.trim()) {
|
|
1370
|
+
if (/no pull requests found/i.test(stderr)) return null;
|
|
1371
|
+
return null;
|
|
1372
|
+
}
|
|
1373
|
+
try {
|
|
1374
|
+
const view = JSON.parse(stdout);
|
|
1375
|
+
return {
|
|
1376
|
+
number: Number(view.number),
|
|
1377
|
+
title: String(view.title ?? ""),
|
|
1378
|
+
url: String(view.url ?? ""),
|
|
1379
|
+
state: String(view.state ?? ""),
|
|
1380
|
+
isDraft: Boolean(view.isDraft),
|
|
1381
|
+
reviewDecision: typeof view.reviewDecision === "string" && view.reviewDecision ? view.reviewDecision : null,
|
|
1382
|
+
baseRefName: String(view.baseRefName ?? ""),
|
|
1383
|
+
headRefName: String(view.headRefName ?? "")
|
|
1384
|
+
};
|
|
1385
|
+
} catch {
|
|
1386
|
+
return null;
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
async function getPrDetails(cwd, selector) {
|
|
1390
|
+
const slug = await resolveGithubRepoSlug(cwd);
|
|
1391
|
+
const viewArgs = [
|
|
1392
|
+
"pr",
|
|
1393
|
+
"view",
|
|
1394
|
+
selector,
|
|
1395
|
+
"--json",
|
|
1396
|
+
[
|
|
1397
|
+
"number",
|
|
1398
|
+
"title",
|
|
1399
|
+
"body",
|
|
1400
|
+
"url",
|
|
1401
|
+
"state",
|
|
1402
|
+
"isDraft",
|
|
1403
|
+
"reviewDecision",
|
|
1404
|
+
"author",
|
|
1405
|
+
"baseRefName",
|
|
1406
|
+
"headRefName",
|
|
1407
|
+
"additions",
|
|
1408
|
+
"deletions",
|
|
1409
|
+
"changedFiles",
|
|
1410
|
+
"comments",
|
|
1411
|
+
"reviews"
|
|
1412
|
+
].join(",")
|
|
1413
|
+
];
|
|
1414
|
+
if (slug) viewArgs.push("--repo", slug);
|
|
1415
|
+
const { stdout, exitCode, stderr } = await gh(viewArgs, cwd, { reject: false });
|
|
1416
|
+
if (exitCode !== 0 || !stdout.trim()) {
|
|
1417
|
+
if (stderr.trim()) {
|
|
1418
|
+
if (/no pull requests found/i.test(stderr)) return null;
|
|
1419
|
+
}
|
|
1420
|
+
return null;
|
|
1421
|
+
}
|
|
1422
|
+
let view;
|
|
1423
|
+
try {
|
|
1424
|
+
view = JSON.parse(stdout);
|
|
1425
|
+
} catch {
|
|
1426
|
+
throw new Error(stderr.trim() || "gh pr view returned invalid JSON");
|
|
1427
|
+
}
|
|
1428
|
+
const author = view.author ?? {};
|
|
1429
|
+
const comments = Array.isArray(view.comments) ? view.comments : [];
|
|
1430
|
+
const reviews = Array.isArray(view.reviews) ? view.reviews : [];
|
|
1431
|
+
return {
|
|
1432
|
+
number: Number(view.number),
|
|
1433
|
+
title: String(view.title ?? ""),
|
|
1434
|
+
body: String(view.body ?? ""),
|
|
1435
|
+
url: String(view.url ?? ""),
|
|
1436
|
+
state: String(view.state ?? ""),
|
|
1437
|
+
isDraft: Boolean(view.isDraft),
|
|
1438
|
+
reviewDecision: typeof view.reviewDecision === "string" && view.reviewDecision ? view.reviewDecision : null,
|
|
1439
|
+
author: { login: author.login ?? "unknown", name: author.name ?? null },
|
|
1440
|
+
baseRefName: String(view.baseRefName ?? ""),
|
|
1441
|
+
headRefName: String(view.headRefName ?? ""),
|
|
1442
|
+
additions: Number(view.additions ?? 0),
|
|
1443
|
+
deletions: Number(view.deletions ?? 0),
|
|
1444
|
+
changedFiles: Number(view.changedFiles ?? 0),
|
|
1445
|
+
// Commits live in Changes; omit from GraphQL to save rate-limit points.
|
|
1446
|
+
commits: [],
|
|
1447
|
+
comments: comments.map((c) => {
|
|
1448
|
+
const row = c;
|
|
1449
|
+
const a = row.author ?? {};
|
|
1450
|
+
return {
|
|
1451
|
+
author: { login: a.login ?? "unknown" },
|
|
1452
|
+
body: String(row.body ?? ""),
|
|
1453
|
+
createdAt: String(row.createdAt ?? "")
|
|
1454
|
+
};
|
|
1455
|
+
}),
|
|
1456
|
+
reviews: reviews.map((r) => {
|
|
1457
|
+
const row = r;
|
|
1458
|
+
const a = row.author ?? {};
|
|
1459
|
+
return {
|
|
1460
|
+
author: { login: a.login ?? "unknown" },
|
|
1461
|
+
state: String(row.state ?? ""),
|
|
1462
|
+
body: String(row.body ?? ""),
|
|
1463
|
+
submittedAt: normalizeGhTime(row.submittedAt)
|
|
1464
|
+
};
|
|
1465
|
+
}),
|
|
1466
|
+
// CI lives in Checks tab via getPrChecks — nesting burned GraphQL points.
|
|
1467
|
+
checks: []
|
|
1468
|
+
};
|
|
1469
|
+
}
|
|
1470
|
+
async function fetchPrHead(repoPath, number, localBranch) {
|
|
1471
|
+
const slug = await resolveGithubRepoSlug(repoPath);
|
|
1472
|
+
const refspec = `+pull/${number}/head:${localBranch}`;
|
|
1473
|
+
const errors = [];
|
|
1474
|
+
const tryFetch = async (remote) => {
|
|
1475
|
+
const result = await git(["fetch", remote, refspec], repoPath, {
|
|
1476
|
+
reject: false
|
|
1477
|
+
});
|
|
1478
|
+
if (result.exitCode === 0) return true;
|
|
1479
|
+
const detail = (result.stderr || result.stdout).trim();
|
|
1480
|
+
if (detail) errors.push(`${remote}: ${detail}`);
|
|
1481
|
+
return false;
|
|
1482
|
+
};
|
|
1483
|
+
let fetched = await tryFetch("origin");
|
|
1484
|
+
if (!fetched && slug) {
|
|
1485
|
+
fetched = await tryFetch(`https://github.com/${slug}.git`);
|
|
1486
|
+
}
|
|
1487
|
+
const localOk = async () => {
|
|
1488
|
+
const verify = await git(["rev-parse", "--verify", localBranch], repoPath, {
|
|
1489
|
+
reject: false
|
|
1490
|
+
});
|
|
1491
|
+
return verify.exitCode === 0;
|
|
1492
|
+
};
|
|
1493
|
+
if (!await localOk()) {
|
|
1494
|
+
const viewArgs = [
|
|
1495
|
+
"pr",
|
|
1496
|
+
"view",
|
|
1497
|
+
String(number),
|
|
1498
|
+
"--json",
|
|
1499
|
+
"headRefOid"
|
|
1500
|
+
];
|
|
1501
|
+
if (slug) viewArgs.push("--repo", slug);
|
|
1502
|
+
const view = await gh(viewArgs, repoPath, { reject: false });
|
|
1503
|
+
let oid = "";
|
|
1504
|
+
if (view.exitCode === 0 && view.stdout.trim()) {
|
|
1505
|
+
try {
|
|
1506
|
+
oid = String(
|
|
1507
|
+
JSON.parse(view.stdout).headRefOid ?? ""
|
|
1508
|
+
).trim();
|
|
1509
|
+
} catch {
|
|
1510
|
+
oid = "";
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
if (oid) {
|
|
1514
|
+
const ensureOid = async (remote) => {
|
|
1515
|
+
const got = await git(["fetch", remote, oid], repoPath, {
|
|
1516
|
+
reject: false
|
|
1517
|
+
});
|
|
1518
|
+
return got.exitCode === 0;
|
|
1519
|
+
};
|
|
1520
|
+
let haveObject = (await git(["cat-file", "-e", `${oid}^{commit}`], repoPath, {
|
|
1521
|
+
reject: false
|
|
1522
|
+
})).exitCode === 0;
|
|
1523
|
+
if (!haveObject) haveObject = await ensureOid("origin");
|
|
1524
|
+
if (!haveObject && slug) {
|
|
1525
|
+
haveObject = await ensureOid(`https://github.com/${slug}.git`);
|
|
1526
|
+
}
|
|
1527
|
+
if (haveObject) {
|
|
1528
|
+
const branched = await git(["branch", "-f", localBranch, oid], repoPath, {
|
|
1529
|
+
reject: false
|
|
1530
|
+
});
|
|
1531
|
+
if (branched.exitCode !== 0) {
|
|
1532
|
+
const detail = (branched.stderr || branched.stdout).trim();
|
|
1533
|
+
if (detail) errors.push(`branch -f: ${detail}`);
|
|
1534
|
+
}
|
|
1535
|
+
} else {
|
|
1536
|
+
errors.push(`could not fetch commit ${oid.slice(0, 12)}`);
|
|
1537
|
+
}
|
|
1538
|
+
} else if (view.stderr.trim()) {
|
|
1539
|
+
errors.push(view.stderr.trim());
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
if (!await localOk()) {
|
|
1543
|
+
const hint = errors.length ? ` (${errors.join(" | ")})` : "";
|
|
1544
|
+
throw new Error(
|
|
1545
|
+
`Failed to fetch PR #${number} head into ${localBranch}${hint}`
|
|
1546
|
+
);
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
async function resolveWorktreeStartPoint(repoPath, sourceRef) {
|
|
1550
|
+
const ref = sourceRef.trim();
|
|
1551
|
+
if (!ref) throw new Error("sourceRef is required");
|
|
1552
|
+
if (ref.startsWith("origin/") || ref.startsWith("refs/")) {
|
|
1553
|
+
const ok = await git(["rev-parse", "--verify", ref], repoPath, {
|
|
1554
|
+
reject: false
|
|
1555
|
+
});
|
|
1556
|
+
if (ok.exitCode === 0) return ref;
|
|
1557
|
+
throw new Error(`Invalid git reference: ${ref}`);
|
|
1558
|
+
}
|
|
1559
|
+
const remote = `origin/${ref}`;
|
|
1560
|
+
const remoteOk = await git(["rev-parse", "--verify", remote], repoPath, {
|
|
1561
|
+
reject: false
|
|
1562
|
+
});
|
|
1563
|
+
if (remoteOk.exitCode === 0) return remote;
|
|
1564
|
+
const localOk = await git(["rev-parse", "--verify", ref], repoPath, {
|
|
1565
|
+
reject: false
|
|
1566
|
+
});
|
|
1567
|
+
if (localOk.exitCode === 0) return ref;
|
|
1568
|
+
throw new Error(`Invalid git reference: ${ref}`);
|
|
1569
|
+
}
|
|
1570
|
+
function isLocalPrFetchBranch(ref) {
|
|
1571
|
+
return /^sideboard-pr-\d+$/.test(ref.trim());
|
|
1572
|
+
}
|
|
1573
|
+
async function createThreadWorktree(opts) {
|
|
1574
|
+
let branchName = `thread/${opts.slug}`;
|
|
1575
|
+
const worktreePath = join(worktreesRoot(opts.repoPath), opts.slug);
|
|
1576
|
+
if (existsSync(worktreePath)) {
|
|
1577
|
+
throw new Error(`Worktree already exists at ${worktreePath}`);
|
|
1578
|
+
}
|
|
1579
|
+
await ensureGhPreferOrigin(opts.repoPath);
|
|
1580
|
+
await git(["fetch", "origin", "--prune"], opts.repoPath, { reject: false });
|
|
1581
|
+
if (!isLocalPrFetchBranch(opts.sourceRef) && !opts.sourceRef.startsWith("origin/") && !opts.sourceRef.startsWith("refs/")) {
|
|
1582
|
+
await git(["fetch", "origin", opts.sourceRef], opts.repoPath, {
|
|
1583
|
+
reject: false
|
|
1584
|
+
});
|
|
1585
|
+
}
|
|
1586
|
+
const startPoint = await resolveWorktreeStartPoint(
|
|
1587
|
+
opts.repoPath,
|
|
1588
|
+
opts.sourceRef
|
|
1589
|
+
);
|
|
1590
|
+
const add = await git(
|
|
1591
|
+
["worktree", "add", "-b", branchName, worktreePath, startPoint],
|
|
1592
|
+
opts.repoPath,
|
|
1593
|
+
{ reject: false }
|
|
1594
|
+
);
|
|
1595
|
+
if (add.exitCode !== 0) {
|
|
1596
|
+
const err = `${add.stderr}
|
|
1597
|
+
${add.stdout}`;
|
|
1598
|
+
if (/already used by worktree|already exists|checked out/i.test(err)) {
|
|
1599
|
+
for (let n = 2; n <= 20; n++) {
|
|
1600
|
+
const alt = `${branchName}-${n}`;
|
|
1601
|
+
const retry = await git(
|
|
1602
|
+
["worktree", "add", "-b", alt, worktreePath, startPoint],
|
|
1603
|
+
opts.repoPath,
|
|
1604
|
+
{ reject: false }
|
|
1605
|
+
);
|
|
1606
|
+
if (retry.exitCode === 0) {
|
|
1607
|
+
branchName = alt;
|
|
1608
|
+
await ensureGhPreferOrigin(worktreePath);
|
|
1609
|
+
return { branchName, worktreePath };
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
throw new Error(
|
|
1614
|
+
`Failed to create worktree: ${add.stderr.trim() || add.stdout.trim() || `exit ${add.exitCode}`}`
|
|
1615
|
+
);
|
|
1616
|
+
}
|
|
1617
|
+
await ensureGhPreferOrigin(worktreePath);
|
|
1618
|
+
return { branchName, worktreePath };
|
|
1619
|
+
}
|
|
1620
|
+
async function removeWorktree(repoPath, worktreePath, opts) {
|
|
1621
|
+
await git(["worktree", "remove", "--force", worktreePath], repoPath, {
|
|
1622
|
+
reject: false
|
|
1623
|
+
});
|
|
1624
|
+
if (opts?.deleteBranch) {
|
|
1625
|
+
await git(["branch", "-D", opts.deleteBranch], repoPath, { reject: false });
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
async function listWorktrees(repoPath) {
|
|
1629
|
+
const { stdout } = await git(
|
|
1630
|
+
["worktree", "list", "--porcelain"],
|
|
1631
|
+
repoPath,
|
|
1632
|
+
{ reject: false }
|
|
1633
|
+
);
|
|
1634
|
+
if (!stdout.trim()) return [];
|
|
1635
|
+
const entries = [];
|
|
1636
|
+
let current = null;
|
|
1637
|
+
for (const line of stdout.split("\n")) {
|
|
1638
|
+
if (line.startsWith("worktree ")) {
|
|
1639
|
+
if (current) entries.push(current);
|
|
1640
|
+
current = { path: line.slice("worktree ".length), branch: null };
|
|
1641
|
+
} else if (line.startsWith("branch ") && current) {
|
|
1642
|
+
current.branch = line.slice("branch ".length).replace(/^refs\/heads\//, "");
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
if (current) entries.push(current);
|
|
1646
|
+
return entries;
|
|
1647
|
+
}
|
|
1648
|
+
async function isDirty(worktreePath) {
|
|
1649
|
+
const { stdout } = await git(["status", "--porcelain"], worktreePath);
|
|
1650
|
+
return stdout.trim().length > 0;
|
|
1651
|
+
}
|
|
1652
|
+
async function currentBranch(worktreePath) {
|
|
1653
|
+
const { stdout } = await git(["rev-parse", "--abbrev-ref", "HEAD"], worktreePath);
|
|
1654
|
+
return stdout.trim();
|
|
1655
|
+
}
|
|
1656
|
+
async function commitAll(worktreePath, message) {
|
|
1657
|
+
const dirty = await isDirty(worktreePath);
|
|
1658
|
+
if (!dirty) return false;
|
|
1659
|
+
await git(["add", "-A"], worktreePath);
|
|
1660
|
+
await git(["commit", "-m", message], worktreePath);
|
|
1661
|
+
return true;
|
|
1662
|
+
}
|
|
1663
|
+
async function pushBranch(worktreePath, branchName) {
|
|
1664
|
+
await git(["push", "-u", "origin", branchName], worktreePath);
|
|
1665
|
+
}
|
|
1666
|
+
async function mergePr(cwd, selector, opts) {
|
|
1667
|
+
const slug = await resolveGithubRepoSlug(cwd);
|
|
1668
|
+
const viewArgs = ["pr", "view", selector, "--json", "url,state,isDraft"];
|
|
1669
|
+
if (slug) viewArgs.push("--repo", slug);
|
|
1670
|
+
const before = await gh(viewArgs, cwd, { reject: false });
|
|
1671
|
+
if (before.exitCode !== 0 || !before.stdout.trim()) {
|
|
1672
|
+
throw new Error(before.stderr.trim() || "Could not load pull request");
|
|
1673
|
+
}
|
|
1674
|
+
let url = "";
|
|
1675
|
+
let isDraft = false;
|
|
1676
|
+
try {
|
|
1677
|
+
const parsed = JSON.parse(before.stdout);
|
|
1678
|
+
url = String(parsed.url ?? "");
|
|
1679
|
+
isDraft = Boolean(parsed.isDraft);
|
|
1680
|
+
if (String(parsed.state ?? "").toUpperCase() === "MERGED") {
|
|
1681
|
+
return { url, state: "MERGED" };
|
|
1682
|
+
}
|
|
1683
|
+
} catch {
|
|
1684
|
+
throw new Error("Could not parse pull request details");
|
|
1685
|
+
}
|
|
1686
|
+
if (isDraft) {
|
|
1687
|
+
const readyArgs = ["pr", "ready", selector];
|
|
1688
|
+
if (slug) readyArgs.push("--repo", slug);
|
|
1689
|
+
const ready = await gh(readyArgs, cwd, { reject: false });
|
|
1690
|
+
if (ready.exitCode !== 0) {
|
|
1691
|
+
throw new Error(
|
|
1692
|
+
ready.stderr.trim() || ready.stdout.trim() || "Could not mark draft pull request as ready"
|
|
1693
|
+
);
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
const method = opts?.method ?? "squash";
|
|
1697
|
+
const mergeFlag = method === "rebase" ? "--rebase" : method === "merge" ? "--merge" : "--squash";
|
|
1698
|
+
const args = ["pr", "merge", selector, mergeFlag, "--delete-branch=false"];
|
|
1699
|
+
if (slug) args.push("--repo", slug);
|
|
1700
|
+
const { exitCode, stderr, stdout } = await gh(args, cwd, { reject: false });
|
|
1701
|
+
if (exitCode !== 0) {
|
|
1702
|
+
throw new Error(stderr.trim() || stdout.trim() || "gh pr merge failed");
|
|
1703
|
+
}
|
|
1704
|
+
const after = await gh(viewArgs, cwd, { reject: false });
|
|
1705
|
+
if (after.exitCode === 0 && after.stdout.trim()) {
|
|
1706
|
+
try {
|
|
1707
|
+
const parsed = JSON.parse(after.stdout);
|
|
1708
|
+
return {
|
|
1709
|
+
url: String(parsed.url ?? url),
|
|
1710
|
+
state: String(parsed.state ?? "MERGED")
|
|
1711
|
+
};
|
|
1712
|
+
} catch {
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
return { url, state: "MERGED" };
|
|
1716
|
+
}
|
|
1717
|
+
async function createOrUpdatePr(worktreePath, opts) {
|
|
1718
|
+
await ensureGhPreferOrigin(worktreePath);
|
|
1719
|
+
const slug = await resolveGithubRepoSlug(worktreePath);
|
|
1720
|
+
if (!slug) {
|
|
1721
|
+
throw new Error(
|
|
1722
|
+
"Could not resolve the origin GitHub repo (owner/name) for this worktree. Check that `git remote get-url origin` points at github.com."
|
|
1723
|
+
);
|
|
1724
|
+
}
|
|
1725
|
+
const repoArgs = ghRepoSelectArgs(slug);
|
|
1726
|
+
const headRef = ghHeadRef(slug, opts.head);
|
|
1727
|
+
const branchOnly = opts.head.trim().replace(/^refs\/heads\//, "");
|
|
1728
|
+
const existing = await gh(
|
|
1729
|
+
[...repoArgs, "pr", "view", branchOnly, "--json", "url", "--jq", ".url"],
|
|
1730
|
+
worktreePath,
|
|
1731
|
+
{ reject: false }
|
|
1732
|
+
);
|
|
1733
|
+
if (existing.exitCode === 0 && existing.stdout.trim() && !opts.web) {
|
|
1734
|
+
const url2 = existing.stdout.trim();
|
|
1735
|
+
await gh(
|
|
1736
|
+
[
|
|
1737
|
+
...repoArgs,
|
|
1738
|
+
"pr",
|
|
1739
|
+
"edit",
|
|
1740
|
+
branchOnly,
|
|
1741
|
+
"--title",
|
|
1742
|
+
opts.title,
|
|
1743
|
+
"--body",
|
|
1744
|
+
opts.body ?? opts.title
|
|
1745
|
+
],
|
|
1746
|
+
worktreePath,
|
|
1747
|
+
{ reject: false }
|
|
1748
|
+
);
|
|
1749
|
+
return url2;
|
|
1750
|
+
}
|
|
1751
|
+
if (opts.web) {
|
|
1752
|
+
const args2 = [
|
|
1753
|
+
...repoArgs,
|
|
1754
|
+
"pr",
|
|
1755
|
+
"create",
|
|
1756
|
+
"--web",
|
|
1757
|
+
"--title",
|
|
1758
|
+
opts.title,
|
|
1759
|
+
"--body",
|
|
1760
|
+
opts.body ?? opts.title,
|
|
1761
|
+
"--base",
|
|
1762
|
+
opts.base,
|
|
1763
|
+
"--head",
|
|
1764
|
+
headRef
|
|
1765
|
+
];
|
|
1766
|
+
if (opts.draft) args2.push("--draft");
|
|
1767
|
+
await gh(args2, worktreePath, { reject: false });
|
|
1768
|
+
const again = await gh(
|
|
1769
|
+
[...repoArgs, "pr", "view", branchOnly, "--json", "url", "--jq", ".url"],
|
|
1770
|
+
worktreePath,
|
|
1771
|
+
{ reject: false }
|
|
1772
|
+
);
|
|
1773
|
+
return again.stdout.trim() || "";
|
|
1774
|
+
}
|
|
1775
|
+
const args = [
|
|
1776
|
+
...repoArgs,
|
|
1777
|
+
"pr",
|
|
1778
|
+
"create",
|
|
1779
|
+
"--title",
|
|
1780
|
+
opts.title,
|
|
1781
|
+
"--body",
|
|
1782
|
+
opts.body ?? opts.title,
|
|
1783
|
+
"--base",
|
|
1784
|
+
opts.base,
|
|
1785
|
+
"--head",
|
|
1786
|
+
headRef
|
|
1787
|
+
];
|
|
1788
|
+
if (opts.draft) args.push("--draft");
|
|
1789
|
+
const created = await gh(args, worktreePath, { reject: false });
|
|
1790
|
+
if (created.exitCode !== 0) {
|
|
1791
|
+
const raw = created.stderr.trim() || created.stdout.trim() || "gh pr create failed";
|
|
1792
|
+
throw new Error(await formatPrCreateFailure(raw, worktreePath, { slug, head: headRef }));
|
|
1793
|
+
}
|
|
1794
|
+
const url = created.stdout.trim().split("\n").find((l) => l.startsWith("http")) ?? created.stdout.trim();
|
|
1795
|
+
return url;
|
|
1796
|
+
}
|
|
1797
|
+
function suggestSlug(source) {
|
|
1798
|
+
const base = slugify(source || "thread");
|
|
1799
|
+
const stamp = Date.now().toString(36).slice(-4);
|
|
1800
|
+
return `${base || "thread"}-${stamp}`;
|
|
1801
|
+
}
|
|
1802
|
+
function sameRepoPath(a, b) {
|
|
1803
|
+
return normalizeWorktreePath(a) === normalizeWorktreePath(b);
|
|
1804
|
+
}
|
|
1805
|
+
function listLocalThreadBranchSlugs(repoPath) {
|
|
1806
|
+
const refsDir = join(repoPath, ".git", "refs", "heads", "thread");
|
|
1807
|
+
if (!existsSync(refsDir)) return [];
|
|
1808
|
+
try {
|
|
1809
|
+
return readdirSync(refsDir).filter((name) => !name.startsWith(".")).map((name) => normalizeTakenSlug(name));
|
|
1810
|
+
} catch {
|
|
1811
|
+
return [];
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
function collectTakenTeamSlugs(repoPath) {
|
|
1815
|
+
const taken = /* @__PURE__ */ new Set();
|
|
1816
|
+
const root = worktreesRoot(repoPath);
|
|
1817
|
+
if (existsSync(root)) {
|
|
1818
|
+
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
1819
|
+
if (entry.isDirectory() && entry.name !== ".DS_Store") {
|
|
1820
|
+
taken.add(normalizeTakenSlug(entry.name));
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
for (const slug of listLocalThreadBranchSlugs(repoPath)) {
|
|
1825
|
+
taken.add(slug);
|
|
1826
|
+
}
|
|
1827
|
+
for (const thread of listThreads({ includeArchived: true })) {
|
|
1828
|
+
if (!sameRepoPath(thread.repoPath, repoPath)) continue;
|
|
1829
|
+
for (const slug of takenSlugsFromThread(thread)) {
|
|
1830
|
+
taken.add(slug);
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
return taken;
|
|
1834
|
+
}
|
|
1835
|
+
function allocateTeamSlug(repoPath) {
|
|
1836
|
+
const taken = collectTakenTeamSlugs(repoPath);
|
|
1837
|
+
for (let attempt = 0; attempt < 32; attempt++) {
|
|
1838
|
+
const team = allocateTeamName(taken);
|
|
1839
|
+
const path = join(worktreesRoot(repoPath), team.slug);
|
|
1840
|
+
if (!existsSync(path)) return team;
|
|
1841
|
+
taken.add(team.slug);
|
|
1842
|
+
}
|
|
1843
|
+
throw new Error("No available soccer team worktree directories left");
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
export {
|
|
1847
|
+
FAMOUS_SOCCER_TEAMS,
|
|
1848
|
+
lookupSoccerTeam,
|
|
1849
|
+
teamSlugFromName,
|
|
1850
|
+
takenSlugsFromThread,
|
|
1851
|
+
allocateTeamName,
|
|
1852
|
+
normalizeWorktreePath,
|
|
1853
|
+
worktreeNameFromPath,
|
|
1854
|
+
isPlaceholderBranch,
|
|
1855
|
+
branchDisplayLabel,
|
|
1856
|
+
threadDisplayLabel,
|
|
1857
|
+
worktreeDisplayLabel,
|
|
1858
|
+
worktreeDisplayLabelForGroup,
|
|
1859
|
+
formatGhLandError,
|
|
1860
|
+
slugify,
|
|
1861
|
+
resolveRepoRoot,
|
|
1862
|
+
parseGithubSlugFromRemoteUrl,
|
|
1863
|
+
resolveGithubRepoSlug,
|
|
1864
|
+
ghRepoSelectArgs,
|
|
1865
|
+
ghHeadRef,
|
|
1866
|
+
ensureGhPreferOrigin,
|
|
1867
|
+
originGhRepoEnv,
|
|
1868
|
+
resolveDefaultBranch,
|
|
1869
|
+
resolveDiffBaseRef,
|
|
1870
|
+
listBranches,
|
|
1871
|
+
listPrs,
|
|
1872
|
+
getPr,
|
|
1873
|
+
resolvePrSelector,
|
|
1874
|
+
detectLocalMergeConflicts,
|
|
1875
|
+
getPrChecks,
|
|
1876
|
+
getPrMeta,
|
|
1877
|
+
getPrDetails,
|
|
1878
|
+
fetchPrHead,
|
|
1879
|
+
resolveWorktreeStartPoint,
|
|
1880
|
+
createThreadWorktree,
|
|
1881
|
+
removeWorktree,
|
|
1882
|
+
listWorktrees,
|
|
1883
|
+
isDirty,
|
|
1884
|
+
currentBranch,
|
|
1885
|
+
commitAll,
|
|
1886
|
+
pushBranch,
|
|
1887
|
+
mergePr,
|
|
1888
|
+
createOrUpdatePr,
|
|
1889
|
+
suggestSlug,
|
|
1890
|
+
collectTakenTeamSlugs,
|
|
1891
|
+
allocateTeamSlug
|
|
1892
|
+
};
|