create-eigen-game 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "create-eigen-game",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "license": "MIT",
5
- "description": "Scaffold an Eigen game Worker and Flutter app without cloning the engine repositories.",
5
+ "description": "Scaffold an EigenInteractive game Worker and Flutter app without cloning the engine repositories.",
6
6
  "type": "module",
7
7
  "bin": {
8
8
  "create-eigen-game": "./dist/cli.js"
@@ -25,9 +25,9 @@
25
25
  "vitest": "^4.1.10",
26
26
  "wrangler": "^4.110.0",
27
27
  "zod": "^4.4.3",
28
- "@eigeninteractive/server": "0.2.0",
29
- "@eigeninteractive/testkit": "0.2.0",
30
- "@eigeninteractive/rules": "0.2.0"
28
+ "@eigeninteractive/rules": "0.2.1",
29
+ "@eigeninteractive/server": "0.2.1",
30
+ "@eigeninteractive/testkit": "0.2.1"
31
31
  },
32
32
  "engines": {
33
33
  "node": ">=22"
@@ -0,0 +1,3 @@
1
+ {
2
+ "flutter": "3.44.8"
3
+ }
@@ -0,0 +1 @@
1
+ 4.0
@@ -0,0 +1,9 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to
7
+ [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
8
+
9
+ ## [Unreleased]
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "fastlane", "~> 2.233"
@@ -0,0 +1,9 @@
1
+ # Flutter engine rules are added automatically by the Flutter Gradle plugin.
2
+ #
3
+ # image_cropper ships its own consumer-proguard-rules.pro (OkHttp + uCrop).
4
+ # google_sign_in (Credential Manager), in_app_update, and in_app_review use
5
+ # Google Play libraries that include their own consumer rules via Maven.
6
+ #
7
+ # Only libraries confirmed to lack consumer rules are listed below — currently
8
+ # none. Add a keep rule here only after confirming a dependency ships no
9
+ # consumer rules of its own and is stripped by R8.
@@ -0,0 +1 @@
1
+ package_name("{{PACKAGE_ID}}")
@@ -0,0 +1,29 @@
1
+ default_platform(:android)
2
+
3
+ platform :android do
4
+ desc "Upload AAB to Google Play internal testing track"
5
+ lane :internal do
6
+ upload_to_play_store(
7
+ track: "internal",
8
+ aab: "build/app/outputs/bundle/release/app-release.aab",
9
+ skip_upload_apk: true,
10
+ skip_upload_metadata: true,
11
+ skip_upload_changelogs: true,
12
+ skip_upload_images: true,
13
+ skip_upload_screenshots: true,
14
+ )
15
+ end
16
+
17
+ desc "Upload AAB directly to Google Play production track"
18
+ lane :production do
19
+ upload_to_play_store(
20
+ track: "production",
21
+ aab: "build/app/outputs/bundle/release/app-release.aab",
22
+ skip_upload_apk: true,
23
+ skip_upload_metadata: true,
24
+ skip_upload_changelogs: true,
25
+ skip_upload_images: true,
26
+ skip_upload_screenshots: true,
27
+ )
28
+ end
29
+ end
@@ -9,6 +9,9 @@
9
9
  <meta name="apple-mobile-web-app-status-bar-style" content="black">
10
10
  <meta name="apple-mobile-web-app-title" content="Example Game">
11
11
  <title>Example Game</title>
12
+ <!-- `flutter_launcher_icons` generates favicon.png and web/icons/ from
13
+ assets/icon/icon.png, and rewrites this href in place. -->
14
+ <link rel="icon" type="image/png" href="favicon.png">
12
15
  <link rel="manifest" href="manifest.json">
13
16
  </head>
14
17
  <body>
@@ -0,0 +1,125 @@
1
+ name: Checks
2
+
3
+ # The single definition of "is this commit good?", invoked two ways:
4
+ #
5
+ # - directly, on every pull request;
6
+ # - by `release.yml` via `workflow_call`, on every push to main, so the
7
+ # commit that is about to ship a Play Store build clears exactly the
8
+ # same bar.
9
+ #
10
+ # There is deliberately no `push: [main]` trigger here. Release owns main and
11
+ # calls this, so adding one would run the whole gate twice per merge.
12
+ on:
13
+ pull_request:
14
+ branches: [main]
15
+ workflow_call:
16
+
17
+ # A pull request's run is superseded freely; a run gating a release is not.
18
+ # Keyed on the SHA rather than the ref so a called run never shares a group
19
+ # with release.yml's own.
20
+ concurrency:
21
+ group: checks-${{ github.event.pull_request.number || github.sha }}
22
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
23
+
24
+ # Needs no secrets or Cloudflare/Play credentials, which is why `release.yml`
25
+ # calls it without `secrets: inherit`. Worker deployment and the signed
26
+ # Android build both happen in `release.yml` instead.
27
+ permissions:
28
+ contents: read
29
+
30
+ jobs:
31
+ server:
32
+ name: Server
33
+ runs-on: ubuntu-latest
34
+ defaults:
35
+ run:
36
+ working-directory: server
37
+ steps:
38
+ - uses: actions/checkout@v7
39
+
40
+ # `corepack enable`, not `pnpm/action-setup` — this project can be
41
+ # scaffolded with either npm or pnpm (see the {{PACKAGE_MANAGER}}
42
+ # commands below), and corepack puts both on PATH regardless of which
43
+ # one a game author actually chose.
44
+ - name: Enable corepack
45
+ run: corepack enable
46
+
47
+ - uses: actions/setup-node@v7
48
+ with:
49
+ node-version-file: .nvmrc
50
+ cache: {{PACKAGE_MANAGER}}
51
+
52
+ - name: Install dependencies
53
+ run: {{PACKAGE_MANAGER}} install
54
+
55
+ - name: Test
56
+ run: {{PACKAGE_MANAGER}} run test
57
+
58
+ - name: Typecheck
59
+ run: {{PACKAGE_MANAGER}} run typecheck
60
+
61
+ app:
62
+ name: App
63
+ runs-on: ubuntu-latest
64
+ steps:
65
+ - uses: actions/checkout@v7
66
+
67
+ - name: Enable corepack
68
+ run: corepack enable
69
+
70
+ - uses: actions/setup-node@v7
71
+ with:
72
+ node-version-file: .nvmrc
73
+ cache: {{PACKAGE_MANAGER}}
74
+
75
+ # `contract:check` (below) round-trips through both halves — it
76
+ # invokes the server's own contract generator, then verifies the app's
77
+ # generated payloads still match — so the server's Node dependencies
78
+ # have to exist here too, not just in the `server` job's own runner.
79
+ - name: Install server dependencies
80
+ working-directory: server
81
+ run: {{PACKAGE_MANAGER}} install
82
+
83
+ - uses: subosito/flutter-action@v2
84
+ with:
85
+ flutter-version-file: app/.fvmrc
86
+ channel: stable
87
+ cache: true
88
+
89
+ - name: Install app dependencies
90
+ working-directory: app
91
+ run: flutter pub get
92
+
93
+ # The generated payload types are committed, so a game rule or a
94
+ # server schema that changed without regenerating them would otherwise
95
+ # go unnoticed until a decode failure at runtime.
96
+ - name: Contract and generated payloads are in sync
97
+ run: {{PACKAGE_MANAGER}} run contract:check
98
+
99
+ - name: Check formatting
100
+ working-directory: app
101
+ run: dart format --output=none --set-exit-if-changed .
102
+
103
+ - name: Analyze
104
+ working-directory: app
105
+ run: flutter analyze
106
+
107
+ - name: Test
108
+ working-directory: app
109
+ run: flutter test
110
+
111
+ # A VM widget test can pass while a dependency still imports dart:io or
112
+ # calls an unsupported plugin. Compiling the real web entrypoint is
113
+ # what actually proves it ships — mirrors eigen-flutter's own `example`
114
+ # job, which does the same for the RPS reference app.
115
+ #
116
+ # No Android SDK step in this job, for the same reason
117
+ # `create-eigen-game`'s own `scaffold-e2e.mjs` check skips one:
118
+ # `flutter analyze`/`test`/`build web` don't need it, and it would
119
+ # roughly double this job for no extra coverage. The Android build is
120
+ # verified for real, with a real signing config, in `release.yml`.
121
+ - name: Build web
122
+ working-directory: app
123
+ run: >
124
+ flutter build web --release
125
+ --dart-define-from-file=app-config.json
@@ -0,0 +1,130 @@
1
+ name: Release
2
+
3
+ # Ships a signed Android build on every push to main:
4
+ #
5
+ # 1. checks — the exact PR gate, re-run against the merge commit.
6
+ # 2. build — a signed, obfuscated AAB.
7
+ # 3. deploy — fastlane uploads it to the Play Store internal track.
8
+ #
9
+ # Worker deployment is NOT here, on purpose: it applies D1 migrations against
10
+ # a real database, so it stays the deliberate, manual `{{PACKAGE_MANAGER}} run
11
+ # deploy` from a machine with `wrangler login` — the same reasoning the
12
+ # engine's own CI uses for never deploying a Worker automatically.
13
+ on:
14
+ push:
15
+ branches: [main]
16
+
17
+ concurrency:
18
+ group: release
19
+ cancel-in-progress: false
20
+
21
+ # Default deny. Every job below grants itself only what it needs.
22
+ permissions: {}
23
+
24
+ jobs:
25
+ # The same gate every pull request clears, re-run against the commit that
26
+ # is about to ship. Needs no secrets, so it is called without
27
+ # `secrets: inherit`.
28
+ checks:
29
+ name: Checks
30
+ uses: ./.github/workflows/checks.yml
31
+ permissions:
32
+ contents: read
33
+
34
+ build:
35
+ name: Build signed AAB
36
+ needs: checks
37
+ runs-on: ubuntu-latest
38
+ permissions:
39
+ contents: read
40
+ defaults:
41
+ run:
42
+ working-directory: app
43
+ steps:
44
+ - uses: actions/checkout@v7
45
+
46
+ - uses: actions/setup-java@v5.6.0
47
+ with:
48
+ distribution: temurin
49
+ java-version: "17"
50
+ cache: gradle
51
+
52
+ - uses: subosito/flutter-action@v2
53
+ with:
54
+ flutter-version-file: app/.fvmrc
55
+ channel: stable
56
+ cache: true
57
+
58
+ - name: Install dependencies
59
+ run: flutter pub get
60
+
61
+ # The keystore and Play service account are the only things here that
62
+ # are genuinely secret. `app-config.json`, `firebase_options.dart` and
63
+ # `google-services.json` are public app identifiers, not credentials —
64
+ # commit them for real once `firebase:configure` has been run, rather
65
+ # than reconstructing them from secrets on every build. See
66
+ # https://eigeninteractive.com/docs/ship-it/configure and
67
+ # https://eigeninteractive.com/docs/ship-it/store-release.
68
+ - name: Decode keystore
69
+ run: echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 --decode > "${{ github.workspace }}/upload-keystore.jks"
70
+
71
+ - name: Write key.properties
72
+ env:
73
+ STORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
74
+ KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
75
+ run: |
76
+ echo "storeFile=${{ github.workspace }}/upload-keystore.jks" > android/key.properties
77
+ echo "storePassword=$STORE_PASSWORD" >> android/key.properties
78
+ echo "keyAlias=${{ secrets.KEY_ALIAS }}" >> android/key.properties
79
+ echo "keyPassword=$KEY_PASSWORD" >> android/key.properties
80
+
81
+ - name: Build AAB
82
+ run: |
83
+ flutter build appbundle --release \
84
+ --build-number=${{ github.run_number }} \
85
+ --dart-define-from-file=app-config.json \
86
+ --obfuscate --split-debug-info=build/debug-info/android/
87
+
88
+ - uses: actions/upload-artifact@v7
89
+ with:
90
+ name: app-release-${{ github.run_number }}
91
+ path: app/build/app/outputs/bundle/release/app-release.aab
92
+ retention-days: 30
93
+
94
+ - uses: actions/upload-artifact@v7
95
+ with:
96
+ name: debug-info-android-${{ github.run_number }}
97
+ path: app/build/debug-info/android/
98
+ retention-days: 30
99
+
100
+ # `environment: play-store` is not decoration: it is where a required
101
+ # reviewer would be attached if this ever wants one, matching how
102
+ # eigen-flutter's own release.yml gates its real pub.dev publish job with
103
+ # `environment: npm`.
104
+ deploy:
105
+ name: Deploy to Google Play
106
+ needs: build
107
+ runs-on: ubuntu-latest
108
+ environment: play-store
109
+ permissions:
110
+ contents: read
111
+ defaults:
112
+ run:
113
+ working-directory: app
114
+ steps:
115
+ - uses: actions/checkout@v7
116
+
117
+ - uses: ruby/setup-ruby@v1
118
+ with:
119
+ bundler-cache: true
120
+ working-directory: app
121
+
122
+ - uses: actions/download-artifact@v8
123
+ with:
124
+ name: app-release-${{ github.run_number }}
125
+ path: app/build/app/outputs/bundle/release/
126
+
127
+ - name: Deploy to internal track
128
+ env:
129
+ SUPPLY_JSON_KEY_DATA: ${{ secrets.GOOGLE_PLAY_JSON_KEY }}
130
+ run: bundle exec fastlane android internal
@@ -0,0 +1 @@
1
+ 24
@@ -1,8 +1,8 @@
1
1
  # Example Game
2
2
 
3
- This repository contains the two application-owned halves of an Eigen game.
4
- The engine itself is consumed from npm and pub.dev; its repositories are not
5
- part of this project.
3
+ This repository contains the two application-owned halves of an
4
+ EigenInteractive game. The engine itself is consumed from npm and pub.dev; its
5
+ repositories are not part of this project.
6
6
 
7
7
  ## Worker
8
8
 
@@ -102,3 +102,90 @@ deletion. Copy `server/.dev.vars.example` to `server/.dev.vars`, then fill its
102
102
  service-account email and private key. The Firebase project ID and web origin
103
103
  remain in `server/wrangler.jsonc`. For deployment, store the two credentials
104
104
  with `wrangler secret put`; never commit the downloaded JSON or `.dev.vars`.
105
+
106
+ ## Store release
107
+
108
+ CI is **not** generated by default, because the release pipeline needs an
109
+ upload keystore and a Play service account that a new project does not have
110
+ — it would fail on every push until both exist. Add it when you are ready to
111
+ ship, from the project root:
112
+
113
+ ```sh
114
+ npx create-eigen-game add ci
115
+ ```
116
+
117
+ That writes `.github/workflows/checks.yml` and `release.yml`. (Passing
118
+ `--ci` at scaffold time does the same thing up front.)
119
+
120
+ `release.yml` builds a signed, obfuscated Android App
121
+ Bundle on every push to `main` and uploads it to the Play Store internal
122
+ track via `app/fastlane`. `checks.yml` is the PR gate it
123
+ calls first — it needs no secrets, since `app-config.json`,
124
+ `firebase_options.dart` and `google-services.json` are public app
125
+ identifiers, not credentials. Commit them for real once
126
+ `{{PACKAGE_MANAGER}} run firebase:configure` has been run, rather than
127
+ reconstructing them from secrets on every build. The full reasoning is at
128
+ https://eigeninteractive.com/docs/ship-it/configure and
129
+ https://eigeninteractive.com/docs/ship-it/store-release.
130
+
131
+ Only five things are genuinely secret, so only five repository secrets are
132
+ required:
133
+
134
+ | Secret | Used for |
135
+ |---|---|
136
+ | `KEYSTORE_BASE64` | The upload keystore, base64-encoded (`base64 -i upload-keystore.jks`) |
137
+ | `KEYSTORE_PASSWORD`, `KEY_ALIAS`, `KEY_PASSWORD` | Signing `android/key.properties` |
138
+ | `GOOGLE_PLAY_JSON_KEY` | A Play service account JSON with the *Release* permission, for `fastlane`'s `upload_to_play_store` |
139
+
140
+ Per-app one-time setup: create an upload keystore, add the four signing
141
+ secrets above, create the Google Play service account and add its JSON, and
142
+ do the **first** Play Console upload by hand to create the listing —
143
+ everything after that flows through `fastlane android internal` /
144
+ `fastlane android production`. The Fastlane lanes upload the binary only
145
+ (`skip_upload_metadata`/`images`/`screenshots`), so the store listing stays
146
+ hand-maintained in the Console.
147
+
148
+ ### Replace the placeholder branding
149
+
150
+ A fresh scaffold is already branded — with the **EigenInteractive mark**, not
151
+ this game's. The launcher icon, splash and web icons were generated at scaffold
152
+ time so the
153
+ app looks finished from the first run, but shipping them to a store would put
154
+ the engine's mark on your listing. Replace them before a first release:
155
+
156
+ | Asset | What it is |
157
+ |---|---|
158
+ | `assets/icon/icon.png` | 1024×1024 launcher icon, full bleed |
159
+ | `assets/icon/icon_foreground.png` | 1024×1024 Android adaptive foreground, transparent, artwork inside the centre 66% |
160
+ | `assets/icon/splash.png` / `splash_dark.png` | Splash artwork for light and dark |
161
+
162
+ The notification icon is the exception: `eigen_flutter` ships a default
163
+ `ic_notification` drawable, so nothing is missing and nothing breaks if you
164
+ leave it. To use your own, add
165
+ `android/app/src/main/res/drawable/ic_notification.xml` — a monochrome
166
+ `<vector>`, not an SVG. Android resource merging gives your app precedence
167
+ over the package, so there is nothing to delete or override explicitly.
168
+
169
+ Also update the palette in `pubspec.yaml` — `adaptive_icon_background` and the
170
+ `flutter_native_splash` colours are the EigenInteractive ink/paper pair, which
171
+ will not suit different artwork. Then regenerate:
172
+
173
+ ```bash
174
+ dart run flutter_launcher_icons
175
+ dart run flutter_native_splash:create
176
+ ```
177
+
178
+ The full checklist is at https://eigeninteractive.com/docs/ship-it/branding.
179
+
180
+ Versioning uses [`cider`](https://pub.dev/packages/cider) against
181
+ `app/pubspec.yaml` and `app/CHANGELOG.md`:
182
+
183
+ ```bash
184
+ dart pub global activate cider # once, from app/
185
+ cider log added "New feature description"
186
+ cider bump patch # 1.0.0 -> 1.0.1
187
+ ```
188
+
189
+ The build number is set from `github.run_number` in CI and is not managed by
190
+ hand. Worker deployment is unaffected by any of this — it stays the manual
191
+ `{{PACKAGE_MANAGER}} run deploy` described above.
@@ -7,3 +7,5 @@ server/.env*
7
7
  server/public/*
8
8
  !server/public/.gitkeep
9
9
  *.log
10
+
11
+ app/pubspec_overrides.yaml
@@ -1,6 +1,6 @@
1
1
  # Example Game Worker
2
2
 
3
- The authoritative game rules and Cloudflare Worker for an Eigen game.
3
+ The authoritative game rules and Cloudflare Worker for an EigenInteractive game.
4
4
 
5
5
  ## Develop
6
6
 
@@ -1,152 +0,0 @@
1
- // src/index.ts
2
- import { execFileSync } from "child_process";
3
- import { appendFileSync, cpSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "fs";
4
- import { basename, dirname, resolve } from "path";
5
- import { fileURLToPath } from "url";
6
-
7
- // src/engine-range.ts
8
- var ENGINE_PACKAGE = "@eigeninteractive/server";
9
- function engineRange(declared, readSiblingVersion) {
10
- if (!declared) {
11
- throw new Error(`create-eigen-game has no ${ENGINE_PACKAGE} devDependency, which is where the engine range it emits comes from`);
12
- }
13
- const version = declared.startsWith("workspace:") ? readSiblingVersion() : declared;
14
- if (!version) {
15
- throw new Error(`create-eigen-game declares ${ENGINE_PACKAGE} as "${declared}" but the workspace package could not be read, so there is no engine version to emit`);
16
- }
17
- if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(version)) {
18
- throw new Error(`expected an exact ${ENGINE_PACKAGE} version to build a range from, got "${version}"`);
19
- }
20
- return `^${version}`;
21
- }
22
-
23
- // src/index.ts
24
- var packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
25
- var templatesRoot = resolve(packageRoot, "templates");
26
- var engineVersion = engineRange(JSON.parse(readFileSync(resolve(packageRoot, "package.json"), "utf8")).devDependencies?.[ENGINE_PACKAGE], () => {
27
- const sibling = resolve(packageRoot, "../server/package.json");
28
- return existsSync(sibling) ? JSON.parse(readFileSync(sibling, "utf8")).version : void 0;
29
- });
30
- var flutterClientVersion = "^0.2.0";
31
- var gameSlug = (value) => {
32
- if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value)) {
33
- throw new Error("destination directory name must be a lowercase kebab-case slug, for example my-game");
34
- }
35
- return value;
36
- };
37
- var dartName = (value) => value.replaceAll("-", "_");
38
- var title = (value) => value.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
39
- var identifier = (value) => {
40
- const pascal = value.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join("");
41
- return /^[0-9]/.test(pascal) ? `Game${pascal}` : pascal;
42
- };
43
- function detectPackageManager(userAgent = process.env.npm_config_user_agent) {
44
- if (userAgent?.startsWith("pnpm/")) return "pnpm";
45
- if (userAgent?.startsWith("npm/")) return "npm";
46
- return void 0;
47
- }
48
- function render(contents, name, manager) {
49
- const replacements = [
50
- ["@game/example-game-server", `@game/${name}-server`],
51
- ["{{PACKAGE_MANAGER}}", manager],
52
- ["{{ENGINE_VERSION}}", engineVersion],
53
- ["ExampleGame", identifier(name)],
54
- ["Example Game", title(name)],
55
- ["example_game", dartName(name)],
56
- ["example-game", name]
57
- ];
58
- return replacements.reduce((result, [from, to]) => result.replaceAll(from, to), contents);
59
- }
60
- function renderTree(source, destination, name, manager) {
61
- cpSync(source, destination, { recursive: true });
62
- const visit = (directory) => {
63
- for (const entry of readdirSync(directory, { withFileTypes: true })) {
64
- const path = resolve(directory, entry.name);
65
- if (entry.isDirectory()) visit(path);
66
- else if (entry.isFile()) {
67
- writeFileSync(path, render(readFileSync(path, "utf8"), name, manager));
68
- if (entry.name.endsWith(".template")) {
69
- renameSync(path, path.slice(0, -".template".length));
70
- }
71
- }
72
- }
73
- };
74
- visit(destination);
75
- const packagedGitignore = resolve(templatesRoot, "scaffold", `${basename(source)}.gitignore`);
76
- if (existsSync(packagedGitignore)) {
77
- writeFileSync(resolve(destination, ".gitignore"), render(readFileSync(packagedGitignore, "utf8"), name, manager));
78
- }
79
- }
80
- function packageCommand(manager, operation) {
81
- if (operation === "install") return [manager, ["install"]];
82
- return [manager, ["run", "contract"]];
83
- }
84
- var androidDesugaring = `// flutter_local_notifications requires desugaring in the application module.
85
- // A library plugin cannot enable this compiler setting transitively.
86
- android {
87
- compileOptions {
88
- isCoreLibraryDesugaringEnabled = true
89
- }
90
- }
91
-
92
- dependencies {
93
- coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
94
- }`;
95
- function enableAndroidCoreLibraryDesugaring(appRoot) {
96
- const gradlePath = resolve(appRoot, "android/app/build.gradle.kts");
97
- const gradle = readFileSync(gradlePath, "utf8");
98
- if (!gradle.includes('id("com.android.application")')) {
99
- throw new Error(`Flutter created an unexpected Android application build file: ${gradlePath}`);
100
- }
101
- const settingExists = gradle.includes("isCoreLibraryDesugaringEnabled");
102
- const dependencyExists = gradle.includes("coreLibraryDesugaring(");
103
- if (settingExists && dependencyExists) return;
104
- if (settingExists || dependencyExists) {
105
- throw new Error(`Flutter created an incomplete Android core library desugaring configuration: ${gradlePath}`);
106
- }
107
- appendFileSync(gradlePath, `${gradle.endsWith("\n") ? "\n" : "\n\n"}${androidDesugaring}
108
- `);
109
- }
110
- function scaffoldGame(options) {
111
- const root = resolve(options.directory);
112
- const name = gameSlug(basename(root));
113
- const manager = options.packageManager ?? detectPackageManager() ?? "pnpm";
114
- const org = options.org?.trim() || "com.example";
115
- const bootstrap = options.bootstrap ?? true;
116
- const run = options.run ?? ((command, args, cwd) => execFileSync(command, args, { cwd, stdio: "inherit" }));
117
- if (existsSync(root)) throw new Error(`target already exists: ${root}`);
118
- mkdirSync(dirname(root), { recursive: true });
119
- const stagingRoot = mkdtempSync(resolve(dirname(root), `.${basename(root)}-`));
120
- try {
121
- const serverRoot = resolve(stagingRoot, "server");
122
- const appRoot = resolve(stagingRoot, "app");
123
- renderTree(resolve(templatesRoot, "worker"), serverRoot, name, manager);
124
- renderTree(resolve(templatesRoot, "project"), stagingRoot, name, manager);
125
- if (bootstrap) {
126
- run("flutter", ["create", "--empty", "--platforms", "android,web", "--project-name", dartName(name), "--org", org, appRoot], stagingRoot);
127
- enableAndroidCoreLibraryDesugaring(appRoot);
128
- } else {
129
- mkdirSync(appRoot, { recursive: true });
130
- }
131
- renderTree(resolve(templatesRoot, "app-overlay"), appRoot, name, manager);
132
- if (bootstrap) {
133
- run("flutter", ["pub", "add", `eigen_flutter@${flutterClientVersion}`, "firebase_core@^4.9.0", "firebase_messaging@^16.2.2"], appRoot);
134
- const [install, installArgs] = packageCommand(manager, "install");
135
- run(install, installArgs, serverRoot);
136
- const [contract, contractArgs] = packageCommand(manager, "contract");
137
- run(contract, contractArgs, serverRoot);
138
- run("dart", ["run", "eigen_flutter:generate_payloads", "--contract", "../server/game-contract.json", "--output", "lib/game/generated/payloads.dart", "--fixtures-output", "test/fixtures"], appRoot);
139
- }
140
- renameSync(stagingRoot, root);
141
- return { root, name };
142
- } catch (error) {
143
- rmSync(stagingRoot, { recursive: true, force: true });
144
- throw error;
145
- }
146
- }
147
-
148
- export {
149
- detectPackageManager,
150
- scaffoldGame
151
- };
152
- //# sourceMappingURL=chunk-DEHAHQTO.js.map