@makefully/adaptfully 4.0.0 → 4.1.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/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  All notable changes to this project are documented in this file.
4
4
 
5
+ ## 4.1.0 — 2026-09-04
6
+
7
+ ### Added
8
+
9
+ - **`adaptfully android-keystore`** — Generate debug/release keystores with `keytool` and write `assets/meta/deployments/android/build.json`.
10
+ - **`adaptfully apple-signing`** — Generate CSR/key (OpenSSL), export `.p12` from an Apple `.cer`, place provisioning profiles under `ios/apple/`, optional `apple.p12Password` in `build.json`.
11
+ - **Credential setup guides** — First-time walkthroughs under [`docs/credentials/`](docs/credentials/README.md) for iOS, Android, and Steam (full manual steps + helper skip markers).
12
+
5
13
  ## 4.0.0 — 2026-09-03
6
14
 
7
15
  ### Removed
package/README.md CHANGED
@@ -29,6 +29,8 @@ adaptfully deploy steam # prebuild + POST to Wrapfully ste
29
29
  adaptfully steam-publish # one-time: log in with steamcmd → assets/meta/deployments/steam/steam.json
30
30
  adaptfully google-publish --from ./sa.json # one-time: import Play service account → deployments/android/google.json
31
31
  adaptfully apple-publish # one-time: write App Store Connect creds → deployments/ios/apple.json
32
+ adaptfully android-keystore # one-time: generate keystores + android build.json
33
+ adaptfully apple-signing # one-time: CSR / .p12 / provisioning files under ios/apple/
32
34
  ```
33
35
 
34
36
  | Stage | What it does |
@@ -39,6 +41,10 @@ adaptfully apple-publish # one-time: write App Store Connec
39
41
  | `steam-publish` | One-time local setup: install steamcmd, interactive login, write the `steam` deployment's `steam.json` (also updates `.gitignore`) |
40
42
  | `google-publish` | One-time local setup: import a Play Console service-account JSON into the `android` deployment folder (also updates `.gitignore`) |
41
43
  | `apple-publish` | One-time local setup: write App Store Connect credentials into the `ios` deployment folder (also updates `.gitignore`) |
44
+ | `android-keystore` | One-time local setup: generate debug/release keystores and write `android` `build.json` (also updates `.gitignore`) |
45
+ | `apple-signing` | One-time local setup: CSR → Apple `.cer` → `.p12` / provisioning profiles under `ios/apple/` (also updates `.gitignore`) |
46
+
47
+ First-time walkthroughs (manual steps + helper skip markers): [docs/credentials/](docs/credentials/README.md).
42
48
 
43
49
  `wrapfully-deploy` is a compatibility alias for `adaptfully deploy` when invoked with a Wrapfully builder name (`steam`, `win`, `android`, etc.).
44
50
 
@@ -547,10 +553,14 @@ For a single platform, pass the specific builder name.
547
553
 
548
554
  ### Platform package requirements
549
555
 
556
+ **First-time setup:** see [docs/credentials/](docs/credentials/README.md) for iOS, Android, and Steam walkthroughs (CLI helpers + full manual steps).
557
+
550
558
  Signing keys, provisioning profiles, and store credentials go in the deployment folder `./assets/meta/deployments/<deployment>/` on disk (the selected deployment is sent as `meta/publish/` in the zip). The examples below use `<deployment>` as a placeholder for the target key (e.g. `steam`, `web-prod`). **These files contain secrets** — add them to `.gitignore` and never commit them to a public repository. (Projects that have not adopted the `deployments/` layout may still place these under the legacy `./assets/meta/publish/`.)
551
559
 
552
560
  #### Android (`android`, `android-dev`)
553
561
 
562
+ First-time setup: [docs/credentials/android.md](docs/credentials/android.md) (`android-keystore`, `google-publish`).
563
+
554
564
  Place keystore files in `assets/meta/deployments/<deployment>/android/`. Include `assets/meta/deployments/<deployment>/build.json`:
555
565
 
556
566
  ```json
@@ -602,6 +612,8 @@ The file looks like:
602
612
 
603
613
  #### Apple (`ios`, `ios-dev`, `ios-sim`, `mac`)
604
614
 
615
+ First-time setup: [docs/credentials/ios.md](docs/credentials/ios.md) (`apple-signing`, `apple-publish`).
616
+
605
617
  **Capacitor iOS** signing is file-based under the deployment folder:
606
618
 
607
619
  ```
@@ -614,7 +626,7 @@ assets/meta/deployments/<deployment>/
614
626
  build.json # optional: { "apple": { "p12Password": "..." } }
615
627
  ```
616
628
 
617
- `adaptfully build` (zip-only) still packages this deployment folder for Capacitor so device debug builds can sign. Optional `build.json` may set `apple.p12Password` for the `.p12` passphrase (Wrapfully uses an ephemeral keychain per build). Team ID and profile UUID are read from the `.mobileprovision`. Enable **Sign in with Apple** on the App ID when using Capgo Apple auth.
629
+ `adaptfully build` (zip-only) still packages this deployment folder for Capacitor so device debug builds can sign. Optional `build.json` may set `apple.p12Password` for the `.p12` passphrase (defaults to empty when omitted). Team ID and profile UUID are read from the `.mobileprovision`. Enable **Sign in with Apple** on the App ID when using Capgo Apple auth.
618
630
 
619
631
  To deploy to the App Store, include `assets/meta/deployments/<deployment>/apple.json`. The easiest path is:
620
632
 
@@ -638,6 +650,8 @@ iOS IPA upload uses `username` + `password` via `xcrun altool --upload-app`. `ca
638
650
 
639
651
  #### Steam (`steam`, `steam-dev`)
640
652
 
653
+ First-time setup: [docs/credentials/steam.md](docs/credentials/steam.md) (`steam-publish`).
654
+
641
655
  `steam-dev` builds debug Electron binaries for Windows, Mac, and Linux without uploading to Steam. No `steam.json` credentials are required.
642
656
 
643
657
  For release uploads, run **`adaptfully steam-publish`** once on your machine. It installs steamcmd if needed, logs you in interactively (enter a Steam Guard code if prompted), and writes `assets/meta/deployments/steam/steam.json`:
@@ -0,0 +1,50 @@
1
+ # Credential setup guides
2
+
3
+ One-time local setup for signing and store-upload credentials under
4
+ `assets/meta/deployments/<deployment>/`.
5
+
6
+ These guides stay focused on **files in your game repo** and Adaptfully CLI helpers.
7
+ They do not cover which OS builds the artifact.
8
+
9
+ ## Guides
10
+
11
+ | Platform | Guide | Typical deployment folder |
12
+ |----------|--------|---------------------------|
13
+ | iOS | [ios.md](./ios.md) | `assets/meta/deployments/ios/` |
14
+ | Android | [android.md](./android.md) | `assets/meta/deployments/android/` |
15
+ | Steam | [steam.md](./steam.md) | `assets/meta/deployments/steam/` |
16
+
17
+ ## Helpers
18
+
19
+ | Command | Writes |
20
+ |---------|--------|
21
+ | `npx adaptfully android-keystore` | `android/*.keystore` + `build.json` |
22
+ | `npx adaptfully google-publish --from <sa.json>` | `google.json` |
23
+ | `npx adaptfully apple-signing` | CSR/key work files, `apple/*.p12`, optional `.mobileprovision` + `build.json` `apple.p12Password` |
24
+ | `npx adaptfully apple-publish` | `apple.json` (App Store Connect upload login) |
25
+ | `npx adaptfully steam-publish` | `steam.json` |
26
+
27
+ Each guide lists the **full manual process** and marks **Helper** steps with **skip to step N** when you use a command instead.
28
+
29
+ ## Debug vs release
30
+
31
+ | Goal | Credentials needed? |
32
+ |------|---------------------|
33
+ | `android-dev` / local debug APK | Project debug keystore (recommended) or host default |
34
+ | `android` Play release | Release keystore + `google.json` |
35
+ | `ios-dev` device IPA | `development.p12` + `development.mobileprovision` |
36
+ | `ios` App Store | `distribution.p12` + `app-store.mobileprovision` + `apple.json` |
37
+ | `ios-sim` | No device signing files |
38
+ | `steam-dev` | None |
39
+ | `steam` upload | `steam.json` |
40
+
41
+ ## Secrets
42
+
43
+ Do not commit credential files. Helpers update `.gitignore` with Adaptfully patterns such as:
44
+
45
+ - `assets/meta/deployments/**/build.json`
46
+ - `assets/meta/deployments/**/google.json`
47
+ - `assets/meta/deployments/**/apple.json`
48
+ - `assets/meta/deployments/**/steam.json`
49
+ - `assets/meta/deployments/**/android/`
50
+ - `assets/meta/deployments/**/apple/`
@@ -0,0 +1,124 @@
1
+ # Android credentials
2
+
3
+ Set up signing keystores and (for Play uploads) a Google Play service account.
4
+
5
+ **Default folder:** `assets/meta/deployments/android/`
6
+
7
+ ## Recommended path
8
+
9
+ 1. **Helper:** `npx adaptfully android-keystore` — creates debug + release keystores and `build.json`.
10
+ 2. In Google Play Console, create a service account and download its JSON key (manual).
11
+ 3. **Helper:** `npx adaptfully google-publish --from ./path/to/sa.json` — writes `google.json`.
12
+
13
+ Debug-only: `npx adaptfully android-keystore --debug-only`.
14
+
15
+ ## What you need
16
+
17
+ | Goal | Files |
18
+ |------|--------|
19
+ | Debug / `android-dev` | `android/debug.keystore` + `build.json` `android.debug` block |
20
+ | Play release / `android` | `android/release.keystore` + `build.json` `android.release` + `google.json` |
21
+
22
+ `build.json` keystore paths are relative to the deployment folder (the directory that contains `build.json`).
23
+
24
+ ## Full steps
25
+
26
+ ### 1. Prerequisites
27
+
28
+ - JDK on PATH so `keytool` works (`keytool -help`).
29
+ - A Play Console app (for release uploads).
30
+
31
+ ### 2. Helper: generate keystores
32
+
33
+ **Helper:** `npx adaptfully android-keystore [--deployment android] [--debug-only] [--yes]`
34
+
35
+ If you run this, **skip to step 5** (Play service account). For debug-only, you can stop after this step.
36
+
37
+ Flags:
38
+
39
+ - `--debug-only` — only the debug keystore + `build.json` debug block
40
+ - `--yes` — overwrite existing `build.json` without prompting
41
+ - `--alias`, `--store-password`, `--key-password`, `--cn` — non-interactive release keystore fields
42
+
43
+ ### 3. Manual alternative: create keystores with keytool
44
+
45
+ Skip this if you used step 2.
46
+
47
+ ```bash
48
+ mkdir -p assets/meta/deployments/android/android
49
+
50
+ keytool -genkeypair -v \
51
+ -keystore assets/meta/deployments/android/android/debug.keystore \
52
+ -alias androiddebugkey -keyalg RSA -keysize 2048 -validity 10000 \
53
+ -storepass android -keypass android \
54
+ -dname "CN=Android Debug,O=Android,C=US"
55
+
56
+ keytool -genkeypair -v \
57
+ -keystore assets/meta/deployments/android/android/release.keystore \
58
+ -alias upload -keyalg RSA -keysize 2048 -validity 10000 \
59
+ -storepass "(your store password)" -keypass "(your key password)" \
60
+ -dname "CN=Your Studio,O=Your Studio,C=US"
61
+ ```
62
+
63
+ ### 4. Manual alternative: write `build.json`
64
+
65
+ Skip this if you used step 2.
66
+
67
+ Create `assets/meta/deployments/android/build.json`:
68
+
69
+ ```json
70
+ {
71
+ "android": {
72
+ "debug": {
73
+ "keystore": "./android/debug.keystore",
74
+ "packageType": "apk",
75
+ "storePassword": "android",
76
+ "alias": "androiddebugkey",
77
+ "password": "android",
78
+ "keystoreType": ""
79
+ },
80
+ "release": {
81
+ "keystore": "./android/release.keystore",
82
+ "packageType": "bundle",
83
+ "storePassword": "(your store password)",
84
+ "alias": "upload",
85
+ "password": "(your key password)",
86
+ "keystoreType": ""
87
+ }
88
+ }
89
+ }
90
+ ```
91
+
92
+ ### 5. Create a Play Console service account (manual)
93
+
94
+ Required only for Play uploads (`adaptfully release android` / deploy to Google).
95
+
96
+ 1. Open [Google Play Console](https://play.google.com/console/) → **Setup** → **API access** (or Google Cloud IAM linked to the Play developer account).
97
+ 2. Create or select a service account with permission to manage releases.
98
+ 3. Download a JSON key for that service account.
99
+ 4. Invite the service account email in Play Console with the right app permissions.
100
+
101
+ ### 6. Helper: import `google.json`
102
+
103
+ **Helper:** `npx adaptfully google-publish --from ./path/to/service-account.json [--deployment android]`
104
+
105
+ If you run this, **skip to step 8**.
106
+
107
+ ### 7. Manual alternative: copy the service account JSON
108
+
109
+ Skip this if you used step 6.
110
+
111
+ Copy the downloaded key to `assets/meta/deployments/android/google.json`. Ensure `manifest.json` includes `"type": "google"` (helpers write this for you).
112
+
113
+ ### 8. Done when
114
+
115
+ - [ ] `build.json` exists with debug (and release, if shipping)
116
+ - [ ] Keystore files exist at the paths listed in `build.json`
117
+ - [ ] For Play uploads: `google.json` is present and the service account can access the app
118
+ - [ ] Credential paths are gitignored
119
+
120
+ ## Notes
121
+
122
+ - Prefer **project-owned** keystores so debug installs stay reproducible across machines.
123
+ - Some build hosts supply a default debug keystore when `build.json` is missing; do not rely on that for shared projects.
124
+ - Keep release keystore passwords offline; losing the release keystore can block Play updates for that signing key.
@@ -0,0 +1,182 @@
1
+ # iOS credentials
2
+
3
+ Set up Apple signing files and (for App Store uploads) App Store Connect login credentials.
4
+
5
+ **Default folder:** `assets/meta/deployments/ios/`
6
+
7
+ You can create signing material on **macOS or Windows**. The sections below branch where the local tools differ.
8
+
9
+ ## Recommended path
10
+
11
+ 1. **Helper:** `npx adaptfully apple-signing --kind development` (and/or `--kind distribution`) — generates a CSR, walks portal checklist, exports `.p12`, optionally copies the provisioning profile.
12
+ 2. Complete the Apple Developer portal steps the helper prints (upload CSR, download `.cer` / `.mobileprovision`).
13
+ 3. For App Store uploads only — **Helper:** `npx adaptfully apple-publish` — writes `apple.json`.
14
+
15
+ Simulator builds (`ios-sim`) need no device signing files.
16
+
17
+ ## What you need
18
+
19
+ | Goal | Files |
20
+ |------|--------|
21
+ | Device debug (`ios-dev`) | `apple/development.p12`, `apple/development.mobileprovision` |
22
+ | App Store (`ios` release) | `apple/distribution.p12`, `apple/app-store.mobileprovision`, `apple.json` |
23
+ | Simulator (`ios-sim`) | None |
24
+
25
+ Optional `build.json` next to `apple/`:
26
+
27
+ ```json
28
+ {
29
+ "apple": {
30
+ "p12Password": "(passphrase used when exporting the .p12)"
31
+ }
32
+ }
33
+ ```
34
+
35
+ If omitted, the build pipeline treats the passphrase as empty.
36
+
37
+ ## Prerequisites
38
+
39
+ - Apple Developer Program membership.
40
+ - An App ID matching your platform `packageName`.
41
+ - If you use Capgo Apple social login: enable **Sign in with Apple** on that App ID.
42
+ - For development profiles: registered device UDIDs.
43
+ - OpenSSL on PATH for the helper / Windows manual path (`openssl version`). macOS Keychain users can still use the helper if OpenSSL is available (e.g. via Homebrew or Xcode CLT tooling).
44
+
45
+ ## Full steps
46
+
47
+ ### 1. Helper: CSR, `.p12`, and profile placement
48
+
49
+ **Helper:** `npx adaptfully apple-signing [--kind development|distribution] [--csr-only] [--from-cer path] [--provision path] [--p12-password secret]`
50
+
51
+ If you run the full interactive helper (not `--csr-only`), **skip to step 7** after it finishes (or step 8 for App Store Connect login).
52
+
53
+ Useful flags:
54
+
55
+ - `--csr-only` — stop after writing the CSR; later continue with `--from-cer`
56
+ - `--from-cer ./Certificates.cer` — skip CSR generation when `apple/.work/private.key` already exists
57
+ - `--provision ./Profile.mobileprovision` — copy/rename into `apple/`
58
+ - `--kind development` or `--kind distribution`
59
+
60
+ Work files live under `assets/meta/deployments/ios/apple/.work/` by default (`private.key` + CSR).
61
+
62
+ ### 2. Create a Certificate Signing Request (manual)
63
+
64
+ Skip this if you used step 1 (or will use `--from-cer` with an existing key from step 1).
65
+
66
+ #### macOS (Keychain Access)
67
+
68
+ 1. Open **Keychain Access** → **Certificate Assistant** → **Request a Certificate From a Certificate Authority…**
69
+ 2. Enter your email and common name; choose **Saved to disk**.
70
+ 3. Save the `.certSigningRequest` file.
71
+
72
+ #### Windows / OpenSSL (any OS)
73
+
74
+ ```bash
75
+ mkdir -p assets/meta/deployments/ios/apple/.work
76
+ cd assets/meta/deployments/ios/apple/.work
77
+
78
+ openssl genrsa -out private.key 2048
79
+ openssl req -new -key private.key -out CertificateSigningRequest.certSigningRequest -subj "/emailAddress=you@example.com/CN=Your Name/C=US"
80
+ ```
81
+
82
+ ### 3. Create the certificate on the Apple Developer site (manual)
83
+
84
+ Always required — Apple must issue the certificate.
85
+
86
+ 1. Open [Certificates](https://developer.apple.com/account/resources/certificates/list).
87
+ 2. Create **Apple Development** (device debug) and/or **Apple Distribution** (App Store).
88
+ 3. Upload the CSR from step 1 or 2.
89
+ 4. Download the `.cer` file.
90
+
91
+ ### 4. Export a `.p12` (manual)
92
+
93
+ Skip this if step 1 already exported `development.p12` / `distribution.p12`.
94
+
95
+ #### macOS (Keychain Access)
96
+
97
+ 1. Double-click the `.cer` to import it into Keychain Access.
98
+ 2. Find the certificate (and its private key) → **Export…** → `.p12`.
99
+ 3. Choose a passphrase; remember it for `build.json` `apple.p12Password`.
100
+ 4. Save as `assets/meta/deployments/ios/apple/development.p12` or `distribution.p12`.
101
+
102
+ #### Windows / OpenSSL
103
+
104
+ ```bash
105
+ # From the folder that has private.key and the downloaded .cer
106
+ openssl x509 -in Certificates.cer -inform DER -out cert.pem -outform PEM
107
+ openssl pkcs12 -export -inkey private.key -in cert.pem -out development.p12
108
+ # or distribution.p12 — use the matching kind
109
+ ```
110
+
111
+ Move the `.p12` to `assets/meta/deployments/ios/apple/`.
112
+
113
+ If DER conversion fails, try importing the `.cer` as PEM directly in the `pkcs12 -export` `-in` argument.
114
+
115
+ ### 5. Create and download a provisioning profile (manual)
116
+
117
+ Always required for device / App Store signing.
118
+
119
+ 1. Register devices (development) under [Devices](https://developer.apple.com/account/resources/devices/list).
120
+ 2. Open [Profiles](https://developer.apple.com/account/resources/profiles/list).
121
+ 3. Create a **Development** profile or **App Store** profile for your App ID and certificate.
122
+ 4. Download the `.mobileprovision`.
123
+
124
+ ### 6. Place the provisioning profile (manual)
125
+
126
+ Skip this if step 1 copied it with `--provision`.
127
+
128
+ Rename/copy to:
129
+
130
+ - Development → `assets/meta/deployments/ios/apple/development.mobileprovision`
131
+ - App Store → `assets/meta/deployments/ios/apple/app-store.mobileprovision`
132
+
133
+ ### 7. Optional: record the `.p12` passphrase
134
+
135
+ Skip this if `apple-signing` already wrote `build.json`.
136
+
137
+ Create or merge `assets/meta/deployments/ios/build.json`:
138
+
139
+ ```json
140
+ {
141
+ "apple": {
142
+ "p12Password": "(your .p12 passphrase)"
143
+ }
144
+ }
145
+ ```
146
+
147
+ ### 8. Helper: App Store Connect login (`apple.json`)
148
+
149
+ Required only for App Store uploads.
150
+
151
+ **Helper:** `npx adaptfully apple-publish [--deployment ios]`
152
+
153
+ If you run this, **skip to step 10**.
154
+
155
+ You need an [app-specific password](https://appleid.apple.com/) for your Apple ID.
156
+
157
+ ### 9. Manual alternative: write `apple.json`
158
+
159
+ Skip this if you used step 8.
160
+
161
+ ```json
162
+ {
163
+ "category": "public.app-category.games",
164
+ "identity": "(your team id)",
165
+ "username": "(Apple ID email)",
166
+ "password": "(app-specific password)"
167
+ }
168
+ ```
169
+
170
+ ### 10. Done when
171
+
172
+ - [ ] For `ios-dev`: `apple/development.p12` + `apple/development.mobileprovision`
173
+ - [ ] For App Store: `apple/distribution.p12` + `apple/app-store.mobileprovision` + `apple.json`
174
+ - [ ] `apple.p12Password` matches the `.p12` passphrase when the passphrase is non-empty
175
+ - [ ] App ID matches `packageName`; Sign in with Apple enabled if you use Capgo Apple auth
176
+ - [ ] Credential paths are gitignored
177
+
178
+ ## Notes
179
+
180
+ - Filenames under `apple/` must match the table above.
181
+ - You can create credentials on Windows even if the IPA is built elsewhere.
182
+ - Re-run `apple-signing --kind …` separately for development and distribution when you need both.
@@ -0,0 +1,99 @@
1
+ # Steam credentials
2
+
3
+ Set up Steamworks upload login for release deploys. Debug Steam builds need no upload credentials.
4
+
5
+ **Default folder:** `assets/meta/deployments/steam/`
6
+
7
+ ## Recommended path
8
+
9
+ 1. Create or choose a Steamworks **build account** (manual; prefer a dedicated publisher account).
10
+ 2. **Helper:** `npx adaptfully steam-publish` — installs steamcmd if needed, logs in interactively, writes `steam.json`.
11
+ 3. Set upload `steamId` on the deployment `manifest.json` (and client `platforms.*.steamId` if you use Steamworks auth).
12
+
13
+ ## What you need
14
+
15
+ | Goal | Files |
16
+ |------|--------|
17
+ | `steam-dev` (local Electron debug) | None |
18
+ | `steam` release upload | `steam.json` (+ `manifest.json` with upload `steamId`) |
19
+
20
+ ## Full steps
21
+
22
+ ### 1. Prerequisites
23
+
24
+ - A Steamworks partner app.
25
+ - A Steam account allowed to upload builds (Steam Guard may be enabled).
26
+
27
+ ### 2. Helper: write `steam.json`
28
+
29
+ **Helper:** `npx adaptfully steam-publish [--username U] [--password P] [--deployment steam]`
30
+
31
+ If you run this, **skip to step 6**.
32
+
33
+ The helper installs steamcmd under `~/.adaptfully/steamcmd` when needed, logs in (enter a Steam Guard code if prompted), and writes `assets/meta/deployments/steam/steam.json`.
34
+
35
+ ### 3. Manual alternative: install steamcmd
36
+
37
+ Skip this if you used step 2.
38
+
39
+ Download steamcmd for your OS from Valve and unpack it somewhere durable.
40
+
41
+ ### 4. Manual alternative: log in once
42
+
43
+ Skip this if you used step 2.
44
+
45
+ ```bash
46
+ steamcmd +login YOUR_USER YOUR_PASSWORD +quit
47
+ ```
48
+
49
+ Complete Steam Guard if prompted. After a successful login, steamcmd stores session files under its install directory (`config/config.vdf`, and an `ssfn*` sentry file when Guard is enabled).
50
+
51
+ ### 5. Manual alternative: assemble `steam.json`
52
+
53
+ Skip this if you used step 2.
54
+
55
+ Create `assets/meta/deployments/steam/steam.json`:
56
+
57
+ ```json
58
+ {
59
+ "username": "(your Steam build account)",
60
+ "password": "(your password)",
61
+ "configVdf": "(base64 of config/config.vdf)",
62
+ "sentryFileName": "(ssfn filename — only with Steam Guard)",
63
+ "sentryFile": "(base64 of the ssfn file — only with Steam Guard)"
64
+ }
65
+ ```
66
+
67
+ Accounts **without** Steam Guard only need `username`, `password`, and `configVdf`.
68
+
69
+ ### 6. App IDs (client vs upload)
70
+
71
+ These are independent:
72
+
73
+ | Setting | Purpose |
74
+ |---------|---------|
75
+ | `config.platforms.<name>.steamId` | Client Steamworks / `steam-auth` |
76
+ | `assets/meta/deployments/steam/manifest.json` → `steamId` | Depot upload target |
77
+
78
+ You can ship with Steam auth and no upload credentials, or upload without client Steamworks.
79
+
80
+ Example `manifest.json`:
81
+
82
+ ```json
83
+ {
84
+ "type": "steam",
85
+ "steamId": 480
86
+ }
87
+ ```
88
+
89
+ ### 7. Done when
90
+
91
+ - [ ] For uploads: `steam.json` exists and login tokens are valid
92
+ - [ ] Upload `steamId` is set on the deployment manifest
93
+ - [ ] Client `steamId` is set on the platform if you use Steamworks auth
94
+ - [ ] Credential files are gitignored
95
+
96
+ ## Notes
97
+
98
+ - Re-run `adaptfully steam-publish` when Steam invalidates the token (new machine, password change, or expired Guard).
99
+ - `steam-dev` never requires `steam.json`.
@@ -0,0 +1,243 @@
1
+ import { spawn } from 'node:child_process';
2
+ import fsp from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import {
5
+ defaultDeploymentCredentialPath,
6
+ ensureDeploymentManifest,
7
+ ensurePublishCredentialsGitignore,
8
+ parsePublishCredentialArgv,
9
+ promptConfirm,
10
+ promptRequired,
11
+ } from './publish-credentials.js';
12
+
13
+ const DEBUG_ALIAS = 'androiddebugkey';
14
+ const DEBUG_PASSWORD = 'android';
15
+ const DEBUG_DNAME = 'CN=Android Debug,O=Android,C=US';
16
+
17
+ /**
18
+ * @param {string} [projectRoot]
19
+ * @param {string} [deploymentKey='android']
20
+ */
21
+ export function defaultAndroidDeploymentDir(projectRoot = '.', deploymentKey = 'android') {
22
+ return path.dirname(defaultDeploymentCredentialPath(projectRoot, deploymentKey, 'build.json'));
23
+ }
24
+
25
+ /**
26
+ * @param {{
27
+ * debugOnly?: boolean,
28
+ * debug?: { keystore: string, storePassword: string, alias: string, password: string },
29
+ * release?: { keystore: string, storePassword: string, alias: string, password: string },
30
+ * }} options
31
+ */
32
+ export function buildAndroidBuildJson(options = {}) {
33
+ /** @type {Record<string, unknown>} */
34
+ const android = {
35
+ debug: {
36
+ keystore: options.debug?.keystore ?? './android/debug.keystore',
37
+ packageType: 'apk',
38
+ storePassword: options.debug?.storePassword ?? DEBUG_PASSWORD,
39
+ alias: options.debug?.alias ?? DEBUG_ALIAS,
40
+ password: options.debug?.password ?? DEBUG_PASSWORD,
41
+ keystoreType: '',
42
+ },
43
+ };
44
+
45
+ if (!options.debugOnly && options.release) {
46
+ android.release = {
47
+ keystore: options.release.keystore,
48
+ packageType: 'bundle',
49
+ storePassword: options.release.storePassword,
50
+ alias: options.release.alias,
51
+ password: options.release.password,
52
+ keystoreType: '',
53
+ };
54
+ }
55
+
56
+ return { android };
57
+ }
58
+
59
+ /**
60
+ * @param {string[]} args
61
+ * @param {{ command?: string }} [options]
62
+ * @returns {Promise<void>}
63
+ */
64
+ export function runKeytool(args, options = {}) {
65
+ const command = options.command ?? 'keytool';
66
+
67
+ return new Promise((resolve, reject) => {
68
+ const child = spawn(command, args, {
69
+ stdio: ['ignore', 'pipe', 'pipe'],
70
+ shell: process.platform === 'win32',
71
+ });
72
+
73
+ let stderr = '';
74
+ child.stderr?.on('data', (chunk) => {
75
+ stderr += String(chunk);
76
+ });
77
+ child.on('error', (err) => {
78
+ reject(new Error(
79
+ `Unable to run "${command}". Install a JDK and ensure keytool is on PATH. (${err.message})`,
80
+ ));
81
+ });
82
+ child.on('close', (code) => {
83
+ if (code === 0) {
84
+ resolve();
85
+ return;
86
+ }
87
+ reject(new Error(`keytool failed (exit ${code}): ${stderr.trim() || 'no output'}`));
88
+ });
89
+ });
90
+ }
91
+
92
+ /**
93
+ * @param {{
94
+ * keystorePath: string,
95
+ * alias: string,
96
+ * storePassword: string,
97
+ * keyPassword: string,
98
+ * dname: string,
99
+ * validityDays?: number,
100
+ * runKeytoolFn?: typeof runKeytool,
101
+ * }} options
102
+ */
103
+ export async function generateKeystore(options) {
104
+ const run = options.runKeytoolFn ?? runKeytool;
105
+ await fsp.mkdir(path.dirname(options.keystorePath), { recursive: true });
106
+ await run([
107
+ '-genkeypair',
108
+ '-v',
109
+ '-keystore', options.keystorePath,
110
+ '-alias', options.alias,
111
+ '-keyalg', 'RSA',
112
+ '-keysize', '2048',
113
+ '-validity', String(options.validityDays ?? 10000),
114
+ '-storepass', options.storePassword,
115
+ '-keypass', options.keyPassword,
116
+ '-dname', options.dname,
117
+ ]);
118
+ }
119
+
120
+ /**
121
+ * @param {{
122
+ * deployment?: string,
123
+ * projectRoot?: string,
124
+ * debugOnly?: boolean,
125
+ * yes?: boolean,
126
+ * alias?: string,
127
+ * storePassword?: string,
128
+ * keyPassword?: string,
129
+ * cn?: string,
130
+ * log?: (message: string) => void,
131
+ * runKeytoolFn?: typeof runKeytool,
132
+ * }} [options]
133
+ */
134
+ export async function runAndroidKeystore(options = {}) {
135
+ const log = options.log ?? console.log;
136
+ const projectRoot = options.projectRoot ?? '.';
137
+ const deploymentKey = options.deployment ?? 'android';
138
+ const deploymentDir = defaultAndroidDeploymentDir(projectRoot, deploymentKey);
139
+ const keystoreDir = path.join(deploymentDir, 'android');
140
+ const buildJsonPath = path.join(deploymentDir, 'build.json');
141
+ const debugOnly = options.debugOnly === true;
142
+
143
+ let buildJsonExists = false;
144
+ try {
145
+ await fsp.access(buildJsonPath);
146
+ buildJsonExists = true;
147
+ } catch {
148
+ buildJsonExists = false;
149
+ }
150
+
151
+ if (buildJsonExists) {
152
+ const overwrite = options.yes === true
153
+ || await promptConfirm(`Overwrite existing ${buildJsonPath}?`, false);
154
+ if (!overwrite) {
155
+ throw new Error(`Refusing to overwrite ${buildJsonPath}. Pass --yes to overwrite.`);
156
+ }
157
+ }
158
+
159
+ const debugKeystorePath = path.join(keystoreDir, 'debug.keystore');
160
+ log(`adaptfully: generating debug keystore → ${debugKeystorePath}`);
161
+ await generateKeystore({
162
+ keystorePath: debugKeystorePath,
163
+ alias: DEBUG_ALIAS,
164
+ storePassword: DEBUG_PASSWORD,
165
+ keyPassword: DEBUG_PASSWORD,
166
+ dname: DEBUG_DNAME,
167
+ runKeytoolFn: options.runKeytoolFn,
168
+ });
169
+
170
+ /** @type {{ keystore: string, storePassword: string, alias: string, password: string } | undefined} */
171
+ let release;
172
+
173
+ if (!debugOnly) {
174
+ const alias = options.alias
175
+ ?? await promptRequired('Release keystore alias: ');
176
+ const storePassword = options.storePassword
177
+ ?? await promptRequired('Release store password: ');
178
+ const keyPassword = options.keyPassword
179
+ ?? options.storePassword
180
+ ?? await promptRequired('Release key password (often same as store): ');
181
+ const cn = options.cn
182
+ ?? await promptRequired('Certificate CN (e.g. Your Studio): ');
183
+
184
+ const releaseKeystorePath = path.join(keystoreDir, 'release.keystore');
185
+ log(`adaptfully: generating release keystore → ${releaseKeystorePath}`);
186
+ await generateKeystore({
187
+ keystorePath: releaseKeystorePath,
188
+ alias,
189
+ storePassword,
190
+ keyPassword,
191
+ dname: `CN=${cn},O=${cn},C=US`,
192
+ runKeytoolFn: options.runKeytoolFn,
193
+ });
194
+
195
+ release = {
196
+ keystore: './android/release.keystore',
197
+ storePassword,
198
+ alias,
199
+ password: keyPassword,
200
+ };
201
+ }
202
+
203
+ const buildJson = buildAndroidBuildJson({
204
+ debugOnly,
205
+ debug: {
206
+ keystore: './android/debug.keystore',
207
+ storePassword: DEBUG_PASSWORD,
208
+ alias: DEBUG_ALIAS,
209
+ password: DEBUG_PASSWORD,
210
+ },
211
+ release,
212
+ });
213
+
214
+ await fsp.mkdir(deploymentDir, { recursive: true });
215
+ await fsp.writeFile(buildJsonPath, `${JSON.stringify(buildJson, null, 4)}\n`);
216
+ await ensureDeploymentManifest(deploymentDir, 'google', log);
217
+ await ensurePublishCredentialsGitignore(projectRoot, {
218
+ extraPaths: [buildJsonPath, keystoreDir],
219
+ log,
220
+ });
221
+
222
+ log(`adaptfully: wrote ${buildJsonPath}`);
223
+ if (debugOnly) {
224
+ log('adaptfully: debug-only keystore ready. For Play uploads, re-run without --debug-only, then use google-publish.');
225
+ } else {
226
+ log('adaptfully: next — create a Play Console service account JSON, then: adaptfully google-publish --from <sa.json>');
227
+ }
228
+
229
+ return {
230
+ deploymentKey,
231
+ deploymentDir,
232
+ buildJsonPath,
233
+ buildJson,
234
+ debugOnly,
235
+ };
236
+ }
237
+
238
+ /**
239
+ * @param {string[]} [argv]
240
+ */
241
+ export async function androidKeystoreFromCli(argv = process.argv) {
242
+ return runAndroidKeystore(parsePublishCredentialArgv(argv));
243
+ }
@@ -0,0 +1,392 @@
1
+ import { spawn } from 'node:child_process';
2
+ import fsp from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import {
5
+ defaultDeploymentCredentialPath,
6
+ ensureDeploymentManifest,
7
+ ensurePublishCredentialsGitignore,
8
+ parsePublishCredentialArgv,
9
+ promptConfirm,
10
+ promptRequired,
11
+ resolveUserPath,
12
+ } from './publish-credentials.js';
13
+
14
+ /** @typedef {'development' | 'distribution'} AppleSigningKind */
15
+
16
+ /**
17
+ * @param {string} [projectRoot]
18
+ * @param {string} [deploymentKey='ios']
19
+ */
20
+ export function defaultAppleSigningDir(projectRoot = '.', deploymentKey = 'ios') {
21
+ return path.join(
22
+ path.dirname(defaultDeploymentCredentialPath(projectRoot, deploymentKey, 'apple.json')),
23
+ 'apple',
24
+ );
25
+ }
26
+
27
+ /**
28
+ * @param {string} kind
29
+ * @returns {AppleSigningKind}
30
+ */
31
+ export function normalizeAppleSigningKind(kind) {
32
+ const value = String(kind ?? '').trim().toLowerCase();
33
+ if (value === 'development' || value === 'dev' || value === 'debug') {
34
+ return 'development';
35
+ }
36
+ if (value === 'distribution' || value === 'dist' || value === 'release' || value === 'app-store') {
37
+ return 'distribution';
38
+ }
39
+ throw new Error(
40
+ `Unknown Apple signing kind "${kind}". Use "development" or "distribution".`,
41
+ );
42
+ }
43
+
44
+ /**
45
+ * @param {AppleSigningKind} kind
46
+ */
47
+ export function appleSigningFilenames(kind) {
48
+ if (kind === 'development') {
49
+ return {
50
+ p12: 'development.p12',
51
+ provision: 'development.mobileprovision',
52
+ certLabel: 'Apple Development',
53
+ };
54
+ }
55
+ return {
56
+ p12: 'distribution.p12',
57
+ provision: 'app-store.mobileprovision',
58
+ certLabel: 'Apple Distribution',
59
+ };
60
+ }
61
+
62
+ /**
63
+ * Merge or create build.json with apple.p12Password.
64
+ * @param {object | null | undefined} existing
65
+ * @param {string} p12Password
66
+ */
67
+ export function mergeAppleP12PasswordBuildJson(existing, p12Password) {
68
+ /** @type {Record<string, unknown>} */
69
+ const next = existing && typeof existing === 'object' && !Array.isArray(existing)
70
+ ? { ...existing }
71
+ : {};
72
+ const apple = next.apple && typeof next.apple === 'object' && !Array.isArray(next.apple)
73
+ ? { .../** @type {Record<string, unknown>} */ (next.apple) }
74
+ : {};
75
+ apple.p12Password = p12Password;
76
+ next.apple = apple;
77
+ return next;
78
+ }
79
+
80
+ /**
81
+ * @param {string[]} args
82
+ * @param {{ command?: string }} [options]
83
+ * @returns {Promise<void>}
84
+ */
85
+ export function runOpenssl(args, options = {}) {
86
+ const command = options.command ?? 'openssl';
87
+
88
+ return new Promise((resolve, reject) => {
89
+ const child = spawn(command, args, {
90
+ stdio: ['ignore', 'pipe', 'pipe'],
91
+ shell: process.platform === 'win32',
92
+ });
93
+
94
+ let stderr = '';
95
+ child.stderr?.on('data', (chunk) => {
96
+ stderr += String(chunk);
97
+ });
98
+ child.on('error', (err) => {
99
+ reject(new Error(
100
+ `Unable to run "${command}". Install OpenSSL and ensure it is on PATH. (${err.message})`,
101
+ ));
102
+ });
103
+ child.on('close', (code) => {
104
+ if (code === 0) {
105
+ resolve();
106
+ return;
107
+ }
108
+ reject(new Error(`openssl failed (exit ${code}): ${stderr.trim() || 'no output'}`));
109
+ });
110
+ });
111
+ }
112
+
113
+ /**
114
+ * @param {{
115
+ * workdir: string,
116
+ * cn: string,
117
+ * email?: string,
118
+ * runOpensslFn?: typeof runOpenssl,
119
+ * }} options
120
+ */
121
+ export async function generateAppleCsr(options) {
122
+ const run = options.runOpensslFn ?? runOpenssl;
123
+ await fsp.mkdir(options.workdir, { recursive: true });
124
+
125
+ const keyPath = path.join(options.workdir, 'private.key');
126
+ const csrPath = path.join(options.workdir, 'CertificateSigningRequest.certSigningRequest');
127
+ const subject = options.email
128
+ ? `/emailAddress=${options.email}/CN=${options.cn}/C=US`
129
+ : `/CN=${options.cn}/C=US`;
130
+
131
+ await run(['genrsa', '-out', keyPath, '2048']);
132
+ await run([
133
+ 'req',
134
+ '-new',
135
+ '-key', keyPath,
136
+ '-out', csrPath,
137
+ '-subj', subject,
138
+ ]);
139
+
140
+ return { keyPath, csrPath };
141
+ }
142
+
143
+ /**
144
+ * @param {{
145
+ * cerPath: string,
146
+ * keyPath: string,
147
+ * p12Path: string,
148
+ * p12Password: string,
149
+ * runOpensslFn?: typeof runOpenssl,
150
+ * }} options
151
+ */
152
+ export async function exportAppleP12(options) {
153
+ const run = options.runOpensslFn ?? runOpenssl;
154
+ await fsp.mkdir(path.dirname(options.p12Path), { recursive: true });
155
+
156
+ const pemPath = `${options.cerPath}.pem`;
157
+ await run(['x509', '-in', options.cerPath, '-inform', 'DER', '-out', pemPath, '-outform', 'PEM']);
158
+
159
+ try {
160
+ await run([
161
+ 'pkcs12',
162
+ '-export',
163
+ '-inkey', options.keyPath,
164
+ '-in', pemPath,
165
+ '-out', options.p12Path,
166
+ '-passout', `pass:${options.p12Password}`,
167
+ ]);
168
+ } catch (derErr) {
169
+ // Some downloads are already PEM
170
+ try {
171
+ await run([
172
+ 'pkcs12',
173
+ '-export',
174
+ '-inkey', options.keyPath,
175
+ '-in', options.cerPath,
176
+ '-out', options.p12Path,
177
+ '-passout', `pass:${options.p12Password}`,
178
+ ]);
179
+ } catch {
180
+ throw derErr;
181
+ }
182
+ } finally {
183
+ try {
184
+ await fsp.unlink(pemPath);
185
+ } catch {
186
+ // ignore
187
+ }
188
+ }
189
+ }
190
+
191
+ /**
192
+ * @param {(message: string) => void} log
193
+ * @param {AppleSigningKind} kind
194
+ * @param {string} csrPath
195
+ */
196
+ function printPortalChecklist(log, kind, csrPath) {
197
+ const names = appleSigningFilenames(kind);
198
+ log('');
199
+ log('adaptfully: Apple Developer portal steps (browser):');
200
+ log(` 1. Open https://developer.apple.com/account/resources/certificates/list`);
201
+ log(` 2. Create a new "${names.certLabel}" certificate`);
202
+ log(` 3. Upload CSR: ${csrPath}`);
203
+ log(' 4. Download the .cer file');
204
+ log(' 5. Ensure your App ID exists; enable Sign in with Apple if you use Capgo Apple auth');
205
+ if (kind === 'development') {
206
+ log(' 6. Register device UDIDs, then create a Development provisioning profile');
207
+ } else {
208
+ log(' 6. Create an App Store Connect provisioning profile for the App ID');
209
+ }
210
+ log(' 7. Download the .mobileprovision file');
211
+ log('');
212
+ }
213
+
214
+ /**
215
+ * @param {{
216
+ * deployment?: string,
217
+ * projectRoot?: string,
218
+ * workdir?: string,
219
+ * csrOnly?: boolean,
220
+ * fromCer?: string,
221
+ * kind?: string,
222
+ * provision?: string,
223
+ * p12Password?: string,
224
+ * cn?: string,
225
+ * email?: string,
226
+ * yes?: boolean,
227
+ * log?: (message: string) => void,
228
+ * runOpensslFn?: typeof runOpenssl,
229
+ * }} [options]
230
+ */
231
+ export async function runAppleSigning(options = {}) {
232
+ const log = options.log ?? console.log;
233
+ const projectRoot = options.projectRoot ?? '.';
234
+ const deploymentKey = options.deployment ?? 'ios';
235
+ const appleDir = defaultAppleSigningDir(projectRoot, deploymentKey);
236
+ const deploymentDir = path.dirname(appleDir);
237
+ const workdir = path.resolve(
238
+ options.workdir
239
+ ?? path.join(appleDir, '.work'),
240
+ );
241
+
242
+ let kind = options.kind
243
+ ? normalizeAppleSigningKind(options.kind)
244
+ : null;
245
+
246
+ if (!kind && (options.fromCer || options.provision || !options.csrOnly)) {
247
+ kind = normalizeAppleSigningKind(
248
+ await promptRequired('Signing kind (development|distribution): ', options.kind),
249
+ );
250
+ }
251
+
252
+ if (!kind) {
253
+ kind = 'development';
254
+ }
255
+
256
+ const names = appleSigningFilenames(kind);
257
+ const keyPath = path.join(workdir, 'private.key');
258
+ const csrPath = path.join(workdir, 'CertificateSigningRequest.certSigningRequest');
259
+
260
+ let createdCsr = false;
261
+ try {
262
+ await fsp.access(keyPath);
263
+ await fsp.access(csrPath);
264
+ } catch {
265
+ const cn = options.cn ?? await promptRequired('Common name for CSR (e.g. Your Name): ');
266
+ const email = options.email;
267
+ log(`adaptfully: generating CSR in ${workdir}`);
268
+ await generateAppleCsr({
269
+ workdir,
270
+ cn,
271
+ email,
272
+ runOpensslFn: options.runOpensslFn,
273
+ });
274
+ createdCsr = true;
275
+ }
276
+
277
+ printPortalChecklist(log, kind, csrPath);
278
+
279
+ if (options.csrOnly) {
280
+ await ensurePublishCredentialsGitignore(projectRoot, {
281
+ extraPaths: [appleDir],
282
+ log,
283
+ });
284
+ log('adaptfully: --csr-only set; stop here, then re-run with --from-cer after downloading the .cer');
285
+ return {
286
+ deploymentKey,
287
+ appleDir,
288
+ workdir,
289
+ csrPath,
290
+ keyPath,
291
+ kind,
292
+ csrOnly: true,
293
+ createdCsr,
294
+ };
295
+ }
296
+
297
+ const cerPath = resolveUserPath(
298
+ await promptRequired('Path to downloaded Apple .cer: ', options.fromCer),
299
+ );
300
+
301
+ const p12Password = options.p12Password != null
302
+ ? String(options.p12Password)
303
+ : await promptRequired('Passphrase for the .p12 (remember this for build.json apple.p12Password): ');
304
+
305
+ const p12Path = path.join(appleDir, names.p12);
306
+ log(`adaptfully: exporting ${p12Path}`);
307
+ await exportAppleP12({
308
+ cerPath,
309
+ keyPath,
310
+ p12Path,
311
+ p12Password,
312
+ runOpensslFn: options.runOpensslFn,
313
+ });
314
+
315
+ const buildJsonPath = path.join(deploymentDir, 'build.json');
316
+ let existingBuild = null;
317
+ try {
318
+ existingBuild = JSON.parse(await fsp.readFile(buildJsonPath, 'utf8'));
319
+ } catch {
320
+ existingBuild = null;
321
+ }
322
+
323
+ const writePassword = options.yes === true
324
+ || p12Password === ''
325
+ || await promptConfirm(
326
+ `Write apple.p12Password into ${buildJsonPath}?`,
327
+ true,
328
+ );
329
+
330
+ if (writePassword) {
331
+ const merged = mergeAppleP12PasswordBuildJson(existingBuild, p12Password);
332
+ await fsp.mkdir(deploymentDir, { recursive: true });
333
+ await fsp.writeFile(buildJsonPath, `${JSON.stringify(merged, null, 4)}\n`);
334
+ log(`adaptfully: wrote ${buildJsonPath}`);
335
+ }
336
+
337
+ let provisionPath = options.provision;
338
+ if (!provisionPath) {
339
+ if (options.yes === true) {
340
+ log(`adaptfully: --yes set without --provision; skipping provisioning profile copy`);
341
+ } else {
342
+ const rlAnswer = await promptRequired(
343
+ `Path to downloaded ${names.provision} (type skip to finish later): `,
344
+ );
345
+ if (rlAnswer.toLowerCase() !== 'skip') {
346
+ provisionPath = rlAnswer;
347
+ }
348
+ }
349
+ }
350
+
351
+ if (provisionPath) {
352
+ const dest = path.join(appleDir, names.provision);
353
+ await fsp.mkdir(appleDir, { recursive: true });
354
+ await fsp.copyFile(resolveUserPath(provisionPath), dest);
355
+ log(`adaptfully: copied provisioning profile → ${dest}`);
356
+ } else {
357
+ log(`adaptfully: skipped provisioning profile — place ${names.provision} under ${appleDir} when ready`);
358
+ }
359
+
360
+ await ensureDeploymentManifest(deploymentDir, 'apple', log);
361
+ await ensurePublishCredentialsGitignore(projectRoot, {
362
+ extraPaths: [appleDir, buildJsonPath],
363
+ log,
364
+ });
365
+
366
+ log(`adaptfully: signing files under ${appleDir}`);
367
+ if (kind === 'distribution') {
368
+ log('adaptfully: next for App Store upload — adaptfully apple-publish');
369
+ } else {
370
+ log('adaptfully: development signing ready for ios-dev device builds');
371
+ }
372
+
373
+ return {
374
+ deploymentKey,
375
+ appleDir,
376
+ workdir,
377
+ csrPath,
378
+ keyPath,
379
+ p12Path,
380
+ kind,
381
+ csrOnly: false,
382
+ createdCsr,
383
+ provisionPath: provisionPath ? resolveUserPath(provisionPath) : null,
384
+ };
385
+ }
386
+
387
+ /**
388
+ * @param {string[]} [argv]
389
+ */
390
+ export async function appleSigningFromCli(argv = process.argv) {
391
+ return runAppleSigning(parsePublishCredentialArgv(argv));
392
+ }
package/lib/node/index.js CHANGED
@@ -62,6 +62,7 @@ export {
62
62
  ensurePublishCredentialsGitignore,
63
63
  normalizeUserPath,
64
64
  parsePublishCredentialArgv,
65
+ promptConfirm,
65
66
  promptRequired,
66
67
  PUBLISH_CREDENTIAL_GITIGNORE_PATTERNS,
67
68
  resolveUserPath,
@@ -90,3 +91,22 @@ export {
90
91
  runApplePublish,
91
92
  validateApplePublishJson,
92
93
  } from './apple-publish.js';
94
+ export {
95
+ androidKeystoreFromCli,
96
+ buildAndroidBuildJson,
97
+ defaultAndroidDeploymentDir,
98
+ generateKeystore,
99
+ runAndroidKeystore,
100
+ runKeytool,
101
+ } from './android-keystore.js';
102
+ export {
103
+ appleSigningFilenames,
104
+ appleSigningFromCli,
105
+ defaultAppleSigningDir,
106
+ exportAppleP12,
107
+ generateAppleCsr,
108
+ mergeAppleP12PasswordBuildJson,
109
+ normalizeAppleSigningKind,
110
+ runAppleSigning,
111
+ runOpenssl,
112
+ } from './apple-signing.js';
@@ -12,6 +12,8 @@ import {
12
12
  import { steamPublishFromCli } from './steam-publish.js';
13
13
  import { googlePublishFromCli } from './google-publish.js';
14
14
  import { applePublishFromCli } from './apple-publish.js';
15
+ import { androidKeystoreFromCli } from './android-keystore.js';
16
+ import { appleSigningFromCli } from './apple-signing.js';
15
17
 
16
18
  /** @typedef {'prebuild' | 'build' | 'deploy' | 'release'} AdaptfullyStage */
17
19
 
@@ -20,6 +22,8 @@ const PUBLISH_COMMANDS = {
20
22
  'steam-publish': steamPublishFromCli,
21
23
  'google-publish': googlePublishFromCli,
22
24
  'apple-publish': applePublishFromCli,
25
+ 'android-keystore': androidKeystoreFromCli,
26
+ 'apple-signing': appleSigningFromCli,
23
27
  };
24
28
 
25
29
  /**
@@ -202,6 +206,8 @@ export async function adaptfullyFromCli(argv = process.argv) {
202
206
  + ' adaptfully steam-publish [--username U] [--password P] [--deployment steam] [--output path]\n'
203
207
  + ' adaptfully google-publish [--from service-account.json] [--deployment android] [--output path]\n'
204
208
  + ' adaptfully apple-publish [--deployment ios] [--category C] [--identity I] [--username U] [--password P]\n'
209
+ + ' adaptfully android-keystore [--deployment android] [--debug-only] [--yes]\n'
210
+ + ' adaptfully apple-signing [--deployment ios] [--kind development|distribution] [--csr-only] [--from-cer path] [--provision path]\n'
205
211
  + '\n'
206
212
  + 'Stages:\n'
207
213
  + ' prebuild Copy deploy/ and apply platform registrations → output/<platform>-prebuild/\n'
@@ -213,9 +219,11 @@ export async function adaptfullyFromCli(argv = process.argv) {
213
219
  + ' --artifact <path> Prior build artifact directory (deploy only; default: ./output/ after build).\n'
214
220
  + '\n'
215
221
  + 'Publish credential helpers (one-time local setup):\n'
216
- + ' steam-publish Install steamcmd if needed, log in, write steam deployment steam.json\n'
217
- + ' google-publish Import a Play service-account JSON into the android deployment folder\n'
218
- + ' apple-publish Write App Store Connect credentials into the ios deployment folder',
222
+ + ' steam-publish Install steamcmd if needed, log in, write steam deployment steam.json\n'
223
+ + ' google-publish Import a Play service-account JSON into the android deployment folder\n'
224
+ + ' apple-publish Write App Store Connect credentials into the ios deployment folder\n'
225
+ + ' android-keystore Generate debug/release keystores and write android build.json\n'
226
+ + ' apple-signing Generate CSR, export .p12, place provisioning profiles under ios/apple/',
219
227
  );
220
228
  }
221
229
 
@@ -211,13 +211,33 @@ export async function promptRequired(prompt, existing) {
211
211
  }
212
212
  }
213
213
 
214
+ /**
215
+ * @param {string} prompt
216
+ * @param {boolean} [defaultYes=false]
217
+ * @returns {Promise<boolean>}
218
+ */
219
+ export async function promptConfirm(prompt, defaultYes = false) {
220
+ const rl = readline.createInterface({ input, output });
221
+ const suffix = defaultYes ? ' [Y/n] ' : ' [y/N] ';
222
+
223
+ try {
224
+ const answer = String(await rl.question(`${prompt}${suffix}`)).trim().toLowerCase();
225
+ if (!answer) {
226
+ return defaultYes;
227
+ }
228
+ return answer === 'y' || answer === 'yes';
229
+ } finally {
230
+ rl.close();
231
+ }
232
+ }
233
+
214
234
  /**
215
235
  * Parse shared CLI flags used by publish credential helpers.
216
236
  * @param {string[]} argv
217
237
  * @param {number} [startIndex=3]
218
238
  */
219
239
  export function parsePublishCredentialArgv(argv, startIndex = 3) {
220
- /** @type {Record<string, string>} */
240
+ /** @type {Record<string, string | boolean>} */
221
241
  const options = {
222
242
  projectRoot: '.',
223
243
  };
@@ -243,6 +263,32 @@ export function parsePublishCredentialArgv(argv, startIndex = 3) {
243
263
  options.username = argv[++i];
244
264
  } else if (arg === '--password' && argv[i + 1]) {
245
265
  options.password = argv[++i];
266
+ } else if (arg === '--debug-only') {
267
+ options.debugOnly = true;
268
+ } else if (arg === '--csr-only') {
269
+ options.csrOnly = true;
270
+ } else if (arg === '--yes' || arg === '-y') {
271
+ options.yes = true;
272
+ } else if (arg === '--from-cer' && argv[i + 1]) {
273
+ options.fromCer = normalizeUserPath(argv[++i]);
274
+ } else if (arg === '--kind' && argv[i + 1]) {
275
+ options.kind = argv[++i];
276
+ } else if (arg === '--provision' && argv[i + 1]) {
277
+ options.provision = normalizeUserPath(argv[++i]);
278
+ } else if (arg === '--workdir' && argv[i + 1]) {
279
+ options.workdir = normalizeUserPath(argv[++i]);
280
+ } else if (arg === '--alias' && argv[i + 1]) {
281
+ options.alias = argv[++i];
282
+ } else if (arg === '--store-password' && argv[i + 1]) {
283
+ options.storePassword = argv[++i];
284
+ } else if (arg === '--key-password' && argv[i + 1]) {
285
+ options.keyPassword = argv[++i];
286
+ } else if (arg === '--p12-password' && argv[i + 1]) {
287
+ options.p12Password = argv[++i];
288
+ } else if (arg === '--cn' && argv[i + 1]) {
289
+ options.cn = argv[++i];
290
+ } else if (arg === '--email' && argv[i + 1]) {
291
+ options.email = argv[++i];
246
292
  }
247
293
  }
248
294
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@makefully/adaptfully",
3
- "version": "4.0.0",
3
+ "version": "4.1.0",
4
4
  "description": "Platform abstraction and Wrapfully deploy client for Makefully games",
5
5
  "type": "module",
6
6
  "main": "./lib/node/index.js",
@@ -17,6 +17,7 @@
17
17
  "files": [
18
18
  "bin",
19
19
  "lib",
20
+ "docs",
20
21
  "CHANGELOG.md",
21
22
  "LICENSE",
22
23
  "README.md"