create-eigen-game 0.8.1 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -2,22 +2,26 @@
2
2
  import {
3
3
  addContinuousIntegration,
4
4
  applicationId,
5
+ destinationProblem,
5
6
  detectPackageManager,
6
7
  firebaseReadiness,
8
+ insideWorkTree,
7
9
  normaliseTerminalWidth,
8
10
  plainReporter,
9
11
  scaffoldGame
10
- } from "./chunk-DL4LFOFR.js";
12
+ } from "./chunk-45XDHKSP.js";
11
13
 
12
14
  // src/cli.ts
13
- import { basename, resolve } from "path";
15
+ import { readFileSync } from "fs";
16
+ import { basename, dirname, resolve } from "path";
17
+ import { fileURLToPath } from "url";
14
18
  import { parseArgs } from "util";
15
19
  import { cancel, intro, isCI, isTTY, log, note, outro, taskLog } from "@clack/prompts";
16
20
  import color2 from "picocolors";
17
21
 
18
22
  // src/prompt.ts
19
23
  import { TextPrompt } from "@clack/core";
20
- import { confirm, isCancel, S_BAR, S_BAR_END, symbol } from "@clack/prompts";
24
+ import { confirm, isCancel, S_BAR, S_BAR_END, select, symbol } from "@clack/prompts";
21
25
  import color from "picocolors";
22
26
  var ORG = /^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)+$/;
23
27
  var DEFAULT_ORG = "com.example";
@@ -57,7 +61,7 @@ ${bar} ${color.yellow(this.error ?? "")}` : "";
57
61
  const shorter = repeatsGameName(org, game) ? withoutGameName(org) : void 0;
58
62
  if (shorter !== void 0) {
59
63
  const shorten = await confirm({
60
- message: `That gives ${color.yellow(`${org}.${game}`)} \u2014 the game name twice. Use ${color.green(shorter)} instead?`,
64
+ message: `That gives ${color.yellow(`${org}.${game}`)}, the game name twice. Use ${color.green(shorter)} instead?`,
61
65
  initialValue: true,
62
66
  input,
63
67
  output
@@ -67,13 +71,43 @@ ${bar} ${color.yellow(this.error ?? "")}` : "";
67
71
  }
68
72
  return org;
69
73
  }
74
+ async function ask(message, initialValue, hint, io) {
75
+ const input = io.input ?? process.stdin;
76
+ const output = io.output ?? process.stdout;
77
+ output.write(`${bar}
78
+ ${bar} ${color.dim(hint)}
79
+ `);
80
+ const answer = await confirm({ message, initialValue, input, output });
81
+ return isCancel(answer) ? null : answer;
82
+ }
83
+ function askToScaffoldWithoutFirebase(io = {}) {
84
+ return ask("Scaffold anyway, and connect Firebase yourself later?", false, "Everything except the app's sign-in still works, and `firebase:configure` finishes the job.", io);
85
+ }
86
+ function askForGit(io = {}) {
87
+ return ask("Initialise a git repository and commit the scaffold?", true, "Commits once, so your first diff is your first game change.", io);
88
+ }
89
+ function askForWorkflows(io = {}) {
90
+ return ask("Add the GitHub Actions workflows?", false, "Release needs a signing keystore and a Play service account, so this fails until you have both. `add workflows` writes them later.", io);
91
+ }
92
+ async function askForPackageManager(io = {}) {
93
+ const answer = await select({
94
+ message: "Which package manager should the generated scripts use?",
95
+ options: [
96
+ { value: "pnpm", label: "pnpm" },
97
+ { value: "npm", label: "npm" }
98
+ ],
99
+ input: io.input ?? process.stdin,
100
+ output: io.output ?? process.stdout
101
+ });
102
+ return isCancel(answer) ? null : answer;
103
+ }
70
104
 
71
105
  // src/summary.ts
72
106
  function columns(rows) {
73
107
  const width = Math.max(...rows.map(([left]) => left.length));
74
108
  return rows.map(([left, right]) => ` ${left.padEnd(width)} ${right}`);
75
109
  }
76
- function summarise(result, manager, ci) {
110
+ function summarise(result, manager, workflows) {
77
111
  const run = manager === "npm" ? "npm run" : "pnpm";
78
112
  const status = [];
79
113
  if (result.git === "committed") status.push(`Committed as "Scaffold ${result.name}"`);
@@ -99,46 +133,55 @@ function summarise(result, manager, ci) {
99
133
  ])
100
134
  ].join("\n");
101
135
  const footnotes = ["Docs: https://eigeninteractive.com"];
102
- if (!ci) footnotes.unshift("No CI workflows yet: `create-eigen-game add ci` writes them when shipping is the next step.");
136
+ if (!workflows) footnotes.unshift("No CI workflows yet: `create-eigen-game add workflows` writes them when shipping is the next step.");
103
137
  return { status, next, footnotes, headline: `${result.name} is ready, at ${result.root}` };
104
138
  }
105
139
 
106
140
  // src/cli.ts
141
+ var version = JSON.parse(readFileSync(resolve(dirname(fileURLToPath(import.meta.url)), "../package.json"), "utf8")).version;
142
+ var PREREQUISITES = "https://eigeninteractive.com/docs/getting-started/prerequisites";
107
143
  var help = `Usage: create-eigen-game <game-slug> [options]
108
- create-eigen-game add ci [directory] [options]
144
+ create-eigen-game add workflows [directory] [options]
109
145
 
110
146
  Creates one repository containing an EigenInteractive Cloudflare Worker and
111
147
  Flutter app, and commits it. The destination basename is the lowercase
112
148
  kebab-case game slug; the CLI derives the display name, Dart package name, and
113
149
  Dart/TypeScript type prefix from it.
114
150
 
151
+ Every option below answers a question the CLI would otherwise ask. Pass it to
152
+ skip the question; leave it out to be asked. With no terminal to ask on (CI, a
153
+ pipe, an agent session) an unanswered question is an error rather than a
154
+ default quietly chosen for you, and the message prints the command to re-run.
155
+
115
156
  Options:
116
- --ci Also emit the GitHub Actions workflows (off by
117
- default: release.yml needs an upload keystore and a
118
- Play service account, so it fails until both exist)
119
- --org <reverse-domain> Android/iOS organization (default: com.example). The
120
- applicationId is this plus the game name, as in
121
- com.example.my_game. Asked for when omitted.
122
- --no-firebase Do not configure Firebase. Firebase is configured
123
- before the first commit by default, whenever the two
124
- CLIs are installed and signed in; this skips it and
125
- prints the command to run later
157
+ --org <reverse-domain> Android/iOS organization. The applicationId is this
158
+ plus the game name, as in com.example.my_game, and
159
+ Google Play makes it permanent at first upload
126
160
  --firebase-project <id>
127
- The Firebase project to configure against, instead of
128
- being asked. Works without a terminal, which is how
129
- CI drives the step
130
- --no-git Do not initialise a repository or commit the scaffold
131
- --package-manager <pm> npm or pnpm (defaults to the invoking package manager)
161
+ The Firebase project to configure against. Also the
162
+ form that works with no terminal, since otherwise
163
+ FlutterFire asks
164
+ --no-firebase Do not configure Firebase, leaving firebase:configure
165
+ as a step for later
166
+ --git, --no-git Initialise a repository and commit the scaffold
167
+ --workflows, --no-workflows
168
+ Emit the GitHub Actions workflows. release.yml needs
169
+ an upload keystore and a Play service account, so it
170
+ fails on every push until both exist
171
+ --package-manager <pm> npm or pnpm. Taken from the invoking package manager
172
+ when there is one, so this is for global installs
132
173
  -h, --help Show this help
174
+ -v, --version Show the version
133
175
 
134
176
  Commands:
135
- add ci [directory] Add the workflows to an existing project, for when
177
+ add workflows [directory]
178
+ Add the workflows to an existing project, for when
136
179
  you are ready to ship rather than at scaffold time.
137
180
  Defaults to the current directory.`;
138
181
  normaliseTerminalWidth(process.stdout);
139
182
  var decorated = isTTY(process.stdout) && !isCI();
140
183
  var ui = decorated ? {
141
- open: () => intro(color2.inverse(" create-eigen-game ")),
184
+ open: () => intro(color2.inverse(" EigenInteractive ")),
142
185
  info: (message) => log.info(message),
143
186
  warn: (message) => log.warn(message),
144
187
  success: (message) => log.success(message),
@@ -146,7 +189,7 @@ var ui = decorated ? {
146
189
  close: (message) => outro(color2.bold(message)),
147
190
  stop: (message) => cancel(message)
148
191
  } : {
149
- open: () => console.log("\ncreate-eigen-game\n"),
192
+ open: () => console.log("\nEigenInteractive\n"),
150
193
  info: (message) => console.log(message),
151
194
  warn: (message) => console.warn(message),
152
195
  success: (message) => console.log(message),
@@ -163,31 +206,49 @@ function resolveManager(requested) {
163
206
  }
164
207
  return requested;
165
208
  }
166
- function chooseFirebase(disabled, project) {
167
- if (project !== void 0) {
168
- if (project.trim() === "") throw new Error("--firebase-project needs a project id");
169
- return project.trim();
170
- }
171
- if (disabled === true) return false;
172
- if (!process.stdin.isTTY) {
173
- ui.info("No terminal to choose a Firebase project on, so Firebase is left unconfigured.\nName one with --firebase-project <id>, or configure it later.");
174
- return false;
175
- }
176
- const readiness = firebaseReadiness();
177
- if (readiness.ready) return true;
178
- ui.warn(`Skipping Firebase: ${readiness.reason}.
209
+ function greet() {
210
+ ui.open();
211
+ ui.info("A game is one repository with two halves: a Cloudflare Worker that owns the rules,\nand a Flutter app for Android and the web. This writes both, wired together and\nalready playable.");
212
+ ui.info(
213
+ `Firebase signs your players in. The Worker verifies Firebase ID tokens to decide who
214
+ holds a seat, and sends turn notifications through the same project, so a scaffolded
215
+ app throws ${color2.dim("Firebase is not configured")} at launch until one is connected.
179
216
 
180
- ${color2.cyan(readiness.fix)}
217
+ It is free to start, one project serves every game you build, and this connects yours
218
+ as it goes. ${color2.dim(PREREQUISITES)}`
219
+ );
220
+ }
221
+ function firebaseReport(problems) {
222
+ const lines = problems.flatMap((problem) => [` ${problem.reason}`, ` ${color2.cyan(problem.fix)}`]);
223
+ if (problems.some((problem) => problem.fix.startsWith("dart pub"))) {
224
+ lines.push(` ${color2.dim('\u2026then, if your shell cannot find it: export PATH="$PATH":"$HOME/.pub-cache/bin"')}`);
225
+ }
226
+ return lines.join("\n");
227
+ }
228
+ function stopForFirebase(problems) {
229
+ note(`${problems.map((problem) => ` ${color2.cyan(problem.fix)}`).join("\n")}
181
230
 
182
- The scaffold does not need it, and ends by naming the command that finishes the job.`);
183
- return false;
231
+ Then run this again.
232
+ ${color2.dim(PREREQUISITES)}`, "Set up Firebase");
233
+ ui.stop("Nothing was written.");
234
+ process.exitCode = 1;
235
+ }
236
+ function unanswered(directory, missing) {
237
+ const command = ["npx create-eigen-game", directory, ...missing.map((question) => question.suggestion)].join(" ");
238
+ return [`No terminal to ask on, so every answer has to come from a flag. These were not given:`, "", ...missing.map((question) => ` ${question.flags}`), "", "That command, with each default filled in. Read them before you run it:", "", ` ${command}`].join("\n");
239
+ }
240
+ function flagPair(yes, no, name) {
241
+ if (yes === true && no === true) throw new Error(`--${name} and --no-${name} contradict each other`);
242
+ if (yes === true) return true;
243
+ if (no === true) return false;
244
+ return void 0;
184
245
  }
185
246
  function resolveOrg(value) {
186
247
  if (value.trim() === "") return void 0;
187
248
  if (!ORG.test(value)) {
188
249
  throw new Error(`invalid organization: ${value}
189
250
 
190
- Use reverse domain notation \u2014 two or more dot-separated segments of letters, digits and underscores, each starting with a letter, as in ${DEFAULT_ORG} or dev.yourname.games.`);
251
+ Use reverse domain notation: two or more dot-separated segments of letters, digits and underscores, each starting with a letter, as in ${DEFAULT_ORG} or dev.yourname.games.`);
191
252
  }
192
253
  return value;
193
254
  }
@@ -210,7 +271,7 @@ function clackReporter() {
210
271
  }
211
272
  },
212
273
  handOver(label, body) {
213
- log.step(`${label} ${color2.dim("\u2014 FlutterFire takes over here")}`);
274
+ log.step(`${label} ${color2.dim("(FlutterFire takes over here)")}`);
214
275
  interactive = true;
215
276
  try {
216
277
  return body();
@@ -237,13 +298,18 @@ async function main(args) {
237
298
  strict: true,
238
299
  options: {
239
300
  help: { type: "boolean", short: "h" },
240
- ci: { type: "boolean" },
301
+ version: { type: "boolean", short: "v" },
241
302
  org: { type: "string" },
242
- // Declared under the name it is typed with, as `no-git` is: `parseArgs`
243
- // has no boolean negation.
244
- "no-firebase": { type: "boolean" },
245
303
  "firebase-project": { type: "string" },
304
+ // Each negation is declared under the name it is typed with, because
305
+ // `parseArgs` has no boolean negation of its own. `flagPair` puts the
306
+ // two halves back together, and the third state, neither given, is the
307
+ // one that matters: it is what turns the flag into a question.
308
+ "no-firebase": { type: "boolean" },
309
+ git: { type: "boolean" },
246
310
  "no-git": { type: "boolean" },
311
+ workflows: { type: "boolean" },
312
+ "no-workflows": { type: "boolean" },
247
313
  "package-manager": { type: "string" }
248
314
  }
249
315
  });
@@ -251,9 +317,13 @@ async function main(args) {
251
317
  console.log(help);
252
318
  return;
253
319
  }
320
+ if (values.version) {
321
+ console.log(version);
322
+ return;
323
+ }
254
324
  const requestedManager = resolveManager(values["package-manager"]);
255
325
  if (positionals[0] === "add") {
256
- if (positionals[1] !== "ci") {
326
+ if (positionals[1] !== "workflows" && positionals[1] !== "ci") {
257
327
  throw new Error(`unknown subcommand: add ${positionals[1] ?? ""}
258
328
 
259
329
  ${help}`.trim());
@@ -276,34 +346,97 @@ ${help}`);
276
346
  ${help}`);
277
347
  }
278
348
  const directory = positionals[0];
279
- const manager = requestedManager ?? detectPackageManager() ?? "pnpm";
280
- ui.open();
281
349
  const game = basename(resolve(directory));
282
350
  applicationId(directory);
283
- const firebase = chooseFirebase(values["no-firebase"], values["firebase-project"]);
351
+ const occupied = destinationProblem(directory);
352
+ if (occupied !== void 0) throw new Error(occupied);
353
+ greet();
354
+ const interactive = process.stdin.isTTY === true;
355
+ const missing = [];
356
+ const cancelled = () => {
357
+ ui.stop("Nothing was written.");
358
+ process.exitCode = 130;
359
+ };
360
+ const needsFlag = (flags, suggestion, fallback) => {
361
+ missing.push({ flags, suggestion });
362
+ return fallback;
363
+ };
364
+ const project = values["firebase-project"]?.trim();
365
+ if (project === "") throw new Error("--firebase-project needs a project id");
366
+ if (project !== void 0 && values["no-firebase"] === true) {
367
+ throw new Error("--firebase-project and --no-firebase contradict each other");
368
+ }
369
+ let firebase = false;
370
+ if (project !== void 0) {
371
+ firebase = project;
372
+ } else if (values["no-firebase"] !== true) {
373
+ const readiness = firebaseReadiness();
374
+ if (readiness.ready) {
375
+ if (interactive) firebase = true;
376
+ else firebase = needsFlag("--firebase-project <id>, or --no-firebase", "--no-firebase", false);
377
+ } else {
378
+ ui.warn(`Firebase is not set up on this machine:
379
+
380
+ ${firebaseReport(readiness.problems)}`);
381
+ if (!interactive) {
382
+ firebase = needsFlag("--no-firebase, to scaffold without it", "--no-firebase", false);
383
+ } else {
384
+ const carryOn = await askToScaffoldWithoutFirebase();
385
+ if (carryOn === null) return cancelled();
386
+ if (!carryOn) return stopForFirebase(readiness.problems);
387
+ }
388
+ }
389
+ }
284
390
  let org;
285
- if (values.org === void 0 && process.stdin.isTTY) {
391
+ if (values.org !== void 0) {
392
+ org = resolveOrg(values.org);
393
+ } else if (interactive) {
286
394
  const answer = await askForOrg(game.replaceAll("-", "_"), firebase !== false);
287
- if (answer === null) {
288
- ui.stop("Nothing was written.");
289
- process.exitCode = 130;
290
- return;
291
- }
395
+ if (answer === null) return cancelled();
292
396
  org = answer;
293
- } else if (values.org !== void 0) {
294
- org = resolveOrg(values.org);
397
+ } else {
398
+ org = needsFlag("--org <reverse-domain>", `--org ${DEFAULT_ORG}`, void 0);
399
+ }
400
+ let git = flagPair(values.git, values["no-git"], "git");
401
+ if (git === void 0 && insideWorkTree(directory)) git = true;
402
+ if (git === void 0) {
403
+ if (!interactive) git = needsFlag("--git or --no-git", "--git", true);
404
+ else {
405
+ const answer = await askForGit();
406
+ if (answer === null) return cancelled();
407
+ git = answer;
408
+ }
409
+ }
410
+ let workflows = flagPair(values.workflows, values["no-workflows"], "workflows");
411
+ if (workflows === void 0) {
412
+ if (!interactive) workflows = needsFlag("--workflows or --no-workflows", "--no-workflows", false);
413
+ else {
414
+ const answer = await askForWorkflows();
415
+ if (answer === null) return cancelled();
416
+ workflows = answer;
417
+ }
418
+ }
419
+ let manager = requestedManager ?? detectPackageManager();
420
+ if (manager === void 0) {
421
+ if (!interactive) manager = needsFlag("--package-manager <npm|pnpm>", "--package-manager pnpm", "pnpm");
422
+ else {
423
+ const answer = await askForPackageManager();
424
+ if (answer === null) return cancelled();
425
+ manager = answer;
426
+ }
295
427
  }
428
+ if (missing.length > 0) throw new Error(unanswered(directory, missing));
296
429
  ui.success(`applicationId ${color2.bold(applicationId(directory, org))}`);
297
430
  const result = scaffoldGame({
298
431
  directory,
299
432
  org,
300
433
  packageManager: manager,
301
- ci: values.ci,
302
- git: !values["no-git"],
434
+ ci: workflows,
435
+ git,
303
436
  firebase,
304
437
  reporter: clackReporter()
305
438
  });
306
- const { status, next, footnotes, headline } = summarise(result, manager, values.ci === true);
439
+ const { status, next, footnotes, headline } = summarise(result, manager, workflows);
307
440
  for (const line of status) ui.success(line);
308
441
  ui.block(next);
309
442
  for (const line of footnotes) ui.info(line);
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts","../src/prompt.ts","../src/summary.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { basename, resolve } from \"node:path\";\nimport { parseArgs } from \"node:util\";\nimport { cancel, intro, isCI, isTTY, log, note, outro, taskLog } from \"@clack/prompts\";\nimport color from \"picocolors\";\nimport { addContinuousIntegration, applicationId, detectPackageManager, firebaseReadiness, normaliseTerminalWidth, type PackageManager, plainReporter, type Reporter, scaffoldGame } from \"./index.js\";\nimport { askForOrg, DEFAULT_ORG, ORG } from \"./prompt.js\";\nimport { summarise } from \"./summary.js\";\n\nconst help = `Usage: create-eigen-game <game-slug> [options]\n create-eigen-game add ci [directory] [options]\n\nCreates one repository containing an EigenInteractive Cloudflare Worker and\nFlutter app, and commits it. The destination basename is the lowercase\nkebab-case game slug; the CLI derives the display name, Dart package name, and\nDart/TypeScript type prefix from it.\n\nOptions:\n --ci Also emit the GitHub Actions workflows (off by\n default: release.yml needs an upload keystore and a\n Play service account, so it fails until both exist)\n --org <reverse-domain> Android/iOS organization (default: com.example). The\n applicationId is this plus the game name, as in\n com.example.my_game. Asked for when omitted.\n --no-firebase Do not configure Firebase. Firebase is configured\n before the first commit by default, whenever the two\n CLIs are installed and signed in; this skips it and\n prints the command to run later\n --firebase-project <id>\n The Firebase project to configure against, instead of\n being asked. Works without a terminal, which is how\n CI drives the step\n --no-git Do not initialise a repository or commit the scaffold\n --package-manager <pm> npm or pnpm (defaults to the invoking package manager)\n -h, --help Show this help\n\nCommands:\n add ci [directory] Add the workflows to an existing project, for when\n you are ready to ship rather than at scaffold time.\n Defaults to the current directory.`;\n\nnormaliseTerminalWidth(process.stdout);\n\n/**\n * Whether to draw, using clack's own predicates rather than a hand-rolled\n * `isTTY` — the same pair `taskLog` consults internally. A pipe has nowhere to\n * put a redraw, and a CI log is read after the fact, in full, by someone who\n * wants every line a tool printed.\n */\nconst decorated = isTTY(process.stdout) && !isCI();\n\n/**\n * The CLI's voice, in whichever of the two registers this terminal supports.\n *\n * Every message goes through here so the undecorated path cannot drift into\n * being the untested one.\n */\nconst ui = decorated\n ? {\n open: () => intro(color.inverse(\" create-eigen-game \")),\n info: (message: string) => log.info(message),\n warn: (message: string) => log.warn(message),\n success: (message: string) => log.success(message),\n block: (body: string) => note(body, \"Next\"),\n close: (message: string) => outro(color.bold(message)),\n stop: (message: string) => cancel(message),\n }\n : {\n open: () => console.log(\"\\ncreate-eigen-game\\n\"),\n info: (message: string) => console.log(message),\n warn: (message: string) => console.warn(message),\n success: (message: string) => console.log(message),\n block: (body: string) => console.log(`\\n${body}\\n`),\n close: (message: string) => console.log(message),\n stop: (message: string) => console.error(message),\n };\n\nfunction resolveManager(requested: string | undefined): PackageManager | undefined {\n if (requested === undefined) return undefined;\n if (requested !== \"npm\" && requested !== \"pnpm\") {\n throw new Error(\"--package-manager must be npm or pnpm\");\n }\n return requested;\n}\n\n/**\n * Whether this run configures Firebase, and against which project.\n *\n * On by default, because a scaffold that stops short of it is not a runnable\n * app: `firebase_options.dart` is a throwing placeholder until this has run,\n * so the first `flutter run` ends at `Firebase is not configured`. Doing it\n * before the scaffold commit also keeps the six files it writes out of the\n * project's first diff.\n *\n * Never at the cost of the scaffold, though. Everything that would make the\n * step fail — no CLIs, no login, no terminal to answer the project prompt on —\n * turns it off and says so, here, before the two minutes of Flutter and pub\n * rather than after them. What is left is exactly a `--no-firebase` scaffold,\n * and the summary ends by naming the command that finishes the job.\n */\nfunction chooseFirebase(disabled: boolean | undefined, project: string | undefined): boolean | string {\n // An explicitly named project settles it: FlutterFire has nothing to ask, so\n // this is also the form that works on a machine with no terminal.\n if (project !== undefined) {\n if (project.trim() === \"\") throw new Error(\"--firebase-project needs a project id\");\n return project.trim();\n }\n if (disabled === true) return false;\n\n if (!process.stdin.isTTY) {\n ui.info(\"No terminal to choose a Firebase project on, so Firebase is left unconfigured.\\nName one with --firebase-project <id>, or configure it later.\");\n return false;\n }\n\n const readiness = firebaseReadiness();\n if (readiness.ready) return true;\n\n ui.warn(`Skipping Firebase: ${readiness.reason}.\\n\\n ${color.cyan(readiness.fix)}\\n\\nThe scaffold does not need it, and ends by naming the command that finishes the job.`);\n return false;\n}\n\n/** `--org \"\"` falls through to the default rather than failing, matching what an empty answer to the prompt does. */\nfunction resolveOrg(value: string): string | undefined {\n if (value.trim() === \"\") return undefined;\n if (!ORG.test(value)) {\n throw new Error(`invalid organization: ${value}\\n\\nUse reverse domain notation — two or more dot-separated segments of letters, digits and underscores, each starting with a letter, as in ${DEFAULT_ORG} or dev.yourname.games.`);\n }\n return value;\n}\n\n/**\n * Shows each step running, and keeps its output only when it fails.\n *\n * Every tool a scaffold drives has plenty to say — pub resolving 179\n * dependencies, two icon generators, a package manager — and none of it is\n * read while it scrolls past. `taskLog` is exactly that bargain: the stream is\n * visible while the step runs, cleared when it succeeds, and left on screen\n * when it does not, so a failure is still debuggable from what is in the\n * terminal.\n *\n * `handOver` is the exception, and the reason `interactive` exists at all.\n * FlutterFire asks which Firebase project to use, so that step needs the\n * terminal rather than a captured pipe.\n */\nfunction clackReporter(): Reporter {\n // `taskLog` redraws by clearing lines, which is an escape-sequence storm in\n // anything that is not a terminal — a CI log, a pipe, `scaffold-e2e.mjs`.\n // There, the honest thing is the plain reporter: every tool's output, in\n // full, which is what a log gets read for anyway.\n if (!decorated) return plainReporter;\n\n let task: ReturnType<typeof taskLog> | undefined;\n let interactive = false;\n\n return {\n step(label, body) {\n task = taskLog({ title: label });\n try {\n const value = body();\n task.success(label);\n return value;\n } catch (error) {\n task.error(label);\n throw error;\n } finally {\n task = undefined;\n }\n },\n handOver(label, body) {\n log.step(`${label} ${color.dim(\"— FlutterFire takes over here\")}`);\n interactive = true;\n try {\n return body();\n } finally {\n interactive = false;\n }\n },\n emit(output) {\n const text = output.trimEnd();\n if (text !== \"\") task?.message(text);\n },\n warn(message) {\n ui.warn(message);\n },\n get interactive() {\n return interactive;\n },\n };\n}\n\nasync function main(args: string[]): Promise<void> {\n const { values, positionals } = parseArgs({\n args,\n allowPositionals: true,\n strict: true,\n options: {\n help: { type: \"boolean\", short: \"h\" },\n ci: { type: \"boolean\" },\n org: { type: \"string\" },\n // Declared under the name it is typed with, as `no-git` is: `parseArgs`\n // has no boolean negation.\n \"no-firebase\": { type: \"boolean\" },\n \"firebase-project\": { type: \"string\" },\n \"no-git\": { type: \"boolean\" },\n \"package-manager\": { type: \"string\" },\n },\n });\n\n if (values.help) {\n console.log(help);\n return;\n }\n\n const requestedManager = resolveManager(values[\"package-manager\"]);\n\n if (positionals[0] === \"add\") {\n if (positionals[1] !== \"ci\") {\n throw new Error(`unknown subcommand: add ${positionals[1] ?? \"\"}\\n\\n${help}`.trim());\n }\n if (positionals.length > 3) {\n throw new Error(`expected at most one directory\\n\\n${help}`);\n }\n const result = addContinuousIntegration({\n directory: positionals[2] ?? process.cwd(),\n packageManager: requestedManager,\n });\n console.log(`Added ${result.files.join(\" and \")} in ${result.root}`);\n return;\n }\n\n if (positionals.length !== 1) {\n throw new Error(`expected exactly one destination directory\\n\\n${help}`);\n }\n\n const directory = positionals[0];\n const manager = requestedManager ?? detectPackageManager() ?? \"pnpm\";\n\n ui.open();\n\n // Both decisions about what this run will do, settled before any of it\n // starts. `applicationId` validates the slug on the way, so a destination\n // that cannot be a game name fails here rather than after the first prompt.\n const game = basename(resolve(directory));\n applicationId(directory);\n const firebase = chooseFirebase(values[\"no-firebase\"], values[\"firebase-project\"]);\n\n let org: string | undefined;\n if (values.org === undefined && process.stdin.isTTY) {\n const answer = await askForOrg(game.replaceAll(\"-\", \"_\"), firebase !== false);\n if (answer === null) {\n ui.stop(\"Nothing was written.\");\n process.exitCode = 130;\n return;\n }\n org = answer;\n } else if (values.org !== undefined) {\n org = resolveOrg(values.org);\n }\n\n ui.success(`applicationId ${color.bold(applicationId(directory, org))}`);\n\n const result = scaffoldGame({\n directory,\n org,\n packageManager: manager,\n ci: values.ci,\n git: !values[\"no-git\"],\n firebase,\n reporter: clackReporter(),\n });\n\n const { status, next, footnotes, headline } = summarise(result, manager, values.ci === true);\n for (const line of status) ui.success(line);\n ui.block(next);\n for (const line of footnotes) ui.info(line);\n ui.close(headline);\n}\n\ntry {\n await main(process.argv.slice(2));\n} catch (error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n ui.stop(`create-eigen-game: ${message}`);\n process.exitCode = 1;\n}\n","/**\n * The organization prompt, which is the one question this CLI asks and the one\n * answer that is expensive to get wrong.\n *\n * Its own module because it drops to `@clack/core` rather than using\n * `@clack/prompts`' `text`. The reason is the suffix: `flutter create --org X\n * --project-name Y` derives `X.Y`, so the organization is a *prefix*, and the\n * reliable way to say so is to render the rest of the identifier, dimmed,\n * while it is being typed. `text` has no hook for that; a custom `TextPrompt`\n * render returns a string, so it does.\n *\n * Everything else comes from `@clack/prompts` rather than being drawn here.\n * Its symbols are exported for exactly this, and they are not constants: each\n * falls back to ASCII on a terminal that cannot show the box-drawing\n * characters, which a hand-typed `│` cannot do.\n */\nimport type { Readable, Writable } from \"node:stream\";\nimport { TextPrompt } from \"@clack/core\";\nimport { confirm, isCancel, S_BAR, S_BAR_END, symbol } from \"@clack/prompts\";\nimport color from \"picocolors\";\n\n/**\n * Two or more dot-separated Java identifiers. `flutter create --org` accepts\n * anything and defers the complaint to Gradle, which reports it as a manifest\n * error in a generated file — so `com.example-games` costs a full scaffold and\n * a first build before anyone learns that a hyphen is not legal in a package\n * segment.\n */\nexport const ORG = /^[A-Za-z_][A-Za-z0-9_]*(\\.[A-Za-z_][A-Za-z0-9_]*)+$/;\n\n/** The default, which an empty answer accepts. */\nexport const DEFAULT_ORG = \"com.example\";\n\nconst bar = color.gray(S_BAR);\n\n/** A `com.acme.chess` answer to a game called `chess`, which yields `com.acme.chess.chess`. */\nexport function repeatsGameName(org: string, game: string): boolean {\n return org.split(\".\").at(-1) === game;\n}\n\n/** The organization implied by an answer that already ends in the game name. */\nexport function withoutGameName(org: string): string | undefined {\n const shorter = org.split(\".\").slice(0, -1).join(\".\");\n return ORG.test(shorter) ? shorter : undefined;\n}\n\n/**\n * Asks, with the game name rendered dimmed after the cursor so the shape of\n * the answer is visible while it is being given.\n *\n * Returns the organization, `undefined` for the default, or `null` when\n * cancelled.\n *\n * `input` and `output` default to this process's, and are parameters for the\n * same reason every clack prompt takes them: a prompt is otherwise the one\n * part of a CLI that cannot be driven by a test.\n */\nexport async function askForOrg(game: string, registering: boolean, io: { input?: Readable; output?: Writable } = {}): Promise<string | undefined | null> {\n const input = io.input ?? process.stdin;\n const output = io.output ?? process.stdout;\n const suffix = color.dim(`.${game}`);\n const prompt = new TextPrompt({\n input,\n output,\n // An empty answer is the default, not a mistake.\n validate: (value) => (value === undefined || value === \"\" || ORG.test(value) ? undefined : \"Two or more dot-separated segments, as in com.example or dev.yourname.games.\"),\n render() {\n const typed = String(this.value ?? \"\");\n const shown = typed === \"\" ? `${color.dim(DEFAULT_ORG)}${suffix}` : `${this.userInputWithCursor}${suffix}`;\n const problem = this.state === \"error\" ? `\\n${bar} ${color.yellow(this.error ?? \"\")}` : \"\";\n return [`${bar}`, `${symbol(this.state)} Organization in reverse domain notation`, `${bar} ${shown}${problem}`, color.gray(S_BAR_END)].join(\"\\n\");\n },\n });\n\n // The two things that make this answer permanent, said before it is given\n // rather than after: Google Play freezes the identifier at the first upload,\n // and FlutterFire matches an existing Android app on exactly this string, so\n // it also decides whether the run adopts an app the chosen Firebase project\n // already has or registers a new one.\n output.write(`${bar} ${color.dim(\"Prefixes the Android applicationId, which Google Play makes permanent at first upload.\")}\\n`);\n if (registering) output.write(`${bar} ${color.dim(\"Also the Android app registered in the Firebase project you pick next.\")}\\n`);\n\n const answer = await prompt.prompt();\n if (isCancel(answer)) return null;\n\n const org = String(answer).trim();\n if (org === \"\" || org === DEFAULT_ORG) return undefined;\n\n // The mistake the dimmed suffix is there to prevent, caught for the person\n // who pasted an identifier rather than typing one and never watched it grow.\n const shorter = repeatsGameName(org, game) ? withoutGameName(org) : undefined;\n if (shorter !== undefined) {\n const shorten = await confirm({\n message: `That gives ${color.yellow(`${org}.${game}`)} — the game name twice. Use ${color.green(shorter)} instead?`,\n initialValue: true,\n input,\n output,\n });\n if (isCancel(shorten)) return null;\n if (shorten) return shorter;\n }\n\n return org;\n}\n","/**\n * The closing summary, and the only output of a scaffold anyone reads.\n *\n * A run drives several tools with plenty to say, so where the project went,\n * what to edit, and what to run are worth restating at the bottom. Returned in\n * pieces rather than as one block, because the CLI renders each in a different\n * shape: the outcomes as status lines, the commands as a boxed note, the\n * destination as the closing line.\n *\n * Its own module so it can be asserted: `cli.ts` is the bin entry and runs\n * `main` on import, which makes anything defined there untestable. The package\n * `exports` map does not expose this, so it is internal despite being\n * importable by the tests.\n */\nimport type { PackageManager, ScaffoldResult } from \"./index.js\";\n\nexport interface Summary {\n /** What became of the repository and of Firebase, when there is something to say. */\n status: string[];\n /** The commands worth having, as one block. */\n next: string;\n /** One-line asides, after the block. */\n footnotes: string[];\n /** The closing line. */\n headline: string;\n}\n\n/** Two-column rows, aligned to the widest left cell. */\nfunction columns(rows: [string, string][]): string[] {\n const width = Math.max(...rows.map(([left]) => left.length));\n return rows.map(([left, right]) => ` ${left.padEnd(width)} ${right}`);\n}\n\nexport function summarise(result: ScaffoldResult, manager: PackageManager, ci: boolean): Summary {\n const run = manager === \"npm\" ? \"npm run\" : \"pnpm\";\n const status: string[] = [];\n\n // Only the outcomes that leave the reader something to know. `failed` has\n // already warned, in more detail than a summary line could carry.\n if (result.git === \"committed\") status.push(`Committed as \"Scaffold ${result.name}\"`);\n if (result.git === \"existing\") status.push(\"Created inside a repository you already had, so nothing was committed\");\n // `failed` has warned already, and the step reappears below as the next\n // thing to run, which is the whole of the advice.\n if (result.firebase === \"configured\") status.push(result.git === \"committed\" ? \"Firebase is configured, and everything it generated is in that commit\" : \"Firebase is configured\");\n\n const next = [\n \"Write the game\",\n ...columns([\n [\"server/src/module/v1.ts\", \"the authoritative rules\"],\n [\"app/lib/game/v1/rules.dart\", \"the presentation twin\"],\n ]),\n \"\",\n `Then, from ${result.name}/`,\n ...columns([\n [`cd server && ${run} test:watch`, \"rules and twin fixtures\"],\n [`cd server && ${run} dev`, \"the Worker, on http://localhost:8787\"],\n [`${run} contract`, \"regenerate after a schema change\"],\n // Named here because it is the one step the game itself cannot do\n // without: rules, fixtures and the Worker all run unconfigured, and the\n // app throws `Firebase is not configured` at launch until this has run\n // once.\n ...(result.firebase === \"configured\" ? [] : [[`${run} firebase:configure`, \"connect Firebase before the app runs\"] as [string, string]]),\n ]),\n ].join(\"\\n\");\n\n const footnotes = [\"Docs: https://eigeninteractive.com\"];\n if (!ci) footnotes.unshift(\"No CI workflows yet: `create-eigen-game add ci` writes them when shipping is the next step.\");\n\n return { status, next, footnotes, headline: `${result.name} is ready, at ${result.root}` };\n}\n"],"mappings":"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","names":["color","color","result"]}
1
+ {"version":3,"sources":["../src/cli.ts","../src/prompt.ts","../src/summary.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { readFileSync } from \"node:fs\";\nimport { basename, dirname, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { parseArgs } from \"node:util\";\nimport { cancel, intro, isCI, isTTY, log, note, outro, taskLog } from \"@clack/prompts\";\nimport color from \"picocolors\";\nimport { addContinuousIntegration, applicationId, destinationProblem, detectPackageManager, type FirebaseProblem, firebaseReadiness, insideWorkTree, normaliseTerminalWidth, type PackageManager, plainReporter, type Reporter, scaffoldGame } from \"./index.js\";\nimport { askForGit, askForOrg, askForPackageManager, askForWorkflows, askToScaffoldWithoutFirebase, DEFAULT_ORG, ORG } from \"./prompt.js\";\nimport { summarise } from \"./summary.js\";\n\nconst version = (JSON.parse(readFileSync(resolve(dirname(fileURLToPath(import.meta.url)), \"../package.json\"), \"utf8\")) as { version: string }).version;\n\nconst PREREQUISITES = \"https://eigeninteractive.com/docs/getting-started/prerequisites\";\n\nconst help = `Usage: create-eigen-game <game-slug> [options]\n create-eigen-game add workflows [directory] [options]\n\nCreates one repository containing an EigenInteractive Cloudflare Worker and\nFlutter app, and commits it. The destination basename is the lowercase\nkebab-case game slug; the CLI derives the display name, Dart package name, and\nDart/TypeScript type prefix from it.\n\nEvery option below answers a question the CLI would otherwise ask. Pass it to\nskip the question; leave it out to be asked. With no terminal to ask on (CI, a\npipe, an agent session) an unanswered question is an error rather than a\ndefault quietly chosen for you, and the message prints the command to re-run.\n\nOptions:\n --org <reverse-domain> Android/iOS organization. The applicationId is this\n plus the game name, as in com.example.my_game, and\n Google Play makes it permanent at first upload\n --firebase-project <id>\n The Firebase project to configure against. Also the\n form that works with no terminal, since otherwise\n FlutterFire asks\n --no-firebase Do not configure Firebase, leaving firebase:configure\n as a step for later\n --git, --no-git Initialise a repository and commit the scaffold\n --workflows, --no-workflows\n Emit the GitHub Actions workflows. release.yml needs\n an upload keystore and a Play service account, so it\n fails on every push until both exist\n --package-manager <pm> npm or pnpm. Taken from the invoking package manager\n when there is one, so this is for global installs\n -h, --help Show this help\n -v, --version Show the version\n\nCommands:\n add workflows [directory]\n Add the workflows to an existing project, for when\n you are ready to ship rather than at scaffold time.\n Defaults to the current directory.`;\n\nnormaliseTerminalWidth(process.stdout);\n\n/**\n * Whether to draw, using clack's own predicates rather than a hand-rolled\n * `isTTY`, the same pair `taskLog` consults internally. A pipe has nowhere to\n * put a redraw, and a CI log is read after the fact, in full, by someone who\n * wants every line a tool printed.\n */\nconst decorated = isTTY(process.stdout) && !isCI();\n\n/**\n * The CLI's voice, in whichever of the two registers this terminal supports.\n *\n * Every message goes through here so the undecorated path cannot drift into\n * being the untested one.\n */\nconst ui = decorated\n ? {\n open: () => intro(color.inverse(\" EigenInteractive \")),\n info: (message: string) => log.info(message),\n warn: (message: string) => log.warn(message),\n success: (message: string) => log.success(message),\n block: (body: string) => note(body, \"Next\"),\n close: (message: string) => outro(color.bold(message)),\n stop: (message: string) => cancel(message),\n }\n : {\n open: () => console.log(\"\\nEigenInteractive\\n\"),\n info: (message: string) => console.log(message),\n warn: (message: string) => console.warn(message),\n success: (message: string) => console.log(message),\n block: (body: string) => console.log(`\\n${body}\\n`),\n close: (message: string) => console.log(message),\n stop: (message: string) => console.error(message),\n };\n\nfunction resolveManager(requested: string | undefined): PackageManager | undefined {\n if (requested === undefined) return undefined;\n if (requested !== \"npm\" && requested !== \"pnpm\") {\n throw new Error(\"--package-manager must be npm or pnpm\");\n }\n return requested;\n}\n\n/**\n * Why a scaffold asks about a second service at all, said once, before the\n * first question rather than in the error that follows it.\n *\n * Firebase is not an integration this engine offers; it is where a player\n * comes from. Somebody meeting that for the first time deserves the reason in\n * the same breath as the requirement, and deserves to know the size of it:\n * one free project, shared by every game they build.\n */\nfunction greet(): void {\n ui.open();\n ui.info(\"A game is one repository with two halves: a Cloudflare Worker that owns the rules,\\nand a Flutter app for Android and the web. This writes both, wired together and\\nalready playable.\");\n ui.info(\n `Firebase signs your players in. The Worker verifies Firebase ID tokens to decide who\\nholds a seat, and sends turn notifications through the same project, so a scaffolded\\napp throws ${color.dim(\"Firebase is not configured\")} at launch until one is connected.\\n\\nIt is free to start, one project serves every game you build, and this connects yours\\nas it goes. ${color.dim(PREREQUISITES)}`,\n );\n}\n\n/** What is missing, and the one command that fixes each: the same list the warning and the parting message are both built from. */\nfunction firebaseReport(problems: FirebaseProblem[]): string {\n const lines = problems.flatMap((problem) => [` ${problem.reason}`, ` ${color.cyan(problem.fix)}`]);\n // `dart pub global activate` does not put the binary on PATH, which is the\n // next thing to go wrong for anyone who has just run the command above.\n if (problems.some((problem) => problem.fix.startsWith(\"dart pub\"))) {\n lines.push(` ${color.dim('…then, if your shell cannot find it: export PATH=\"$PATH\":\"$HOME/.pub-cache/bin\"')}`);\n }\n return lines.join(\"\\n\");\n}\n\n/**\n * The parting message when someone answers \"no\" to scaffolding without\n * Firebase: every command they need, in the order they run in.\n *\n * Deliberately not a failure of the scaffold: nothing has been written, they\n * asked for that, and the exit code is non-zero only so a script that wrapped\n * this can tell there is no project.\n */\nfunction stopForFirebase(problems: FirebaseProblem[]): void {\n // The commands only. What each one is for is in the warning directly above,\n // and a `note` draws a box sized to its longest line, so repeating the\n // reasons here is what makes it wrap into something unreadable at 80\n // columns, which is exactly the width this is most likely to be read at.\n note(`${problems.map((problem) => ` ${color.cyan(problem.fix)}`).join(\"\\n\")}\\n\\nThen run this again.\\n${color.dim(PREREQUISITES)}`, \"Set up Firebase\");\n ui.stop(\"Nothing was written.\");\n process.exitCode = 1;\n}\n\n/**\n * The error for a run with no terminal and an unanswered question.\n *\n * The strictness is the point: a default applied where nobody can see it is\n * how `com.example.my_game` reaches Google Play, which freezes it at the first\n * upload. What keeps that from being merely annoying is this message: the\n * whole command, with every default already filled in, so the fix is one\n * paste and the one value worth changing is visible in it.\n */\nfunction unanswered(directory: string, missing: { flags: string; suggestion: string }[]): string {\n const command = [\"npx create-eigen-game\", directory, ...missing.map((question) => question.suggestion)].join(\" \");\n return [`No terminal to ask on, so every answer has to come from a flag. These were not given:`, \"\", ...missing.map((question) => ` ${question.flags}`), \"\", \"That command, with each default filled in. Read them before you run it:\", \"\", ` ${command}`].join(\"\\n\");\n}\n\n/** `--foo` and `--no-foo`, which `parseArgs` has no notion of: `undefined` means nobody said, which is what makes it a question. */\nfunction flagPair(yes: boolean | undefined, no: boolean | undefined, name: string): boolean | undefined {\n if (yes === true && no === true) throw new Error(`--${name} and --no-${name} contradict each other`);\n if (yes === true) return true;\n if (no === true) return false;\n return undefined;\n}\n\n/** `--org \"\"` falls through to the default rather than failing, matching what an empty answer to the prompt does. */\nfunction resolveOrg(value: string): string | undefined {\n if (value.trim() === \"\") return undefined;\n if (!ORG.test(value)) {\n throw new Error(`invalid organization: ${value}\\n\\nUse reverse domain notation: two or more dot-separated segments of letters, digits and underscores, each starting with a letter, as in ${DEFAULT_ORG} or dev.yourname.games.`);\n }\n return value;\n}\n\n/**\n * Shows each step running, and keeps its output only when it fails.\n *\n * Every tool a scaffold drives has plenty to say (pub resolving 179\n * dependencies, two icon generators, a package manager) and none of it is\n * read while it scrolls past. `taskLog` is exactly that bargain: the stream is\n * visible while the step runs, cleared when it succeeds, and left on screen\n * when it does not, so a failure is still debuggable from what is in the\n * terminal.\n *\n * `handOver` is the exception, and the reason `interactive` exists at all.\n * FlutterFire asks which Firebase project to use, so that step needs the\n * terminal rather than a captured pipe.\n */\nfunction clackReporter(): Reporter {\n // `taskLog` redraws by clearing lines, which is an escape-sequence storm in\n // anything that is not a terminal: a CI log, a pipe, `scaffold-e2e.mjs`.\n // There, the honest thing is the plain reporter: every tool's output, in\n // full, which is what a log gets read for anyway.\n if (!decorated) return plainReporter;\n\n let task: ReturnType<typeof taskLog> | undefined;\n let interactive = false;\n\n return {\n step(label, body) {\n task = taskLog({ title: label });\n try {\n const value = body();\n task.success(label);\n return value;\n } catch (error) {\n task.error(label);\n throw error;\n } finally {\n task = undefined;\n }\n },\n handOver(label, body) {\n log.step(`${label} ${color.dim(\"(FlutterFire takes over here)\")}`);\n interactive = true;\n try {\n return body();\n } finally {\n interactive = false;\n }\n },\n emit(output) {\n const text = output.trimEnd();\n if (text !== \"\") task?.message(text);\n },\n warn(message) {\n ui.warn(message);\n },\n get interactive() {\n return interactive;\n },\n };\n}\n\nasync function main(args: string[]): Promise<void> {\n const { values, positionals } = parseArgs({\n args,\n allowPositionals: true,\n strict: true,\n options: {\n help: { type: \"boolean\", short: \"h\" },\n version: { type: \"boolean\", short: \"v\" },\n org: { type: \"string\" },\n \"firebase-project\": { type: \"string\" },\n // Each negation is declared under the name it is typed with, because\n // `parseArgs` has no boolean negation of its own. `flagPair` puts the\n // two halves back together, and the third state, neither given, is the\n // one that matters: it is what turns the flag into a question.\n \"no-firebase\": { type: \"boolean\" },\n git: { type: \"boolean\" },\n \"no-git\": { type: \"boolean\" },\n workflows: { type: \"boolean\" },\n \"no-workflows\": { type: \"boolean\" },\n \"package-manager\": { type: \"string\" },\n },\n });\n\n if (values.help) {\n console.log(help);\n return;\n }\n if (values.version) {\n console.log(version);\n return;\n }\n\n const requestedManager = resolveManager(values[\"package-manager\"]);\n\n if (positionals[0] === \"add\") {\n // `ci` is the name this shipped under, and it is written into the README\n // of every project already scaffolded. Those READMEs are on disk and are\n // not going to be edited, so the old spelling keeps working; only the new\n // one is documented.\n if (positionals[1] !== \"workflows\" && positionals[1] !== \"ci\") {\n throw new Error(`unknown subcommand: add ${positionals[1] ?? \"\"}\\n\\n${help}`.trim());\n }\n if (positionals.length > 3) {\n throw new Error(`expected at most one directory\\n\\n${help}`);\n }\n const result = addContinuousIntegration({\n directory: positionals[2] ?? process.cwd(),\n packageManager: requestedManager,\n });\n console.log(`Added ${result.files.join(\" and \")} in ${result.root}`);\n return;\n }\n\n if (positionals.length !== 1) {\n throw new Error(`expected exactly one destination directory\\n\\n${help}`);\n }\n\n const directory = positionals[0];\n\n // Everything that makes the command impossible, before the greeting rather\n // than after the last question. `applicationId` validates the slug;\n // `destinationProblem` is the one `scaffoldGame` would raise at the far end,\n // asked here instead. Being told the directory was occupied *after* giving\n // an organization is the same insult as being told about `flutterfire` after\n // two minutes of Flutter and pub.\n const game = basename(resolve(directory));\n applicationId(directory);\n const occupied = destinationProblem(directory);\n if (occupied !== undefined) throw new Error(occupied);\n\n greet();\n\n // Every question this run will ask, settled before any of it starts. A flag\n // is an answer already given; anything left over is asked, or, with nothing\n // to ask on, collected here and reported together, so one re-run can carry\n // all of them.\n const interactive = process.stdin.isTTY === true;\n const missing: { flags: string; suggestion: string }[] = [];\n const cancelled = () => {\n ui.stop(\"Nothing was written.\");\n process.exitCode = 130;\n };\n\n /**\n * Records a question that could not be asked, and hands back the default it\n * suggested. The value is never used, since `missing` throws before anything\n * is written, but returning it keeps each answer definitely typed instead of\n * carrying a `| undefined` all the way to the scaffold call.\n */\n const needsFlag = <T>(flags: string, suggestion: string, fallback: T): T => {\n missing.push({ flags, suggestion });\n return fallback;\n };\n\n // Firebase goes first because it is the only answer that can end the run,\n // and ending it after the organization prompt would mean asking for a\n // permanent decision about a project that never gets created.\n const project = values[\"firebase-project\"]?.trim();\n if (project === \"\") throw new Error(\"--firebase-project needs a project id\");\n if (project !== undefined && values[\"no-firebase\"] === true) {\n throw new Error(\"--firebase-project and --no-firebase contradict each other\");\n }\n\n let firebase: boolean | string = false;\n if (project !== undefined) {\n firebase = project;\n } else if (values[\"no-firebase\"] !== true) {\n const readiness = firebaseReadiness();\n if (readiness.ready) {\n // Nothing for this CLI to ask: FlutterFire runs its own project picker\n // during the scaffold, and needs a terminal to do it.\n if (interactive) firebase = true;\n else firebase = needsFlag(\"--firebase-project <id>, or --no-firebase\", \"--no-firebase\", false);\n } else {\n ui.warn(`Firebase is not set up on this machine:\\n\\n${firebaseReport(readiness.problems)}`);\n if (!interactive) {\n firebase = needsFlag(\"--no-firebase, to scaffold without it\", \"--no-firebase\", false);\n } else {\n const carryOn = await askToScaffoldWithoutFirebase();\n if (carryOn === null) return cancelled();\n if (!carryOn) return stopForFirebase(readiness.problems);\n }\n }\n }\n\n let org: string | undefined;\n if (values.org !== undefined) {\n org = resolveOrg(values.org);\n } else if (interactive) {\n const answer = await askForOrg(game.replaceAll(\"-\", \"_\"), firebase !== false);\n if (answer === null) return cancelled();\n org = answer;\n } else {\n org = needsFlag(\"--org <reverse-domain>\", `--org ${DEFAULT_ORG}`, undefined);\n }\n\n // A destination inside a repository someone already has is not a question:\n // `scaffoldGame` declines to nest one repository in another whatever it is\n // told, and says so in the summary.\n let git = flagPair(values.git, values[\"no-git\"], \"git\");\n if (git === undefined && insideWorkTree(directory)) git = true;\n if (git === undefined) {\n if (!interactive) git = needsFlag(\"--git or --no-git\", \"--git\", true);\n else {\n const answer = await askForGit();\n if (answer === null) return cancelled();\n git = answer;\n }\n }\n\n let workflows = flagPair(values.workflows, values[\"no-workflows\"], \"workflows\");\n if (workflows === undefined) {\n if (!interactive) workflows = needsFlag(\"--workflows or --no-workflows\", \"--no-workflows\", false);\n else {\n const answer = await askForWorkflows();\n if (answer === null) return cancelled();\n workflows = answer;\n }\n }\n\n // Reached only when nothing else could say. `npm create` and `pnpm create`\n // both announce themselves in `npm_config_user_agent`. What is left is a\n // global install, where the old silent fallback to pnpm was a guess written\n // into every script in the generated project.\n let manager = requestedManager ?? detectPackageManager();\n if (manager === undefined) {\n if (!interactive) manager = needsFlag(\"--package-manager <npm|pnpm>\", \"--package-manager pnpm\", \"pnpm\" as PackageManager);\n else {\n const answer = await askForPackageManager();\n if (answer === null) return cancelled();\n manager = answer;\n }\n }\n\n if (missing.length > 0) throw new Error(unanswered(directory, missing));\n\n ui.success(`applicationId ${color.bold(applicationId(directory, org))}`);\n\n const result = scaffoldGame({\n directory,\n org,\n packageManager: manager,\n ci: workflows,\n git,\n firebase,\n reporter: clackReporter(),\n });\n\n const { status, next, footnotes, headline } = summarise(result, manager, workflows);\n for (const line of status) ui.success(line);\n ui.block(next);\n for (const line of footnotes) ui.info(line);\n ui.close(headline);\n}\n\ntry {\n await main(process.argv.slice(2));\n} catch (error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n ui.stop(`create-eigen-game: ${message}`);\n process.exitCode = 1;\n}\n","/**\n * Every question this CLI asks, in one module so the wording lives together\n * and can be asserted. `cli.ts` is the bin entry and runs `main` on import,\n * which makes anything defined there untestable.\n *\n * The organization prompt is the reason this drops to `@clack/core` rather\n * than using `@clack/prompts`' `text`. The suffix: `flutter create --org X\n * --project-name Y` derives `X.Y`, so the organization is a *prefix*, and the\n * reliable way to say so is to render the rest of the identifier, dimmed,\n * while it is being typed. `text` has no hook for that; a custom `TextPrompt`\n * render returns a string, so it does.\n *\n * Everything else comes from `@clack/prompts` rather than being drawn here.\n * Its symbols are exported for exactly this, and they are not constants: each\n * falls back to ASCII on a terminal that cannot show the box-drawing\n * characters, which a hand-typed `│` cannot do.\n */\nimport type { Readable, Writable } from \"node:stream\";\nimport { TextPrompt } from \"@clack/core\";\nimport { confirm, isCancel, S_BAR, S_BAR_END, select, symbol } from \"@clack/prompts\";\nimport color from \"picocolors\";\nimport type { PackageManager } from \"./index.js\";\n\n/**\n * Where a question reads its answer from. Parameters for the same reason every\n * clack prompt takes them: a prompt is otherwise the one part of a CLI that\n * cannot be driven by a test.\n */\nexport interface Io {\n input?: Readable;\n output?: Writable;\n}\n\n/**\n * Two or more dot-separated Java identifiers. `flutter create --org` accepts\n * anything and defers the complaint to Gradle, which reports it as a manifest\n * error in a generated file, so `com.example-games` costs a full scaffold and\n * a first build before anyone learns that a hyphen is not legal in a package\n * segment.\n */\nexport const ORG = /^[A-Za-z_][A-Za-z0-9_]*(\\.[A-Za-z_][A-Za-z0-9_]*)+$/;\n\n/** The default, which an empty answer accepts. */\nexport const DEFAULT_ORG = \"com.example\";\n\nconst bar = color.gray(S_BAR);\n\n/** A `com.acme.chess` answer to a game called `chess`, which yields `com.acme.chess.chess`. */\nexport function repeatsGameName(org: string, game: string): boolean {\n return org.split(\".\").at(-1) === game;\n}\n\n/** The organization implied by an answer that already ends in the game name. */\nexport function withoutGameName(org: string): string | undefined {\n const shorter = org.split(\".\").slice(0, -1).join(\".\");\n return ORG.test(shorter) ? shorter : undefined;\n}\n\n/**\n * Asks, with the game name rendered dimmed after the cursor so the shape of\n * the answer is visible while it is being given.\n *\n * Returns the organization, `undefined` for the default, or `null` when\n * cancelled.\n *\n * `input` and `output` default to this process's, and are parameters for the\n * same reason every clack prompt takes them: a prompt is otherwise the one\n * part of a CLI that cannot be driven by a test.\n */\nexport async function askForOrg(game: string, registering: boolean, io: Io = {}): Promise<string | undefined | null> {\n const input = io.input ?? process.stdin;\n const output = io.output ?? process.stdout;\n const suffix = color.dim(`.${game}`);\n const prompt = new TextPrompt({\n input,\n output,\n // An empty answer is the default, not a mistake.\n validate: (value) => (value === undefined || value === \"\" || ORG.test(value) ? undefined : \"Two or more dot-separated segments, as in com.example or dev.yourname.games.\"),\n render() {\n const typed = String(this.value ?? \"\");\n const shown = typed === \"\" ? `${color.dim(DEFAULT_ORG)}${suffix}` : `${this.userInputWithCursor}${suffix}`;\n const problem = this.state === \"error\" ? `\\n${bar} ${color.yellow(this.error ?? \"\")}` : \"\";\n return [`${bar}`, `${symbol(this.state)} Organization in reverse domain notation`, `${bar} ${shown}${problem}`, color.gray(S_BAR_END)].join(\"\\n\");\n },\n });\n\n // The two things that make this answer permanent, said before it is given\n // rather than after: Google Play freezes the identifier at the first upload,\n // and FlutterFire matches an existing Android app on exactly this string, so\n // it also decides whether the run adopts an app the chosen Firebase project\n // already has or registers a new one.\n output.write(`${bar} ${color.dim(\"Prefixes the Android applicationId, which Google Play makes permanent at first upload.\")}\\n`);\n if (registering) output.write(`${bar} ${color.dim(\"Also the Android app registered in the Firebase project you pick next.\")}\\n`);\n\n const answer = await prompt.prompt();\n if (isCancel(answer)) return null;\n\n const org = String(answer).trim();\n if (org === \"\" || org === DEFAULT_ORG) return undefined;\n\n // The mistake the dimmed suffix is there to prevent, caught for the person\n // who pasted an identifier rather than typing one and never watched it grow.\n const shorter = repeatsGameName(org, game) ? withoutGameName(org) : undefined;\n if (shorter !== undefined) {\n const shorten = await confirm({\n message: `That gives ${color.yellow(`${org}.${game}`)}, the game name twice. Use ${color.green(shorter)} instead?`,\n initialValue: true,\n input,\n output,\n });\n if (isCancel(shorten)) return null;\n if (shorten) return shorter;\n }\n\n return org;\n}\n\n/**\n * A yes/no question, with its reason on the line above.\n *\n * The three below share this so cancellation is handled in one place: ^C\n * during any question has to mean the run stops having written nothing, and\n * that is only true if every call site checks. `null` is that answer.\n *\n * `hint` is written rather than passed to `confirm`, which has nowhere to put\n * it. It is the same dimmed line the organization prompt uses, and carries the\n * thing that makes the default the default: a question whose answer is\n * pre-chosen owes the reader why.\n */\nasync function ask(message: string, initialValue: boolean, hint: string, io: Io): Promise<boolean | null> {\n const input = io.input ?? process.stdin;\n const output = io.output ?? process.stdout;\n output.write(`${bar}\\n${bar} ${color.dim(hint)}\\n`);\n const answer = await confirm({ message, initialValue, input, output });\n return isCancel(answer) ? null : answer;\n}\n\n/**\n * Asked only when {@link firebaseReadiness} found something missing, and\n * defaulted to \"no\" because the honest answer usually is: the tools are two\n * commands away, and the alternative is an app that throws at launch until\n * they are installed anyway.\n */\nexport function askToScaffoldWithoutFirebase(io: Io = {}): Promise<boolean | null> {\n return ask(\"Scaffold anyway, and connect Firebase yourself later?\", false, \"Everything except the app's sign-in still works, and `firebase:configure` finishes the job.\", io);\n}\n\n/** Defaulted to yes: the scaffold is a few hundred generated files, and a commit is what makes the first `git diff` the first game change rather than all of them. */\nexport function askForGit(io: Io = {}): Promise<boolean | null> {\n return ask(\"Initialise a git repository and commit the scaffold?\", true, \"Commits once, so your first diff is your first game change.\", io);\n}\n\n/**\n * Defaulted to no, and the hint says why rather than leaving it as an\n * arbitrary-looking choice: `release.yml` needs an upload keystore and a Play\n * service account, so a project that takes the workflows before it has either\n * gets a red `main` from its first push.\n */\nexport function askForWorkflows(io: Io = {}): Promise<boolean | null> {\n return ask(\"Add the GitHub Actions workflows?\", false, \"Release needs a signing keystore and a Play service account, so this fails until you have both. `add workflows` writes them later.\", io);\n}\n\n/**\n * The package manager, asked only when nothing else could say.\n *\n * `npm create` and `pnpm create` both set `npm_config_user_agent`, so this is\n * reached by the paths that do not: a global install, or running the binary\n * directly. Rare, but it decides what every generated script says, and the old\n * silent fallback to pnpm was a guess printed into a project's package.json.\n */\nexport async function askForPackageManager(io: Io = {}): Promise<PackageManager | null> {\n const answer = await select({\n message: \"Which package manager should the generated scripts use?\",\n options: [\n { value: \"pnpm\", label: \"pnpm\" },\n { value: \"npm\", label: \"npm\" },\n ],\n input: io.input ?? process.stdin,\n output: io.output ?? process.stdout,\n });\n return isCancel(answer) ? null : (answer as PackageManager);\n}\n","/**\n * The closing summary, and the only output of a scaffold anyone reads.\n *\n * A run drives several tools with plenty to say, so where the project went,\n * what to edit, and what to run are worth restating at the bottom. Returned in\n * pieces rather than as one block, because the CLI renders each in a different\n * shape: the outcomes as status lines, the commands as a boxed note, the\n * destination as the closing line.\n *\n * Its own module so it can be asserted: `cli.ts` is the bin entry and runs\n * `main` on import, which makes anything defined there untestable. The package\n * `exports` map does not expose this, so it is internal despite being\n * importable by the tests.\n */\nimport type { PackageManager, ScaffoldResult } from \"./index.js\";\n\nexport interface Summary {\n /** What became of the repository and of Firebase, when there is something to say. */\n status: string[];\n /** The commands worth having, as one block. */\n next: string;\n /** One-line asides, after the block. */\n footnotes: string[];\n /** The closing line. */\n headline: string;\n}\n\n/** Two-column rows, aligned to the widest left cell. */\nfunction columns(rows: [string, string][]): string[] {\n const width = Math.max(...rows.map(([left]) => left.length));\n return rows.map(([left, right]) => ` ${left.padEnd(width)} ${right}`);\n}\n\nexport function summarise(result: ScaffoldResult, manager: PackageManager, workflows: boolean): Summary {\n const run = manager === \"npm\" ? \"npm run\" : \"pnpm\";\n const status: string[] = [];\n\n // Only the outcomes that leave the reader something to know. `failed` has\n // already warned, in more detail than a summary line could carry.\n if (result.git === \"committed\") status.push(`Committed as \"Scaffold ${result.name}\"`);\n if (result.git === \"existing\") status.push(\"Created inside a repository you already had, so nothing was committed\");\n // `failed` has warned already, and the step reappears below as the next\n // thing to run, which is the whole of the advice.\n if (result.firebase === \"configured\") status.push(result.git === \"committed\" ? \"Firebase is configured, and everything it generated is in that commit\" : \"Firebase is configured\");\n\n const next = [\n \"Write the game\",\n ...columns([\n [\"server/src/module/v1.ts\", \"the authoritative rules\"],\n [\"app/lib/game/v1/rules.dart\", \"the presentation twin\"],\n ]),\n \"\",\n `Then, from ${result.name}/`,\n ...columns([\n [`cd server && ${run} test:watch`, \"rules and twin fixtures\"],\n [`cd server && ${run} dev`, \"the Worker, on http://localhost:8787\"],\n [`${run} contract`, \"regenerate after a schema change\"],\n // Named here because it is the one step the game itself cannot do\n // without: rules, fixtures and the Worker all run unconfigured, and the\n // app throws `Firebase is not configured` at launch until this has run\n // once.\n ...(result.firebase === \"configured\" ? [] : [[`${run} firebase:configure`, \"connect Firebase before the app runs\"] as [string, string]]),\n ]),\n ].join(\"\\n\");\n\n const footnotes = [\"Docs: https://eigeninteractive.com\"];\n if (!workflows) footnotes.unshift(\"No CI workflows yet: `create-eigen-game add workflows` writes them when shipping is the next step.\");\n\n return { status, next, footnotes, headline: `${result.name} is ready, at ${result.root}` };\n}\n"],"mappings":"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","names":["color","color","result"]}