@makefully/adaptfully 3.5.0 → 3.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/README.md +58 -25
- package/lib/node/archive.js +105 -7
- package/lib/node/deploy.js +59 -11
- package/lib/node/index.js +7 -2
- package/lib/node/pipeline.js +137 -30
- package/lib/node/registrations.js +110 -8
- package/lib/node/steam-auth.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented in this file.
|
|
4
4
|
|
|
5
|
+
## 3.7.0 — 2026-07-09
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **`adaptfully release <platform>`** stage — single Wrapfully call for build + configured deployments.
|
|
10
|
+
- **`resolveBuildSpec()`** — returns `{ family, targets, platformKey, steamworks, deployments }` for multi-target platforms.
|
|
11
|
+
- **`createSourceArchive`**, **`createDeployArchive`**, **`createReleaseArchive`** — split zip profiles for build/deploy/release.
|
|
12
|
+
- **`--artifact <path>`** on deploy for promoting a prior build artifact.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- **`send()`** targets Wrapfully v2 URLs: `/{family}/build`, `/{family}/release`, `/deploy/{key}`.
|
|
17
|
+
- Default **`deployments`** when omitted is `["zip"]` instead of the platform key.
|
|
18
|
+
- **`builder`** in platform config may be a string or array (e.g. `["win","mac","linux"]`).
|
|
19
|
+
|
|
20
|
+
## 3.6.1 — 2026-07-06
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- Web deploy uses the same Wrapfully zip-and-post path as Steam (`webapp` builder). Adaptfully does not implement SFTP or other publish targets directly — credentials in `assets/meta/publish/` travel in the zip for Wrapfully to use.
|
|
25
|
+
|
|
5
26
|
## 3.5.0 — 2026-06-27
|
|
6
27
|
|
|
7
28
|
### Added
|
package/README.md
CHANGED
|
@@ -21,21 +21,44 @@ Games register platform services before load and retrieve them in-game. Adaptful
|
|
|
21
21
|
### Pipeline stages
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
|
-
adaptfully prebuild web
|
|
25
|
-
adaptfully build steam
|
|
26
|
-
adaptfully deploy
|
|
27
|
-
adaptfully
|
|
24
|
+
adaptfully prebuild web # deploy/ → output/web-prebuild/
|
|
25
|
+
adaptfully build steam # prebuild + zip and send to Wrapfully (default deployment)
|
|
26
|
+
adaptfully deploy web # prebuild + POST to Wrapfully for each of web's deployments
|
|
27
|
+
adaptfully deploy web --deployment web-prod # deploy to a single named deployment
|
|
28
|
+
adaptfully deploy steam # prebuild + POST to Wrapfully steam builder
|
|
29
|
+
adaptfully steam-auth # one-time: log in with steamcmd → assets/meta/deployments/steam/steam.json
|
|
28
30
|
```
|
|
29
31
|
|
|
30
32
|
| Stage | What it does |
|
|
31
33
|
|-------|----------------|
|
|
32
34
|
| `prebuild` | Copy `deploy/` to `output/<platform>-prebuild/` and inject registrations into `config.htmlInjections` |
|
|
33
|
-
| `build` | Prebuild, then POST the result to Wrapfully |
|
|
34
|
-
| `deploy` |
|
|
35
|
-
| `steam-auth` | One-time local setup: install steamcmd, interactive login, write `
|
|
35
|
+
| `build` | Prebuild, then POST the result to Wrapfully (the platform's default/first deployment only) |
|
|
36
|
+
| `deploy` | Prebuild, zip, POST to Wrapfully once per deployment; platform release when credentials are in the deployment folder |
|
|
37
|
+
| `steam-auth` | One-time local setup: install steamcmd, interactive login, write the `steam` deployment's `steam.json` |
|
|
36
38
|
|
|
37
39
|
`wrapfully-deploy` is a compatibility alias for `adaptfully deploy` when invoked with a Wrapfully builder name (`steam`, `win`, `android`, etc.).
|
|
38
40
|
|
|
41
|
+
### Platforms vs. deployments
|
|
42
|
+
|
|
43
|
+
A **platform** (`config.platforms.<key>`) describes *how* to build — registrations, packager, and the Wrapfully builder. A **deployment** describes *where* a build is sent, along with the credentials for that target. This lets a single build fan out to several targets of the same platform type (for example, a staging and a production SFTP host).
|
|
44
|
+
|
|
45
|
+
- A platform may declare `"deployments": ["<key>", ...]`. Each key maps to a credential folder at `assets/meta/deployments/<key>/`.
|
|
46
|
+
- If a platform has no `deployments` array, the platform key itself is the single default deployment key (so `steam` → `assets/meta/deployments/steam/`).
|
|
47
|
+
- `adaptfully deploy <platform>` prebuilds once, then sends to Wrapfully once per deployment. `--deployment <key>` narrows it to one.
|
|
48
|
+
- Only the selected deployment's folder is shipped in each zip (as `meta/publish/`), so other targets' credentials never travel with a build.
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
"config": {
|
|
52
|
+
"platforms": {
|
|
53
|
+
"web": { "registrations": { "auth": "google-auth" }, "deployments": ["web-testing", "web-prod"] },
|
|
54
|
+
"steam": { "packager": "electron", "registrations": { "auth": "steam-auth" } },
|
|
55
|
+
"steam-dev": { "packager": "electron", "registrations": { "auth": "steam-auth" }, "deployments": ["steam"] }
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
> **Back-compat:** projects with no `assets/meta/deployments/` directory keep the legacy behavior — the entire `assets/meta/` tree (including `assets/meta/publish/`) is shipped as `meta/`, and a single set of credentials is read from `meta/publish/`.
|
|
61
|
+
|
|
39
62
|
Place `<!-- adaptfully -->` / `<!-- /adaptfully -->` markers in your HTML templates where registrations should be injected (typically between split bundle scripts, before `account.js` runs).
|
|
40
63
|
|
|
41
64
|
```javascript
|
|
@@ -296,16 +319,20 @@ mygame/
|
|
|
296
319
|
│ ├── web-prebuild/ # after adaptfully prebuild web
|
|
297
320
|
│ └── steam-prebuild/ # after adaptfully prebuild steam
|
|
298
321
|
└── assets/
|
|
299
|
-
└── meta/
|
|
322
|
+
└── meta/ # shared files packaged as meta/ in the zip
|
|
300
323
|
├── icon-foreground.png
|
|
301
324
|
├── icon-background.png
|
|
302
|
-
└──
|
|
303
|
-
├──
|
|
304
|
-
|
|
305
|
-
├──
|
|
306
|
-
└──
|
|
325
|
+
└── deployments/ # one folder per deployment target (creds)
|
|
326
|
+
├── web-testing/
|
|
327
|
+
│ └── sftp.json
|
|
328
|
+
├── web-prod/
|
|
329
|
+
│ └── sftp.json
|
|
330
|
+
└── steam/ # default deployment for the `steam` platform
|
|
331
|
+
└── steam.json
|
|
307
332
|
```
|
|
308
333
|
|
|
334
|
+
The selected deployment's folder is shipped as `meta/publish/` in the zip, so per-platform credential file names (`build.json`, `sftp.json`, `steam.json`, `apple.json`, `google.json`, `ms.json`, `android/`, `ms/`) live inside `assets/meta/deployments/<key>/`.
|
|
335
|
+
|
|
309
336
|
Icons (`icon-foreground.png`, `icon-background.png`) are required for mobile, desktop, and Steam builds.
|
|
310
337
|
|
|
311
338
|
### Icons
|
|
@@ -435,11 +462,11 @@ For a single platform, pass the specific builder name rather than a composite.
|
|
|
435
462
|
|
|
436
463
|
### Platform package requirements
|
|
437
464
|
|
|
438
|
-
Signing keys, provisioning profiles, and store credentials go in `./assets/meta/
|
|
465
|
+
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/`.)
|
|
439
466
|
|
|
440
467
|
#### Android (`android`, `android-dev`)
|
|
441
468
|
|
|
442
|
-
Place keystore files in `assets/meta/
|
|
469
|
+
Place keystore files in `assets/meta/deployments/<deployment>/android/`. Include `assets/meta/deployments/<deployment>/build.json`:
|
|
443
470
|
|
|
444
471
|
```json
|
|
445
472
|
{
|
|
@@ -464,7 +491,7 @@ Place keystore files in `assets/meta/publish/android/`. Include `assets/meta/pub
|
|
|
464
491
|
}
|
|
465
492
|
```
|
|
466
493
|
|
|
467
|
-
To deploy to Google Play, also include `assets/meta/
|
|
494
|
+
To deploy to Google Play, also include `assets/meta/deployments/<deployment>/google.json`:
|
|
468
495
|
|
|
469
496
|
```json
|
|
470
497
|
{
|
|
@@ -483,7 +510,7 @@ To deploy to Google Play, also include `assets/meta/publish/google.json`:
|
|
|
483
510
|
|
|
484
511
|
#### Apple (`ios`, `ios-dev`, `ios-sim`, `mac`, `apple`, `apple-dev`)
|
|
485
512
|
|
|
486
|
-
Include `assets/meta/
|
|
513
|
+
Include `assets/meta/deployments/<deployment>/build.json` with iOS signing settings:
|
|
487
514
|
|
|
488
515
|
```json
|
|
489
516
|
{
|
|
@@ -506,7 +533,7 @@ Include `assets/meta/publish/build.json` with iOS signing settings:
|
|
|
506
533
|
}
|
|
507
534
|
```
|
|
508
535
|
|
|
509
|
-
To deploy to the App Store, include `assets/meta/
|
|
536
|
+
To deploy to the App Store, include `assets/meta/deployments/<deployment>/apple.json`:
|
|
510
537
|
|
|
511
538
|
```json
|
|
512
539
|
{
|
|
@@ -525,7 +552,7 @@ Requires the Android and Apple package requirements above.
|
|
|
525
552
|
|
|
526
553
|
`steam-dev` builds debug Electron binaries for Windows, Mac, and Linux without uploading to Steam. No `steam.json` credentials are required.
|
|
527
554
|
|
|
528
|
-
For release uploads, run **`adaptfully steam-auth`** once on your machine. It installs steamcmd if needed, logs you in interactively (enter a Steam Guard code if prompted), and writes `assets/meta/
|
|
555
|
+
For release uploads, run **`adaptfully steam-auth`** 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`:
|
|
529
556
|
|
|
530
557
|
```json
|
|
531
558
|
{
|
|
@@ -551,27 +578,33 @@ When builds relay between servers, `meta/publish/` credentials travel in the zip
|
|
|
551
578
|
|
|
552
579
|
`-dev` builders produce debug Electron apps with DevTools enabled and the application menu visible. Dev builds skip code signing, notarization, and Steam upload. No publish credentials are required for dev builds.
|
|
553
580
|
|
|
554
|
-
Release `win` builds can be signed with `assets/meta/
|
|
581
|
+
Release `win` builds can be signed with `assets/meta/deployments/<deployment>/ms.json` (see Windows below). Release `mac` builds can use `assets/meta/deployments/<deployment>/apple.json` for signing and notarization (see Apple above).
|
|
555
582
|
|
|
556
|
-
#### Web
|
|
583
|
+
#### Web (`web` / `webapp`)
|
|
557
584
|
|
|
558
|
-
|
|
585
|
+
`adaptfully deploy web` prebuilds and POSTs to the Wrapfully **`webapp`** builder (same conduit as Steam). Put an `sftp.json` in each web deployment folder (e.g. `assets/meta/deployments/web-prod/sftp.json`); the selected deployment is zipped as `meta/publish/sftp.json` for Wrapfully to deploy via SFTP. The inner key stays the builder name (`webapp`).
|
|
559
586
|
|
|
560
587
|
```json
|
|
561
588
|
{
|
|
562
589
|
"webapp": {
|
|
563
590
|
"host": "(your sftp host)",
|
|
564
591
|
"port": 22,
|
|
565
|
-
"
|
|
592
|
+
"username": "(your username)",
|
|
566
593
|
"password": "(your password)",
|
|
567
|
-
"path": "
|
|
594
|
+
"path": "/home/user/example.com.incoming",
|
|
595
|
+
"uploadMode": "direct",
|
|
596
|
+
"serviceWorker": false,
|
|
597
|
+
"metaWeb": false,
|
|
598
|
+
"cleanRemote": false
|
|
568
599
|
}
|
|
569
600
|
}
|
|
570
601
|
```
|
|
571
602
|
|
|
603
|
+
See [Wrapfully README](https://github.com/Makefully-Studios/wrapfully-client) for `uploadMode`, `serviceWorker`, `metaWeb`, and `cleanRemote`.
|
|
604
|
+
|
|
572
605
|
#### Windows (`win`, `win-dev`, `uwp`)
|
|
573
606
|
|
|
574
|
-
To sign the app, place your certificate at `assets/meta/
|
|
607
|
+
To sign the app, place your certificate at `assets/meta/deployments/<deployment>/ms/packcert.pfx` and include `assets/meta/deployments/<deployment>/ms.json`:
|
|
575
608
|
|
|
576
609
|
```json
|
|
577
610
|
{
|
package/lib/node/archive.js
CHANGED
|
@@ -1,12 +1,57 @@
|
|
|
1
1
|
import archiver from 'archiver';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
const META_DIR = 'assets/meta';
|
|
6
|
+
const DEPLOYMENTS_DIRNAME = 'deployments';
|
|
7
|
+
const PUBLISH_DIRNAME = 'publish';
|
|
3
8
|
|
|
4
9
|
/**
|
|
5
|
-
* @param {
|
|
6
|
-
* @param {string}
|
|
7
|
-
* @param {
|
|
10
|
+
* @param {import('archiver').Archiver} zip
|
|
11
|
+
* @param {string} [publishDir] Single deployment folder
|
|
12
|
+
* @param {string[]} [deploymentDirs] Multiple deployment folders for release
|
|
8
13
|
*/
|
|
9
|
-
|
|
14
|
+
function appendMeta(zip, publishDir, deploymentDirs = []) {
|
|
15
|
+
if (!fs.existsSync(META_DIR)) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const deploymentsRoot = path.join(META_DIR, DEPLOYMENTS_DIRNAME);
|
|
20
|
+
|
|
21
|
+
if (!fs.existsSync(deploymentsRoot)) {
|
|
22
|
+
zip.directory(`${META_DIR}/`, 'meta');
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
for (const entry of fs.readdirSync(META_DIR, { withFileTypes: true })) {
|
|
27
|
+
if (entry.name === DEPLOYMENTS_DIRNAME || entry.name === PUBLISH_DIRNAME) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const fullPath = path.join(META_DIR, entry.name);
|
|
32
|
+
if (entry.isDirectory()) {
|
|
33
|
+
zip.directory(`${fullPath}/`, `meta/${entry.name}`);
|
|
34
|
+
} else {
|
|
35
|
+
zip.file(fullPath, { name: `meta/${entry.name}` });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (publishDir && fs.existsSync(publishDir)) {
|
|
40
|
+
zip.directory(`${publishDir}/`, `meta/${PUBLISH_DIRNAME}`);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
for (const deploymentDir of deploymentDirs) {
|
|
45
|
+
if (!fs.existsSync(deploymentDir)) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const deploymentKey = path.basename(deploymentDir);
|
|
50
|
+
zip.directory(`${deploymentDir}/`, `meta/${DEPLOYMENTS_DIRNAME}/${deploymentKey}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function createZipStream(deployFolder, contents, options = {}) {
|
|
10
55
|
const zip = archiver('zip', { zlib: { level: 0 } });
|
|
11
56
|
|
|
12
57
|
zip.on('warning', (err) => {
|
|
@@ -33,11 +78,64 @@ export function createArchive(deployFolder, contents, options = {}) {
|
|
|
33
78
|
zip.directory(`${deployFolder}/`, 'deploy');
|
|
34
79
|
zip.file(`${deployFolder}/index.html`, { name: 'deploy/index.html' });
|
|
35
80
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
81
|
+
|
|
82
|
+
appendMeta(zip, options.publishDir, options.deploymentDirs);
|
|
39
83
|
zip.append(contents, { name: 'package.json' });
|
|
40
84
|
zip.finalize();
|
|
41
85
|
|
|
42
86
|
return zip;
|
|
43
87
|
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Game source zip for /build and /release (no credentials unless release).
|
|
91
|
+
*
|
|
92
|
+
* @param {string} deployFolder
|
|
93
|
+
* @param {string} contents
|
|
94
|
+
* @param {{ indexHtml?: string, publishDir?: string, deploymentDirs?: string[] }} [options]
|
|
95
|
+
*/
|
|
96
|
+
export function createSourceArchive(deployFolder, contents, options = {}) {
|
|
97
|
+
return createZipStream(deployFolder, contents, options);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @deprecated Use createSourceArchive, createDeployArchive, or createReleaseArchive.
|
|
102
|
+
*/
|
|
103
|
+
export function createArchive(deployFolder, contents, options = {}) {
|
|
104
|
+
return createSourceArchive(deployFolder, contents, options);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Prior artifact zip plus one deployment folder for /deploy.
|
|
109
|
+
*
|
|
110
|
+
* @param {string} artifactPath
|
|
111
|
+
* @param {string} publishDir
|
|
112
|
+
* @param {string} [contents]
|
|
113
|
+
*/
|
|
114
|
+
export function createDeployArchive(artifactPath, publishDir, contents) {
|
|
115
|
+
const zip = archiver('zip', { zlib: { level: 0 } });
|
|
116
|
+
|
|
117
|
+
zip.directory(`${artifactPath}/`, false);
|
|
118
|
+
appendMeta(zip, publishDir);
|
|
119
|
+
|
|
120
|
+
if (contents) {
|
|
121
|
+
zip.append(contents, { name: 'package.json' });
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
zip.finalize();
|
|
125
|
+
return zip;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Game source zip with all non-zip deployment credential folders for /release.
|
|
130
|
+
*
|
|
131
|
+
* @param {string} deployFolder
|
|
132
|
+
* @param {string} contents
|
|
133
|
+
* @param {string[]} deploymentDirs
|
|
134
|
+
* @param {{ indexHtml?: string }} [options]
|
|
135
|
+
*/
|
|
136
|
+
export function createReleaseArchive(deployFolder, contents, deploymentDirs, options = {}) {
|
|
137
|
+
return createZipStream(deployFolder, contents, {
|
|
138
|
+
...options,
|
|
139
|
+
deploymentDirs,
|
|
140
|
+
});
|
|
141
|
+
}
|
package/lib/node/deploy.js
CHANGED
|
@@ -3,31 +3,79 @@ import fs from 'node:fs';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { pipeline } from 'node:stream/promises';
|
|
5
5
|
import unzipper from 'unzip-stream';
|
|
6
|
-
import {
|
|
6
|
+
import { createDeployArchive, createReleaseArchive, createSourceArchive } from './archive.js';
|
|
7
7
|
import { listHtmlFilesRecursive } from './fs-utils.js';
|
|
8
8
|
import { printBuildReport } from './report.js';
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* @param {string} gameId
|
|
12
|
+
* @param {string} platformKey
|
|
13
|
+
*/
|
|
14
|
+
function buildInfoParam(gameId, platformKey) {
|
|
15
|
+
return platformKey && platformKey !== gameId ? `${gameId}_${platformKey}` : gameId;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {string} server
|
|
20
|
+
* @param {'build' | 'deploy' | 'release'} stage
|
|
21
|
+
* @param {string} family
|
|
22
|
+
* @param {string} gameId
|
|
23
|
+
* @param {string} platformKey
|
|
24
|
+
* @param {string} [deploymentKey]
|
|
25
|
+
*/
|
|
26
|
+
function resolveWrapfullyUrl(server, stage, family, gameId, platformKey, deploymentKey) {
|
|
27
|
+
const info = buildInfoParam(gameId, platformKey);
|
|
28
|
+
|
|
29
|
+
if (stage === 'deploy') {
|
|
30
|
+
return `${server}deploy/${deploymentKey}/${info}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return `${server}${family}/${stage}/${info}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
10
36
|
/**
|
|
11
37
|
* @param {string} gameId
|
|
12
38
|
* @param {string} contents
|
|
13
39
|
* @param {string} server
|
|
14
|
-
* @param {
|
|
15
|
-
* @param {string}
|
|
40
|
+
* @param {'build' | 'deploy' | 'release'} stage
|
|
41
|
+
* @param {string} family
|
|
42
|
+
* @param {string} deployFolder
|
|
16
43
|
* @param {{ name: string, version: string }} pkg
|
|
17
44
|
* @param {'extract' | string} mode
|
|
18
|
-
* @param {{ log?: (message: string) => void }} [options]
|
|
45
|
+
* @param {{ log?: (message: string) => void, publishDir?: string, deploymentDirs?: string[], platformKey?: string, deploymentKey?: string, artifactPath?: string }} [options]
|
|
19
46
|
*/
|
|
20
|
-
export async function send(gameId, contents, server,
|
|
47
|
+
export async function send(gameId, contents, server, stage, family, deployFolder, pkg, mode = 'extract', options = {}) {
|
|
21
48
|
const log = options.log ?? console.log;
|
|
49
|
+
const platformKey = options.platformKey ?? family;
|
|
22
50
|
const destination = mode === 'extract'
|
|
23
51
|
? unzipper.Extract({ path: './output/', concurrency: 1 })
|
|
24
|
-
: fs.createWriteStream(`./output/${pkg.name}-${pkg.version}-${
|
|
52
|
+
: fs.createWriteStream(`./output/${pkg.name}-${pkg.version}-${stage}-${family}.zip`);
|
|
53
|
+
|
|
54
|
+
let archiveStream;
|
|
55
|
+
|
|
56
|
+
if (stage === 'deploy') {
|
|
57
|
+
if (!options.artifactPath) {
|
|
58
|
+
throw new Error('deploy stage requires options.artifactPath to the prior build artifact directory');
|
|
59
|
+
}
|
|
60
|
+
if (!options.deploymentKey) {
|
|
61
|
+
throw new Error('deploy stage requires options.deploymentKey');
|
|
62
|
+
}
|
|
63
|
+
archiveStream = createDeployArchive(options.artifactPath, options.publishDir, contents);
|
|
64
|
+
} else if (stage === 'release') {
|
|
65
|
+
archiveStream = createReleaseArchive(deployFolder, contents, options.deploymentDirs ?? []);
|
|
66
|
+
} else {
|
|
67
|
+
archiveStream = createSourceArchive(deployFolder, contents);
|
|
68
|
+
}
|
|
25
69
|
|
|
26
|
-
const
|
|
27
|
-
log(`adaptfully:
|
|
70
|
+
const url = resolveWrapfullyUrl(server, stage, family, gameId, platformKey, options.deploymentKey);
|
|
71
|
+
log(`adaptfully: POST ${url}`);
|
|
72
|
+
|
|
73
|
+
if (stage !== 'deploy') {
|
|
74
|
+
const htmlFiles = listHtmlFilesRecursive(deployFolder);
|
|
75
|
+
log(`adaptfully: sending ${htmlFiles.length} HTML file(s) from ${path.resolve(deployFolder)}`);
|
|
76
|
+
}
|
|
28
77
|
|
|
29
|
-
const
|
|
30
|
-
const { data } = await axios.post(`${server}${builder}/${gameId}`, archiveStream, {
|
|
78
|
+
const { data } = await axios.post(url, archiveStream, {
|
|
31
79
|
maxRedirects: 0,
|
|
32
80
|
responseType: 'stream',
|
|
33
81
|
});
|
|
@@ -47,6 +95,6 @@ export async function send(gameId, contents, server, builder, deployFolder, pkg,
|
|
|
47
95
|
}
|
|
48
96
|
|
|
49
97
|
if (mode === 'extract') {
|
|
50
|
-
printBuildReport(
|
|
98
|
+
printBuildReport(`${stage}-${family}`, pkg);
|
|
51
99
|
}
|
|
52
100
|
}
|
package/lib/node/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export { createArchive } from './archive.js';
|
|
1
|
+
export { createArchive, createDeployArchive, createReleaseArchive, createSourceArchive } from './archive.js';
|
|
2
2
|
export { loadProjectConfig, resolveServerUrl } from './config.js';
|
|
3
3
|
export { send } from './deploy.js';
|
|
4
|
-
export { adaptfullyFromCli, runAdaptfullyStage } from './pipeline.js';
|
|
4
|
+
export { adaptfullyFromCli, parseStageArgs, runAdaptfullyStage } from './pipeline.js';
|
|
5
5
|
export { prebuildPlatform, prebuildOutputDir, resolveHtmlInjections } from './prebuild.js';
|
|
6
6
|
export { getPackageRoot, getRuntimeDir, resolveRuntimeScript } from './paths.js';
|
|
7
7
|
export {
|
|
@@ -28,9 +28,14 @@ export {
|
|
|
28
28
|
collectRegistrationParts,
|
|
29
29
|
injectAdaptfullyRegistrations,
|
|
30
30
|
resolveBuilderForPlatform,
|
|
31
|
+
resolveBuildSpec,
|
|
31
32
|
resolveCliPlatformAndBuilder,
|
|
33
|
+
resolveDeploymentsForPlatform,
|
|
34
|
+
resolveFamily,
|
|
35
|
+
resolveFamilyFromTarget,
|
|
32
36
|
resolvePlatformKey,
|
|
33
37
|
resolvePlatformRegistrationsByKey,
|
|
38
|
+
resolvePublishDir,
|
|
34
39
|
resolveRegistrationAssets,
|
|
35
40
|
} from './registrations.js';
|
|
36
41
|
export { printBuildReport } from './report.js';
|
package/lib/node/pipeline.js
CHANGED
|
@@ -1,50 +1,121 @@
|
|
|
1
1
|
import { loadProjectConfig, resolveServerUrl } from './config.js';
|
|
2
2
|
import { send } from './deploy.js';
|
|
3
3
|
import { prebuildPlatform } from './prebuild.js';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
resolveBuildSpec,
|
|
6
|
+
resolveDeploymentsForPlatform,
|
|
7
|
+
resolvePublishDir,
|
|
8
|
+
} from './registrations.js';
|
|
5
9
|
import { steamAuthFromCli } from './steam-auth.js';
|
|
6
10
|
|
|
7
|
-
/** @typedef {'prebuild' | 'build' | 'deploy'} AdaptfullyStage */
|
|
11
|
+
/** @typedef {'prebuild' | 'build' | 'deploy' | 'release'} AdaptfullyStage */
|
|
8
12
|
|
|
9
|
-
const VALID_STAGES = new Set(['prebuild', 'build', 'deploy']);
|
|
13
|
+
const VALID_STAGES = new Set(['prebuild', 'build', 'deploy', 'release']);
|
|
10
14
|
|
|
11
15
|
/**
|
|
12
16
|
* @param {AdaptfullyStage} stage
|
|
13
17
|
* @param {string} platformKey
|
|
14
|
-
* @param {{ pkg: object, deployFolder: string, server?: string, mode?: string,
|
|
18
|
+
* @param {{ pkg: object, deployFolder: string, server?: string, mode?: string, deployment?: string, artifactPath?: string, log?: (message: string) => void, outputRoot?: string }} options
|
|
15
19
|
*/
|
|
16
20
|
export async function runAdaptfullyStage(stage, platformKey, options) {
|
|
17
21
|
if (!VALID_STAGES.has(stage)) {
|
|
18
|
-
throw new Error(`Unknown adaptfully stage "${stage}". Expected: prebuild, build, or
|
|
22
|
+
throw new Error(`Unknown adaptfully stage "${stage}". Expected: prebuild, build, deploy, or release.`);
|
|
19
23
|
}
|
|
20
24
|
|
|
21
25
|
const log = options.log ?? console.log;
|
|
22
26
|
const { pkg, deployFolder } = options;
|
|
27
|
+
const buildSpec = resolveBuildSpec(platformKey, pkg);
|
|
28
|
+
let deployments = resolveDeploymentsForPlatform(platformKey, pkg);
|
|
23
29
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
30
|
+
if (options.deployment) {
|
|
31
|
+
if (!deployments.includes(options.deployment)) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`Deployment "${options.deployment}" is not configured for platform "${platformKey}". `
|
|
34
|
+
+ `Available deployments: ${deployments.join(', ')}`,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
deployments = [options.deployment];
|
|
38
|
+
} else if (stage === 'build') {
|
|
39
|
+
deployments = ['zip'];
|
|
40
|
+
} else if (stage === 'deploy') {
|
|
41
|
+
deployments = deployments.filter((key) => key !== 'zip');
|
|
42
|
+
} else if (stage === 'release') {
|
|
43
|
+
deployments = deployments.filter((key) => key !== 'zip');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const prebuiltDir = stage === 'deploy' && options.artifactPath
|
|
47
|
+
? options.artifactPath
|
|
48
|
+
: prebuildPlatform(deployFolder, platformKey, pkg, {
|
|
49
|
+
log,
|
|
50
|
+
outputRoot: options.outputRoot,
|
|
51
|
+
});
|
|
28
52
|
|
|
29
53
|
if (stage === 'prebuild') {
|
|
30
|
-
return { prebuiltDir, platformKey };
|
|
54
|
+
return { prebuiltDir, platformKey, buildSpec };
|
|
31
55
|
}
|
|
32
56
|
|
|
33
|
-
const
|
|
34
|
-
|
|
57
|
+
const gameId = `${pkg.name}-${pkg.version}`;
|
|
58
|
+
const contents = JSON.stringify(pkg);
|
|
59
|
+
/** @type {{ deploymentKey: string, stage: string, family: string }[]} */
|
|
60
|
+
const sent = [];
|
|
35
61
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
62
|
+
if (stage === 'release') {
|
|
63
|
+
const deploymentDirs = deployments.map((key) => resolvePublishDir(key));
|
|
64
|
+
|
|
65
|
+
log(`adaptfully: ${stage} ${platformKey} → ${buildSpec.family} (${buildSpec.targets.join(', ')})`);
|
|
66
|
+
|
|
67
|
+
await send(
|
|
68
|
+
gameId,
|
|
69
|
+
contents,
|
|
70
|
+
options.server,
|
|
71
|
+
'release',
|
|
72
|
+
buildSpec.family,
|
|
73
|
+
prebuiltDir,
|
|
74
|
+
pkg,
|
|
75
|
+
options.mode ?? 'extract',
|
|
76
|
+
{
|
|
77
|
+
log,
|
|
78
|
+
platformKey,
|
|
79
|
+
deploymentDirs,
|
|
80
|
+
},
|
|
81
|
+
);
|
|
46
82
|
|
|
47
|
-
|
|
83
|
+
sent.push({ deploymentKey: 'release', stage, family: buildSpec.family });
|
|
84
|
+
return { prebuiltDir, platformKey, buildSpec, deployments: sent };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
for (const deploymentKey of deployments) {
|
|
88
|
+
const publishDir = deploymentKey === 'zip' ? undefined : resolvePublishDir(deploymentKey);
|
|
89
|
+
const wrapStage = stage === 'deploy' ? 'deploy' : 'build';
|
|
90
|
+
|
|
91
|
+
log(
|
|
92
|
+
`adaptfully: ${stage} ${platformKey} → `
|
|
93
|
+
+ `${deploymentKey === 'zip' ? 'artifact zip' : `deployment "${deploymentKey}"`} `
|
|
94
|
+
+ `via Wrapfully ${buildSpec.family}`,
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
await send(
|
|
98
|
+
gameId,
|
|
99
|
+
contents,
|
|
100
|
+
options.server,
|
|
101
|
+
wrapStage,
|
|
102
|
+
buildSpec.family,
|
|
103
|
+
prebuiltDir,
|
|
104
|
+
pkg,
|
|
105
|
+
options.mode ?? 'extract',
|
|
106
|
+
{
|
|
107
|
+
log,
|
|
108
|
+
publishDir,
|
|
109
|
+
platformKey,
|
|
110
|
+
deploymentKey: wrapStage === 'deploy' ? deploymentKey : undefined,
|
|
111
|
+
artifactPath: options.artifactPath,
|
|
112
|
+
},
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
sent.push({ deploymentKey, stage: wrapStage, family: buildSpec.family });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return { prebuiltDir, platformKey, buildSpec, deployments: sent };
|
|
48
119
|
}
|
|
49
120
|
|
|
50
121
|
/**
|
|
@@ -58,22 +129,27 @@ export async function adaptfullyFromCli(argv = process.argv) {
|
|
|
58
129
|
}
|
|
59
130
|
|
|
60
131
|
const stage = command;
|
|
61
|
-
const
|
|
62
|
-
const
|
|
63
|
-
const
|
|
132
|
+
const { positionals, deployment, artifactPath } = parseStageArgs(argv.slice(3));
|
|
133
|
+
const platformKey = positionals[0];
|
|
134
|
+
const cliServer = positionals[1];
|
|
135
|
+
const mode = positionals[2] ?? 'extract';
|
|
64
136
|
|
|
65
137
|
if (!stage || !platformKey) {
|
|
66
138
|
throw new Error(
|
|
67
139
|
'Usage:\n'
|
|
68
|
-
+ ' adaptfully <prebuild|build|deploy> <platform> [server] [mode]\n'
|
|
140
|
+
+ ' adaptfully <prebuild|build|deploy|release> <platform> [server] [mode] [--deployment <key>] [--artifact <path>]\n'
|
|
69
141
|
+ ' adaptfully steam-auth [--username U] [--password P] [--output path]\n'
|
|
70
142
|
+ '\n'
|
|
71
143
|
+ 'Stages:\n'
|
|
72
144
|
+ ' prebuild Copy deploy/ and apply platform registrations → output/<platform>-prebuild/\n'
|
|
73
|
-
+ ' build prebuild +
|
|
74
|
-
+ ' deploy
|
|
145
|
+
+ ' build prebuild + POST /{family}/build (zip artifact only)\n'
|
|
146
|
+
+ ' deploy POST /deploy/{key} with prior artifact + deployment credentials\n'
|
|
147
|
+
+ ' release prebuild + POST /{family}/release (build + configured deployments)\n'
|
|
75
148
|
+ '\n'
|
|
76
|
-
+ '
|
|
149
|
+
+ ' --deployment <key> Target a single named deployment.\n'
|
|
150
|
+
+ ' --artifact <path> Prior build artifact directory (deploy stage).\n'
|
|
151
|
+
+ '\n'
|
|
152
|
+
+ ' steam-auth Install steamcmd if needed, log in interactively, write steam deployment steam.json',
|
|
77
153
|
);
|
|
78
154
|
}
|
|
79
155
|
|
|
@@ -86,5 +162,36 @@ export async function adaptfullyFromCli(argv = process.argv) {
|
|
|
86
162
|
deployFolder,
|
|
87
163
|
server,
|
|
88
164
|
mode,
|
|
165
|
+
deployment,
|
|
166
|
+
artifactPath,
|
|
89
167
|
});
|
|
90
168
|
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* @param {string[]} args
|
|
172
|
+
* @returns {{ positionals: string[], deployment?: string, artifactPath?: string }}
|
|
173
|
+
*/
|
|
174
|
+
export function parseStageArgs(args) {
|
|
175
|
+
/** @type {string[]} */
|
|
176
|
+
const positionals = [];
|
|
177
|
+
let deployment;
|
|
178
|
+
let artifactPath;
|
|
179
|
+
|
|
180
|
+
for (let i = 0; i < args.length; i++) {
|
|
181
|
+
const arg = args[i];
|
|
182
|
+
|
|
183
|
+
if (arg === '--deployment' && args[i + 1]) {
|
|
184
|
+
deployment = args[++i];
|
|
185
|
+
} else if (arg.startsWith('--deployment=')) {
|
|
186
|
+
deployment = arg.slice('--deployment='.length);
|
|
187
|
+
} else if (arg === '--artifact' && args[i + 1]) {
|
|
188
|
+
artifactPath = args[++i];
|
|
189
|
+
} else if (arg.startsWith('--artifact=')) {
|
|
190
|
+
artifactPath = arg.slice('--artifact='.length);
|
|
191
|
+
} else {
|
|
192
|
+
positionals.push(arg);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return { positionals, deployment, artifactPath };
|
|
197
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
2
3
|
import { resolveRuntimeScript } from './paths.js';
|
|
3
4
|
|
|
4
5
|
/** @typedef {Record<string, string>} RegistrationMap */
|
|
@@ -299,20 +300,119 @@ export function adaptfullyInjectionForPlatform(platformKey, pkg, options = {}) {
|
|
|
299
300
|
}
|
|
300
301
|
|
|
301
302
|
/**
|
|
303
|
+
* Resolve the deployment target keys for a platform. A platform may declare a
|
|
304
|
+
* `deployments` array (each entry maps to assets/meta/deployments/<key>/), which
|
|
305
|
+
* lets one build fan out to multiple targets of the same platform type. When no
|
|
306
|
+
* `deployments` array is present, the default is `["zip"]` (artifact download only).
|
|
307
|
+
*
|
|
302
308
|
* @param {string} platformKey
|
|
303
|
-
* @param {{ config?: { platforms?: Record<string, {
|
|
309
|
+
* @param {{ config?: { platforms?: Record<string, { deployments?: string[] }> } }} pkg
|
|
310
|
+
* @returns {string[]}
|
|
304
311
|
*/
|
|
305
|
-
export function
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
|
|
312
|
+
export function resolveDeploymentsForPlatform(platformKey, pkg) {
|
|
313
|
+
const deployments = pkg.config?.platforms?.[platformKey]?.deployments;
|
|
314
|
+
|
|
315
|
+
if (Array.isArray(deployments) && deployments.length > 0) {
|
|
316
|
+
for (const key of deployments) {
|
|
317
|
+
if (typeof key !== 'string' || !key.length) {
|
|
318
|
+
throw new Error(
|
|
319
|
+
`config.platforms.${platformKey}.deployments entries must be non-empty strings`,
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return [...deployments];
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return ['zip'];
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Absolute-or-cwd-relative path to a deployment's credential/publish folder.
|
|
331
|
+
*
|
|
332
|
+
* @param {string} deploymentKey
|
|
333
|
+
* @param {string} [metaDir='assets/meta']
|
|
334
|
+
* @returns {string}
|
|
335
|
+
*/
|
|
336
|
+
export function resolvePublishDir(deploymentKey, metaDir = 'assets/meta') {
|
|
337
|
+
return path.join(metaDir, 'deployments', deploymentKey);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const ELECTRON_TARGETS = new Set([
|
|
341
|
+
'win', 'win-dev', 'mac', 'mac-dev', 'linux', 'linux-dev',
|
|
342
|
+
]);
|
|
343
|
+
const CORDOVA_TARGETS = new Set([
|
|
344
|
+
'android', 'android-dev', 'ios', 'ios-dev', 'ios-sim',
|
|
345
|
+
]);
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* @param {string} target
|
|
349
|
+
* @returns {string | null}
|
|
350
|
+
*/
|
|
351
|
+
export function resolveFamilyFromTarget(target) {
|
|
352
|
+
if (ELECTRON_TARGETS.has(target)) {
|
|
353
|
+
return 'electron';
|
|
354
|
+
}
|
|
355
|
+
if (CORDOVA_TARGETS.has(target)) {
|
|
356
|
+
return 'cordova';
|
|
357
|
+
}
|
|
358
|
+
if (target === 'webapp' || target === 'web') {
|
|
359
|
+
return 'webapp';
|
|
360
|
+
}
|
|
361
|
+
if (target === 'uwp') {
|
|
362
|
+
return 'uwp';
|
|
363
|
+
}
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* @param {string[]} targets
|
|
369
|
+
* @param {string} [packager]
|
|
370
|
+
*/
|
|
371
|
+
export function resolveFamily(targets, packager) {
|
|
372
|
+
if (packager === 'electron') {
|
|
373
|
+
return 'electron';
|
|
374
|
+
}
|
|
375
|
+
if (packager === 'cordova' || packager === 'capacitor') {
|
|
376
|
+
return 'cordova';
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const families = [...new Set(targets.map((target) => resolveFamilyFromTarget(target)).filter(Boolean))];
|
|
380
|
+
|
|
381
|
+
if (families.length === 1) {
|
|
382
|
+
return families[0];
|
|
309
383
|
}
|
|
310
384
|
|
|
311
|
-
if (
|
|
385
|
+
if (targets.length === 1 && targets[0] === 'webapp') {
|
|
312
386
|
return 'webapp';
|
|
313
387
|
}
|
|
314
388
|
|
|
315
|
-
|
|
389
|
+
throw new Error(`Cannot resolve builder family for targets [${targets.join(', ')}]`);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* @param {string} platformKey
|
|
394
|
+
* @param {{ config?: { platforms?: Record<string, { builder?: string | string[], packager?: string, steamworks?: boolean, deployments?: string[] }> } }} pkg
|
|
395
|
+
*/
|
|
396
|
+
export function resolveBuildSpec(platformKey, pkg) {
|
|
397
|
+
const platform = pkg.config?.platforms?.[platformKey];
|
|
398
|
+
const builder = platform?.builder ?? (platformKey === 'web' ? 'webapp' : platformKey);
|
|
399
|
+
const targets = Array.isArray(builder) ? [...builder] : [builder];
|
|
400
|
+
|
|
401
|
+
return {
|
|
402
|
+
family: resolveFamily(targets, platform?.packager),
|
|
403
|
+
targets,
|
|
404
|
+
platformKey,
|
|
405
|
+
steamworks: platform?.steamworks === true,
|
|
406
|
+
deployments: resolveDeploymentsForPlatform(platformKey, pkg),
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* @param {string} platformKey
|
|
412
|
+
* @param {{ config?: { platforms?: Record<string, { builder?: string | string[] }> } }} pkg
|
|
413
|
+
*/
|
|
414
|
+
export function resolveBuilderForPlatform(platformKey, pkg) {
|
|
415
|
+
return resolveBuildSpec(platformKey, pkg).family;
|
|
316
416
|
}
|
|
317
417
|
|
|
318
418
|
/**
|
|
@@ -323,9 +423,11 @@ export function resolveCliPlatformAndBuilder(arg, pkg) {
|
|
|
323
423
|
const platforms = pkg.config?.platforms ?? {};
|
|
324
424
|
|
|
325
425
|
if (platforms[arg]) {
|
|
426
|
+
const spec = resolveBuildSpec(arg, pkg);
|
|
326
427
|
return {
|
|
327
428
|
platformKey: arg,
|
|
328
|
-
builder:
|
|
429
|
+
builder: spec.family,
|
|
430
|
+
buildSpec: spec,
|
|
329
431
|
};
|
|
330
432
|
}
|
|
331
433
|
|
package/lib/node/steam-auth.js
CHANGED
|
@@ -26,7 +26,7 @@ const STEAMCMD_INSTALL = {
|
|
|
26
26
|
* @param {string} [projectRoot]
|
|
27
27
|
*/
|
|
28
28
|
export function defaultSteamPublishPath(projectRoot = '.') {
|
|
29
|
-
return path.resolve(projectRoot, 'assets', 'meta', '
|
|
29
|
+
return path.resolve(projectRoot, 'assets', 'meta', 'deployments', 'steam', 'steam.json');
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
/**
|