@vatvaghool/create-ipl-dashboard 0.1.18 → 0.1.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +38 -129
  2. package/package.json +2 -3
  3. package/src/index.mjs +18 -22
  4. package/src/prompts.mjs +20 -112
  5. package/src/scaffold.mjs +23 -35
  6. package/template/README.md +23 -200
  7. package/template/app/api/ops/seed/route.ts +76 -0
  8. package/template/app/api/ops/status/route.ts +0 -2
  9. package/template/app/lib/config.ts +0 -4
  10. package/template/app/lib/matchStatus.ts +0 -12
  11. package/template/app/lib/storage/index.ts +1 -10
  12. package/template/package.json +1 -6
  13. package/template/scripts/dev-simple.js +1 -1
  14. package/template/scripts/dev-welcome.mjs +39 -18
  15. package/template/scripts/seed-league.mjs +0 -55
  16. package/screenshots/ai-roasting.png +0 -0
  17. package/screenshots/captain-board.png +0 -0
  18. package/screenshots/dashboard-overview.png +0 -0
  19. package/screenshots/ledger-table.png +0 -0
  20. package/screenshots/match-scrubber.png +0 -0
  21. package/screenshots/performance-tracker.png +0 -0
  22. package/src/scraper.mjs +0 -79
  23. package/template/app/hooks/dashboardPolling.ts +0 -53
  24. package/template/app/hooks/snapshotCache.ts +0 -47
  25. package/template/app/lib/storage/google-sheets-storage.ts +0 -147
  26. package/template/app/lib/utils/diff.ts +0 -24
  27. package/template/app/lib/utils/time.ts +0 -40
  28. package/template/screenshots/ai-roasting.png +0 -0
  29. package/template/screenshots/captain-board.png +0 -0
  30. package/template/screenshots/dashboard-overview.png +0 -0
  31. package/template/screenshots/ledger-table.png +0 -0
  32. package/template/screenshots/match-scrubber.png +0 -0
  33. package/template/screenshots/performance-tracker.png +0 -0
  34. package/template/tests/coverage-gaps.test.ts +0 -290
  35. package/template/tests/dashboard-polling.test.ts +0 -96
  36. package/template/tests/utils-and-cache.test.ts +0 -267
package/README.md CHANGED
@@ -4,13 +4,16 @@ Scaffold a full-featured IPL fantasy cricket dashboard in seconds.
4
4
 
5
5
  ```bash
6
6
  npx @vatvaghool/create-ipl-dashboard my-league
7
+ cd my-league
8
+ # Edit .env with your MongoDB URI, league URL and POST secret
9
+ npm run dev:simple
7
10
  ```
8
11
 
9
- Follow the prompts to enter your MongoDB URI, fantasy league URL, collection name, and team names — you get a ready-to-run Next.js dashboard with standings charts, performance trackers, AI roasts, and more.
12
+ Open http://localhost:3000
10
13
 
11
14
  ## Usage
12
15
 
13
- ```bash
16
+ ```
14
17
  npx @vatvaghool/create-ipl-dashboard [project-name] [options]
15
18
  ```
16
19
 
@@ -19,155 +22,61 @@ npx @vatvaghool/create-ipl-dashboard [project-name] [options]
19
22
  | Flag | Description |
20
23
  |------|-------------|
21
24
  | `--help`, `-h` | Show help message |
22
- | `--scrape` | Auto-detect team names from the league URL |
23
25
  | `--skip-install` | Skip `npm install` (useful for testing) |
24
26
 
25
- ### Interactive prompts
27
+ ### Prompts
26
28
 
27
29
  | Prompt | Description |
28
30
  |--------|-------------|
29
31
  | Project name | Directory to scaffold into |
30
- | Storage backend | `mongodb` (default) or `google_sheets` |
31
- | MongoDB URI | Your MongoDB connection string (only if MongoDB chosen, press Enter for default) |
32
- | Google Sheet ID | Google Sheet ID (only if google_sheets chosen) |
33
- | Service account email | Google service account email (only if google_sheets chosen) |
34
- | Private key | Google service account private key (only if google_sheets chosen) |
35
- | League URL | The fantasy.iplt20.com league page URL |
36
- | Collection/sheet name | MongoDB collection or Google sheet for this league's data |
37
- | League name | Used as collection/table name in storage |
38
-
39
- Set team names later via `app/data/teams.ts` or use `--scrape` to auto-detect from the league URL during scaffolding.
40
-
41
- ## Screenshots
42
-
43
- ![](https://files.catbox.moe/a83xlk.png)
44
-
45
- *Dashboard overview — full page*
46
-
47
- ![](https://files.catbox.moe/hl0kxd.png)
48
-
49
- *Performance Tracker — Recharts line chart*
50
-
51
- ![](https://files.catbox.moe/k4sw53.png)
52
-
53
- *Captain Board — captain/vice-captain picks*
54
-
55
- ![](https://files.catbox.moe/vt5i8w.png)
32
+ | League name | Used as MongoDB collection name |
33
+ | Team names | Comma-separated with optional owner in parens e.g. `Team A (Alice), Team B (Bob)` (Enter for 8 default teams) |
56
34
 
57
- *Ledger Table — standings with rank shifts and efficiency*
35
+ ### Setup
58
36
 
59
- ![](https://files.catbox.moe/tuvkjo.png)
37
+ After scaffolding, edit `.env` and fill in:
60
38
 
61
- *Match Scrubber — interactive timeline scrubber*
62
-
63
- ![](https://files.catbox.moe/5nfi5i.png)
64
-
65
- *AI Roast Corner — generated commentary*
66
-
67
- ## What you get
68
-
69
- A full Next.js 16 project with:
70
-
71
- - **Leaderboard dashboard** — live standings with rank movements and point gaps
72
- - **Performance charts** — Recharts line charts tracking every team's trajectory
73
- - **Captain board** — captain/vice-captain picks per team
74
- - **Match scrubber** — interactive timeline through match history
75
- - **AI roasting** — generated commentary in multiple languages
76
- - **Stock ticker** — fantasy stocks with sparklines
77
- - **Live updates** — bookmarklet or Playwright scraper for live sync
78
- - **MongoDB persistence** — auto-configured database connection
39
+ ```
40
+ MONGODB_URI=mongodb+srv://...
41
+ IPL_LEAGUE_URL=https://fantasy.iplt20.com/classic/league/view/...
42
+ IPL_POST_SECRET=your-secret
43
+ ```
79
44
 
80
- ## Quick start after scaffold
45
+ Then seed data and start syncing:
81
46
 
82
47
  ```bash
83
- cd my-league
84
-
85
- # Start the dev server
86
- npm run dev:simple
48
+ npm run seed:api # Seed match data into MongoDB
49
+ npm run seed:league # Seed league metadata
50
+ npm run capture:ipl-auth # One-time Playwright login
51
+ npm run sync:ipl:watch # Start live sync
87
52
  ```
88
53
 
89
- Open http://localhost:3000 to see your dashboard.
90
-
91
- ### Available commands
54
+ ## Commands
92
55
 
93
56
  | Command | Description |
94
57
  |---------|-------------|
95
- | `npm run dev` | Dev server with welcome splash |
96
- | `npm run dev:simple` | Dev server (simple, no splash) |
58
+ | `npm run dev:simple` | Start dev server |
97
59
  | `npm run build` | Production build |
98
- | `npm start` | Production server |
99
60
  | `npm run capture:ipl-auth` | Capture Playwright login state (one-time) |
100
61
  | `npm run sync:ipl` | Scrape live leaderboard snapshot |
101
- | `npm run sync:ipl:watch` | Scrape leaderboard in watch mode (polls every 2 min) |
102
- | `npm run sync:ipl:transfers-daily` | Scrape transfer/booster data |
103
- | `npm run sync:cloud` | Run both leaderboard + transfer sync (for cloud jobs) |
104
- | `npm run seed:league` | Seed league metadata into storage |
105
- | `npm run seed:mongodb` | Seed initial raw user data from `data.ts` |
106
- | `npm run seed:mongodb:reset` | Reset and re-seed MongoDB data |
107
- | `npm run verify:production` | Verify production setup |
108
- | `npm run monitor:ops` | Check ops health status |
109
- | `npm run test` | Run test suite |
62
+ | `npm run sync:ipl:watch` | Scrape leaderboard in watch mode |
63
+ | `npm run sync:cloud` | Run all scrapers |
64
+ | `npm run seed:api` | POST /api/ops/seed seed match data into MongoDB |
65
+ | `npm run seed:league` | Seed league metadata into MongoDB |
66
+ | `npm run test` | Run tests |
110
67
  | `npm run lint` | Run linter |
111
68
 
112
- ## How it works
113
-
114
- The CLI:
69
+ ## API
115
70
 
116
- 1. Copies a pre-built Next.js app template
117
- 2. Writes your `.env` with the `MONGODB_URI`, `COLLECTION_NAME`, league URL, and league name
118
- 3. Generates `app/data/teams.ts` with your team roster
119
- 4. Generates `app/data/league.ts` with league metadata
120
- 5. Creates a placeholder `app/data/match-points.ts` (auto-populates as you sync)
121
- 6. Installs dependencies
122
- 7. Runs `seed:league` to create a **document in your specified collection** in the pre-configured database, storing league metadata (name, URL, teams, timestamps)
71
+ | Route | Method | Description |
72
+ |-------|--------|-------------|
73
+ | `/api/ipl` | GET | Dashboard payload |
74
+ | `/api/ipl` | POST | Ingest leaderboard snapshot |
75
+ | `/api/ipl/transfers` | GET/POST | Transfer/booster data |
76
+ | `/api/ipl/upcoming-matches` | GET | Upcoming match schedule |
77
+ | `/api/ops/status` | GET | Health check |
78
+ | `/api/ops/seed` | POST | Seed initial match data into MongoDB |
123
79
 
124
- Each league gets its own collection — run `create-ipl-dashboard` again with a different collection name to add another league.
125
-
126
- The template includes all dashboard components, API endpoints, scrapers, and tests from the [ipl-dashboard](https://github.com/anomalyco/ipl-dashboard) project.
127
-
128
- ---
129
-
130
- ## Next Steps
131
-
132
- ### Adding more leagues
133
-
134
- ```bash
135
- npx @vatvaghool/create-ipl-dashboard another-league
136
- ```
137
-
138
- Provide a different league name and the new league will be stored in its own collection/sheet — data stays fully isolated.
139
-
140
- ### Viewing seeded data
141
-
142
- **MongoDB:** Connect with any MongoDB client. Each league appears as a separate collection with `type: "league"`.
143
-
144
- **Google Sheets:** Open your spreadsheet in a browser. Each league has its own sheet (tab) with league metadata in row 2.
145
-
146
- ### Production deployment
147
-
148
- ```bash
149
- cd my-league
150
- npm run build
151
- npx vercel --prod
152
- ```
153
-
154
- Set `IPL_POST_SECRET` in your Vercel dashboard. Storage credentials are already populated in the scaffolded `.env`.
155
-
156
- ### Data sync workflow
157
-
158
- ```bash
159
- # 1. Capture browser login (one-time)
160
- npm run capture:ipl-auth
161
-
162
- # 2. Scrape leaderboard (manual or watch mode)
163
- npm run sync:ipl
164
- npm run sync:ipl:watch # auto-poll every 2 min
165
-
166
- # 3. Scrape transfer/booster data
167
- npm run sync:ipl:transfers-daily
168
-
169
- # 4. Run all scrapers (for cloud jobs)
170
- npm run sync:cloud
171
- ```
80
+ ## Project
172
81
 
173
- See the [Available commands](#quick-start-after-scaffold) table above for all options.
82
+ A full Next.js 16 project with dashboard components, API endpoints, scrapers, and tests.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vatvaghool/create-ipl-dashboard",
3
- "version": "0.1.18",
3
+ "version": "0.1.23",
4
4
  "description": "Scaffold an IPL fantasy cricket dashboard project",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,8 +8,7 @@
8
8
  },
9
9
  "files": [
10
10
  "src",
11
- "template",
12
- "screenshots"
11
+ "template"
13
12
  ],
14
13
  "publishConfig": {
15
14
  "access": "public"
package/src/index.mjs CHANGED
@@ -3,9 +3,8 @@
3
3
  import { join } from "node:path";
4
4
  import { existsSync } from "node:fs";
5
5
  import { mkdir } from "node:fs/promises";
6
- import { getProjectName, getConnectionUrl, getLeagueUrl, getLeagueName, close } from "./prompts.mjs";
6
+ import { getProjectName, getLeagueName, getTeams, close } from "./prompts.mjs";
7
7
  import { scaffoldProject } from "./scaffold.mjs";
8
- import { scrapeTeamsFromUrl } from "./scraper.mjs";
9
8
 
10
9
  function printHelp(exitCode) {
11
10
  console.log(`
@@ -19,11 +18,9 @@ function printHelp(exitCode) {
19
18
  Options:
20
19
  --help, -h Show this help message
21
20
  --skip-install Skip npm install after scaffolding
22
- --scrape Auto-detect team names from the league URL
23
21
 
24
22
  Examples:
25
23
  npx create-ipl-dashboard my-league
26
- npx create-ipl-dashboard my-league --scrape
27
24
  npx create-ipl-dashboard --skip-install
28
25
  `);
29
26
  process.exit(exitCode);
@@ -31,12 +28,11 @@ function printHelp(exitCode) {
31
28
 
32
29
  async function main() {
33
30
  const args = process.argv.slice(2);
34
- const flags = { scrape: false, skipInstall: false, help: false };
31
+ const flags = { skipInstall: false, help: false };
35
32
  const positional = [];
36
33
 
37
34
  for (const arg of args) {
38
35
  if (arg === "--help" || arg === "-h") flags.help = true;
39
- else if (arg === "--scrape") flags.scrape = true;
40
36
  else if (arg === "--skip-install") flags.skipInstall = true;
41
37
  else if (!arg.startsWith("--")) positional.push(arg);
42
38
  else { console.error(`Unknown flag: ${arg}`); printHelp(1); }
@@ -59,33 +55,33 @@ async function main() {
59
55
 
60
56
  await mkdir(projectPath, { recursive: true });
61
57
 
62
- const conn = await getConnectionUrl();
63
- const leagueUrl = await getLeagueUrl();
64
58
  const leagueName = await getLeagueName();
65
- let teams;
66
-
67
- if (flags.scrape && leagueUrl) {
68
- console.log(" Attempting to scrape team names from league URL...");
69
- teams = await scrapeTeamsFromUrl(leagueUrl);
70
- if (teams && teams.length > 0) {
71
- console.log(` Found ${teams.length} team(s): ${teams.map((t) => t.name).join(", ")}`);
72
- }
59
+
60
+ let teams = await getTeams();
61
+
62
+ if (!teams || teams.length === 0) {
63
+ teams = [
64
+ { id: 1, name: "The Cricketers", owner: "Player 1" },
65
+ { id: 2, name: "Six Hitters", owner: "Player 2" },
66
+ { id: 3, name: "Bowling Titans", owner: "Player 3" },
67
+ { id: 4, name: "Fielding Masters", owner: "Player 4" },
68
+ { id: 5, name: "Night Watchmen", owner: "Player 5" },
69
+ { id: 6, name: "Super Kings", owner: "Player 6" },
70
+ { id: 7, name: "Royal Challengers", owner: "Player 7" },
71
+ { id: 8, name: "Mumbai Indians", owner: "Player 8" },
72
+ ];
73
73
  }
74
74
 
75
75
  close();
76
76
 
77
- await scaffoldProject(projectPath, { conn, leagueUrl, leagueName, teams, skipInstall: flags.skipInstall });
77
+ await scaffoldProject(projectPath, { leagueName, teams, skipInstall: flags.skipInstall });
78
78
 
79
79
  console.log("");
80
80
  console.log(" Next steps:");
81
81
  console.log(` cd ${projectName}`);
82
+ console.log(" Edit .env — set MONGODB_URI, IPL_LEAGUE_URL and IPL_POST_SECRET");
82
83
  console.log(" npm run dev:simple");
83
84
  console.log("");
84
- console.log(" For live data sync, capture auth state:");
85
- console.log(` cd ${projectName}`);
86
- console.log(" npm run capture:ipl-auth");
87
- console.log(" npm run sync:ipl");
88
- console.log("");
89
85
  }
90
86
 
91
87
  main().catch((err) => {
package/src/prompts.mjs CHANGED
@@ -1,6 +1,5 @@
1
1
  import { createInterface } from "node:readline";
2
2
  import { stdin as input, stdout as output, stdin } from "node:process";
3
- import * as readline from "node:readline";
4
3
 
5
4
  const isTTY = stdin.isTTY;
6
5
 
@@ -25,86 +24,6 @@ function prompt(query) {
25
24
  });
26
25
  }
27
26
 
28
- const BACKEND_VALUES = { mongodb: "mongodb", google_sheets: "google_sheets" };
29
-
30
- async function select(message, choices, valueMap) {
31
- if (!isTTY) {
32
- await consumeAllLines();
33
- const answer = (pipedLines[promptIndex++] || "").trim().toLowerCase().replace(/[\s_-]+/g, "");
34
- const match = choices.find((c) => c.toLowerCase().replace(/[\s_-]+/g, "").startsWith(answer));
35
- const idx = match ? choices.indexOf(match) : 0;
36
- return valueMap ? valueMap[Object.keys(valueMap)[idx]] : choices[idx];
37
- }
38
-
39
- return new Promise((resolve) => {
40
- let selected = 0;
41
- const rl = createInterface({ input, output });
42
-
43
- function render(n) {
44
- for (let i = 0; i < n; i++) {
45
- readline.moveCursor(output, 0, -1);
46
- readline.clearLine(output, 0);
47
- }
48
- }
49
-
50
- process.stdout.write(`${message}\n`);
51
- choices.forEach((c, i) => {
52
- process.stdout.write(i === selected ? `\x1B[7m ${c} \x1B[0m\n` : ` ${c}\n`);
53
- });
54
-
55
- const numLines = choices.length;
56
-
57
- readline.emitKeypressEvents(input);
58
- if (input.isTTY) input.setRawMode(true);
59
-
60
- function cleanup() {
61
- input.setRawMode(false);
62
- input.pause();
63
- input.removeListener("keypress", handler);
64
- rl.close();
65
- }
66
-
67
- function handler(str, key) {
68
- if (!key) return;
69
- if (key.name === "up" && selected > 0) {
70
- selected--;
71
- for (let i = 0; i < numLines; i++) readline.moveCursor(output, 0, -1);
72
- choices.forEach((c, i) => {
73
- readline.clearLine(output, 0);
74
- readline.cursorTo(output, 0);
75
- output.write(i === selected ? `\x1B[7m ${c} \x1B[0m` : ` ${c} `);
76
- if (i < numLines - 1) output.write("\n");
77
- });
78
- output.write("\n");
79
- } else if (key.name === "down" && selected < numLines - 1) {
80
- selected++;
81
- for (let i = 0; i < numLines; i++) readline.moveCursor(output, 0, -1);
82
- choices.forEach((c, i) => {
83
- readline.clearLine(output, 0);
84
- readline.cursorTo(output, 0);
85
- output.write(i === selected ? `\x1B[7m ${c} \x1B[0m` : ` ${c} `);
86
- if (i < numLines - 1) output.write("\n");
87
- });
88
- output.write("\n");
89
- } else if (key.name === "return") {
90
- cleanup();
91
- for (let i = 0; i < numLines + 1; i++) {
92
- readline.moveCursor(output, 0, -1);
93
- readline.clearLine(output, 0);
94
- }
95
- output.write(`${message} \x1B[32m${choices[selected]}\x1B[0m\n`);
96
- const keys = Object.keys(valueMap || {});
97
- resolve(valueMap ? valueMap[keys[selected]] : choices[selected]);
98
- } else if (key.name === "c" && key.ctrl) {
99
- cleanup();
100
- process.exit(1);
101
- }
102
- }
103
-
104
- input.on("keypress", handler);
105
- });
106
- }
107
-
108
27
  export async function getProjectName(args) {
109
28
  if (args[0]) return args[0];
110
29
  if (!isTTY) {
@@ -114,47 +33,36 @@ export async function getProjectName(args) {
114
33
  return (await prompt("Project name: ")).trim() || "ipl-dashboard";
115
34
  }
116
35
 
117
- export async function getStorageBackend() {
118
- return select("Storage backend:", ["MongoDB", "Google Sheets"], BACKEND_VALUES);
119
- }
120
-
121
- export async function getConnectionUrl() {
122
- const backend = await getStorageBackend();
123
- if (backend === "google_sheets") {
124
- if (!isTTY) {
125
- await consumeAllLines();
126
- return { backend, sheetId: (pipedLines[promptIndex++] || "").trim(), serviceAccountEmail: (pipedLines[promptIndex++] || "").trim(), privateKey: (pipedLines[promptIndex++] || "").trim() };
127
- }
128
- console.log("\n Google Sheets credentials:\n");
129
- const sheetId = (await prompt(" Google Sheet ID: ")).trim();
130
- const serviceAccountEmail = (await prompt(" Service account email: ")).trim();
131
- const privateKey = (await prompt(" Private key (including -----BEGIN/END-----): ")).trim();
132
- return { backend, sheetId, serviceAccountEmail, privateKey };
133
- }
134
-
135
- const DEFAULT_MONGODB_URI = "mongodb+srv://admin:admin@cricket.ptjjrub.mongodb.net/?appName=cricket";
36
+ export async function getLeagueName() {
136
37
  if (!isTTY) {
137
38
  await consumeAllLines();
138
- return { backend, uri: (pipedLines[promptIndex++] || DEFAULT_MONGODB_URI || "").trim() };
39
+ return (pipedLines[promptIndex++] || "my_league").trim();
139
40
  }
140
- const answer = (await prompt("MongoDB URI (press Enter for default): ")).trim();
141
- return { backend, uri: answer || DEFAULT_MONGODB_URI || "" };
41
+ return (await prompt("League name (used as collection name, e.g. my_office_league): ")).trim() || "my_league";
142
42
  }
143
43
 
144
- export async function getLeagueUrl() {
145
- if (!isTTY) {
146
- await consumeAllLines();
147
- return (pipedLines[promptIndex++] || "").trim();
148
- }
149
- return (await prompt("IPL fantasy league URL: ")).trim();
44
+ function parseTeams(input) {
45
+ return input.split(",").map((part, i) => {
46
+ part = part.trim();
47
+ if (!part) return null;
48
+ const match = part.match(/^(.+?)\s*\(([^)]+)\)$/);
49
+ if (match) {
50
+ return { id: i + 1, name: match[1].trim(), owner: match[2].trim() };
51
+ }
52
+ return { id: i + 1, name: part, owner: part };
53
+ }).filter(Boolean);
150
54
  }
151
55
 
152
- export async function getLeagueName() {
56
+ export async function getTeams() {
153
57
  if (!isTTY) {
154
58
  await consumeAllLines();
155
- return (pipedLines[promptIndex++] || "my_league").trim();
59
+ const raw = (pipedLines[promptIndex++] || "").trim();
60
+ if (!raw) return null;
61
+ return parseTeams(raw);
156
62
  }
157
- return (await prompt("League name (used as collection/table name, e.g. my_office_league): ")).trim() || "my_league";
63
+ const answer = (await prompt("Team names (comma-separated, or press Enter for defaults):\n e.g. Team A (Alice), Team B (Bob)\n> ")).trim();
64
+ if (!answer) return null;
65
+ return parseTeams(answer);
158
66
  }
159
67
 
160
68
  export function close() {}
package/src/scaffold.mjs CHANGED
@@ -10,7 +10,7 @@ const TEMPLATE_DIR = join(PACKAGE_ROOT, "template");
10
10
 
11
11
  export async function scaffoldProject(
12
12
  projectPath,
13
- { conn, leagueUrl, leagueName, teams, skipInstall = false },
13
+ { leagueName, teams, skipInstall = false },
14
14
  ) {
15
15
  console.log(`\nCreating project at ${projectPath}...`);
16
16
 
@@ -33,15 +33,11 @@ export async function scaffoldProject(
33
33
  } catch {}
34
34
  }
35
35
 
36
- await writeEnvFile(projectPath, {
37
- conn,
38
- leagueUrl,
39
- leagueName,
40
- });
36
+ await writeEnvFile(projectPath, { leagueName });
41
37
  await writeTeamData(projectPath, teams || []);
42
- await writeLeagueData(projectPath, { leagueName, leagueUrl, teams: teams || [] });
38
+ await writeLeagueData(projectPath, { leagueName, teams: teams || [] });
43
39
  await writeMatchPointsPlaceholder(projectPath);
44
- await updatePackageJson(projectPath, conn.backend);
40
+ await updatePackageJson(projectPath);
45
41
 
46
42
  for (const f of [
47
43
  "app/api/ipl/live-snapshot.json",
@@ -56,16 +52,11 @@ export async function scaffoldProject(
56
52
  console.log(" Running npm install...");
57
53
  execSync("npm install", { cwd: projectPath, stdio: "inherit" });
58
54
 
59
- if (leagueName) {
60
- console.log(" Seeding league metadata...");
61
- try {
62
- execSync(`npm run seed:league -- "${leagueName}" "${leagueName}"`, {
63
- cwd: projectPath,
64
- stdio: "inherit",
65
- });
66
- } catch {
67
- console.log(" League seed skipped (storage may not be reachable yet)");
68
- }
55
+ console.log(" Installing Playwright browser...");
56
+ try {
57
+ execSync("npx playwright install chromium", { cwd: projectPath, stdio: "inherit" });
58
+ } catch {
59
+ console.log(" Playwright install skipped (run manually: npx playwright install chromium)");
69
60
  }
70
61
  } else {
71
62
  console.log(
@@ -76,27 +67,27 @@ export async function scaffoldProject(
76
67
  console.log("\n Done! Your project is ready at:", projectPath);
77
68
  }
78
69
 
79
- async function writeEnvFile(
80
- projectPath,
81
- { conn, leagueUrl, leagueName },
82
- ) {
70
+ async function writeEnvFile(projectPath, { leagueName }) {
83
71
  const envPath = join(projectPath, ".env");
84
72
  const lines = [
85
- `STORAGE_BACKEND=${conn.backend || "mongodb"}`,
86
73
  `COLLECTION_NAME=${leagueName || "my_league"}`,
87
- conn.backend === "google_sheets" ? `GOOGLE_SHEET_ID=${conn.sheetId || ""}` : `MONGODB_URI=${conn.uri || ""}`,
88
- conn.backend === "google_sheets" ? `GOOGLE_SERVICE_ACCOUNT_EMAIL=${conn.serviceAccountEmail || ""}` : "",
89
- conn.backend === "google_sheets" ? `GOOGLE_PRIVATE_KEY=${conn.privateKey || ""}` : "",
90
- `IPL_LEAGUE_URL=${leagueUrl || ""}`,
74
+ `MONGODB_URI=`,
75
+ `IPL_LEAGUE_URL=`,
91
76
  `IPL_LEAGUE_NAME=${leagueName || ""}`,
92
77
  `IPL_POST_SECRET=`,
93
78
  ``,
79
+ `# Replace the values above, then run:`,
80
+ `# npm run seed:api (seed match data into MongoDB)`,
81
+ `# npm run seed:league (seed league metadata)`,
82
+ `# npm run capture:ipl-auth (one-time login)`,
83
+ `# npm run sync:ipl (scrape live leaderboard)`,
84
+ ``,
94
85
  `IPL_API_BASE_URL=http://localhost:3000`,
95
86
  `IPL_API_LOG=1`,
96
87
  `IPL_API_LOG_PAYLOAD=0`,
97
88
  `IPL_WRITE_SEED_DATA_FILE=1`,
98
89
  ``,
99
- ].filter(Boolean);
90
+ ];
100
91
  await writeFile(envPath, lines.join("\n"));
101
92
  }
102
93
 
@@ -140,7 +131,7 @@ for (const team of TEAMS) {
140
131
  await writeFile(join(dir, "teams.ts"), content);
141
132
  }
142
133
 
143
- async function writeLeagueData(projectPath, { leagueName, leagueUrl, teams }) {
134
+ async function writeLeagueData(projectPath, { leagueName, teams }) {
144
135
  const dir = join(projectPath, "app/data");
145
136
  await mkdir(dir, { recursive: true });
146
137
 
@@ -156,7 +147,7 @@ async function writeLeagueData(projectPath, { leagueName, leagueUrl, teams }) {
156
147
 
157
148
  export const leagueInfo: LeagueInfo = {
158
149
  name: "${leagueName || ""}",
159
- leagueUrl: "${leagueUrl || ""}",
150
+ leagueUrl: "",
160
151
  teams: [
161
152
  ${teamEntries},
162
153
  ],
@@ -182,7 +173,7 @@ async function writeMatchPointsPlaceholder(projectPath) {
182
173
  await writeFile(join(dir, "match-points.ts"), content);
183
174
  }
184
175
 
185
- async function updatePackageJson(projectPath, storageBackend) {
176
+ async function updatePackageJson(projectPath) {
186
177
  const pkgPath = join(projectPath, "package.json");
187
178
  try {
188
179
  const raw = await readFile(pkgPath, "utf8");
@@ -191,10 +182,7 @@ async function updatePackageJson(projectPath, storageBackend) {
191
182
  pkg.private = false;
192
183
  delete pkg.scripts?.["generate:template"];
193
184
  pkg.scripts["seed:league"] = "node scripts/seed-league.mjs";
194
- if (storageBackend === "google_sheets" && !pkg.dependencies.googleapis) {
195
- pkg.dependencies = pkg.dependencies || {};
196
- pkg.dependencies.googleapis = "^140.0.0";
197
- }
185
+ pkg.scripts["seed:api"] = "node --experimental-strip-types -e \"fetch('http://localhost:3000/api/ops/seed',{method:'POST'}).then(r=>r.json()).then(console.log).catch(console.error)\"";
198
186
  await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
199
187
  } catch {}
200
188
  }