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/README.md +5 -4
- package/dist/chunk-QI5NQSGJ.js +288 -0
- package/dist/chunk-QI5NQSGJ.js.map +1 -0
- package/dist/cli.js +85 -15
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +31 -2
- package/dist/index.js +5 -1
- package/package.json +5 -5
- package/templates/app-overlay/.fvmrc +3 -0
- package/templates/app-overlay/.ruby-version +1 -0
- package/templates/app-overlay/CHANGELOG.md +9 -0
- package/templates/app-overlay/Gemfile +3 -0
- package/templates/app-overlay/android/app/proguard-rules.pro +9 -0
- package/templates/app-overlay/assets/icon/icon.png +0 -0
- package/templates/app-overlay/assets/icon/icon_foreground.png +0 -0
- package/templates/app-overlay/assets/icon/splash.png +0 -0
- package/templates/app-overlay/assets/icon/splash_dark.png +0 -0
- package/templates/app-overlay/fastlane/Appfile.template +1 -0
- package/templates/app-overlay/fastlane/Fastfile +29 -0
- package/templates/app-overlay/web/index.html.template +3 -0
- package/templates/ci/.github/workflows/checks.yml.template +125 -0
- package/templates/ci/.github/workflows/release.yml.template +130 -0
- package/templates/project/.nvmrc +1 -0
- package/templates/project/README.md +90 -3
- package/templates/scaffold/project.gitignore +2 -0
- package/templates/worker/README.md +1 -1
- package/dist/chunk-DEHAHQTO.js +0 -152
- package/dist/chunk-DEHAHQTO.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-eigen-game",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"description": "Scaffold an
|
|
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/
|
|
29
|
-
"@eigeninteractive/
|
|
30
|
-
"@eigeninteractive/
|
|
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 @@
|
|
|
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,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.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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
|
|
4
|
-
The engine itself is consumed from npm and pub.dev; its
|
|
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.
|
package/dist/chunk-DEHAHQTO.js
DELETED
|
@@ -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
|