@makefully/adaptfully 3.4.0 → 3.6.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 +13 -0
- package/README.md +27 -10
- package/lib/node/index.js +9 -0
- package/lib/node/packagers/electron.js +7 -4
- package/lib/node/pipeline.js +16 -3
- package/lib/node/steam-auth.js +343 -0
- package/lib/runtime/auth/steam-auth.js +7 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented in this file.
|
|
4
4
|
|
|
5
|
+
## 3.6.1 — 2026-07-06
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- 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.
|
|
10
|
+
|
|
11
|
+
## 3.5.0 — 2026-06-27
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **`adaptfully steam-auth`** — installs steamcmd if missing, runs an interactive login, and writes `assets/meta/publish/steam.json` with `configVdf` and optional `ssfn` sentry data for Steam Guard accounts.
|
|
16
|
+
- Exported helpers: `runSteamAuth`, `buildSteamPublishJson`, `collectSteamAuthFiles`, `ensureSteamcmd`.
|
|
17
|
+
|
|
5
18
|
## 3.4.0 — 2026-06-25
|
|
6
19
|
|
|
7
20
|
### Changed
|
package/README.md
CHANGED
|
@@ -23,14 +23,17 @@ Games register platform services before load and retrieve them in-game. Adaptful
|
|
|
23
23
|
```bash
|
|
24
24
|
adaptfully prebuild web # deploy/ → output/web-prebuild/
|
|
25
25
|
adaptfully build steam # prebuild + zip and send to Wrapfully
|
|
26
|
-
adaptfully deploy
|
|
26
|
+
adaptfully deploy web # prebuild + POST to Wrapfully webapp builder (SFTP when sftp.json is in the zip)
|
|
27
|
+
adaptfully deploy steam # prebuild + POST to Wrapfully steam builder
|
|
28
|
+
adaptfully steam-auth # one-time: log in with steamcmd → assets/meta/publish/steam.json
|
|
27
29
|
```
|
|
28
30
|
|
|
29
31
|
| Stage | What it does |
|
|
30
32
|
|-------|----------------|
|
|
31
33
|
| `prebuild` | Copy `deploy/` to `output/<platform>-prebuild/` and inject registrations into `config.htmlInjections` |
|
|
32
34
|
| `build` | Prebuild, then POST the result to Wrapfully |
|
|
33
|
-
| `deploy` |
|
|
35
|
+
| `deploy` | Prebuild, zip, POST to Wrapfully; platform release when credentials are in `assets/meta/publish/` |
|
|
36
|
+
| `steam-auth` | One-time local setup: install steamcmd, interactive login, write `assets/meta/publish/steam.json` |
|
|
34
37
|
|
|
35
38
|
`wrapfully-deploy` is a compatibility alias for `adaptfully deploy` when invoked with a Wrapfully builder name (`steam`, `win`, `android`, etc.).
|
|
36
39
|
|
|
@@ -199,6 +202,7 @@ Run from your project root:
|
|
|
199
202
|
|
|
200
203
|
```bash
|
|
201
204
|
npx adaptfully <prebuild|build|deploy> <platform> [server] [mode]
|
|
205
|
+
npx adaptfully steam-auth [--username U] [--password P] [--output path]
|
|
202
206
|
```
|
|
203
207
|
|
|
204
208
|
| Stage | Description |
|
|
@@ -375,7 +379,7 @@ Standard npm fields (`name`, `version`, `description`) are used directly. Add a
|
|
|
375
379
|
| `publisherWebsite` | Cordova, web | Company URL |
|
|
376
380
|
| `publisherEmailAddress` | Cordova | Contact email |
|
|
377
381
|
| `scope` | Web/PWA | Base URL scope for the web app |
|
|
378
|
-
| `themeColor` | Cordova, UWP, web | Loading screen / theme color |
|
|
382
|
+
| `themeColor` | Cordova, Electron, UWP, web | Loading screen / theme color |
|
|
379
383
|
| `twitterId` | Web | Twitter handle for meta tags |
|
|
380
384
|
| `steamId` | Steam | Steam app ID |
|
|
381
385
|
| `deployFolder` | Client | Neutral deploy directory staged before prebuild (default: `deploy`) |
|
|
@@ -522,15 +526,22 @@ Requires the Android and Apple package requirements above.
|
|
|
522
526
|
|
|
523
527
|
`steam-dev` builds debug Electron binaries for Windows, Mac, and Linux without uploading to Steam. No `steam.json` credentials are required.
|
|
524
528
|
|
|
525
|
-
For release uploads,
|
|
529
|
+
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/publish/steam.json`:
|
|
526
530
|
|
|
527
531
|
```json
|
|
528
532
|
{
|
|
529
|
-
"username": "(your
|
|
530
|
-
"password": "(your password)"
|
|
533
|
+
"username": "(your Steam build account)",
|
|
534
|
+
"password": "(your password)",
|
|
535
|
+
"configVdf": "(base64 login token from steamcmd)",
|
|
536
|
+
"sentryFileName": "(only when Steam Guard is enabled)",
|
|
537
|
+
"sentryFile": "(base64 ssfn file — only when Steam Guard is enabled)"
|
|
531
538
|
}
|
|
532
539
|
```
|
|
533
540
|
|
|
541
|
+
Accounts **without** Steam Guard only need `username`, `password`, and `configVdf`. Accounts **with** Steam Guard also include the `ssfn` sentry file so the Wrapfully server can log in without a live 2FA prompt.
|
|
542
|
+
|
|
543
|
+
Re-run `adaptfully steam-auth` if Steam invalidates the token (new machine, password change, or expired guard).
|
|
544
|
+
|
|
534
545
|
Also set `steamId` in your `config` block.
|
|
535
546
|
|
|
536
547
|
Steam builds can run on either the Windows or Mac server. The server that receives the request builds its own platforms and requests the rest from the other server (Windows builds `win` and requests `mac`/`linux`; Mac builds `mac`/`linux` and requests `win`). Install the Steamworks SDK ContentBuilder on any server that will upload to Steam.
|
|
@@ -543,22 +554,28 @@ When builds relay between servers, `meta/publish/` credentials travel in the zip
|
|
|
543
554
|
|
|
544
555
|
Release `win` builds can be signed with `assets/meta/publish/ms.json` (see Windows below). Release `mac` builds can use `assets/meta/publish/apple.json` for signing and notarization (see Apple above).
|
|
545
556
|
|
|
546
|
-
#### Web
|
|
557
|
+
#### Web (`web` / `webapp`)
|
|
547
558
|
|
|
548
|
-
|
|
559
|
+
`adaptfully deploy web` prebuilds and POSTs to the Wrapfully **`webapp`** builder (same conduit as Steam). Include `assets/meta/publish/sftp.json` in the project so it is zipped as `meta/publish/sftp.json` for Wrapfully to deploy via SFTP.
|
|
549
560
|
|
|
550
561
|
```json
|
|
551
562
|
{
|
|
552
563
|
"webapp": {
|
|
553
564
|
"host": "(your sftp host)",
|
|
554
565
|
"port": 22,
|
|
555
|
-
"
|
|
566
|
+
"username": "(your username)",
|
|
556
567
|
"password": "(your password)",
|
|
557
|
-
"path": "
|
|
568
|
+
"path": "/home/user/example.com.incoming",
|
|
569
|
+
"uploadMode": "direct",
|
|
570
|
+
"serviceWorker": false,
|
|
571
|
+
"metaWeb": false,
|
|
572
|
+
"cleanRemote": false
|
|
558
573
|
}
|
|
559
574
|
}
|
|
560
575
|
```
|
|
561
576
|
|
|
577
|
+
See [Wrapfully README](https://github.com/Makefully-Studios/wrapfully-client) for `uploadMode`, `serviceWorker`, `metaWeb`, and `cleanRemote`.
|
|
578
|
+
|
|
562
579
|
#### Windows (`win`, `win-dev`, `uwp`)
|
|
563
580
|
|
|
564
581
|
To sign the app, place your certificate at `assets/meta/publish/ms/packcert.pfx` and include `assets/meta/publish/ms.json`:
|
package/lib/node/index.js
CHANGED
|
@@ -34,3 +34,12 @@ export {
|
|
|
34
34
|
resolveRegistrationAssets,
|
|
35
35
|
} from './registrations.js';
|
|
36
36
|
export { printBuildReport } from './report.js';
|
|
37
|
+
export {
|
|
38
|
+
buildSteamPublishJson,
|
|
39
|
+
collectSteamAuthFiles,
|
|
40
|
+
defaultSteamPublishPath,
|
|
41
|
+
defaultSteamcmdCacheDir,
|
|
42
|
+
ensureSteamcmd,
|
|
43
|
+
runSteamAuth,
|
|
44
|
+
steamAuthFromCli,
|
|
45
|
+
} from './steam-auth.js';
|
|
@@ -69,7 +69,7 @@ function createWindow () {
|
|
|
69
69
|
};
|
|
70
70
|
})(),
|
|
71
71
|
win = new BrowserWindow({
|
|
72
|
-
backgroundColor:
|
|
72
|
+
backgroundColor: __THEME_COLOR__,
|
|
73
73
|
width: lastState.bounds.width,
|
|
74
74
|
height: lastState.bounds.height,
|
|
75
75
|
minWidth: 200,
|
|
@@ -159,12 +159,15 @@ if (steamClient) {
|
|
|
159
159
|
|
|
160
160
|
/**
|
|
161
161
|
* @param {boolean} withSteam
|
|
162
|
+
* @param {string} [themeColor]
|
|
162
163
|
*/
|
|
163
|
-
export function buildElectronMain(withSteam) {
|
|
164
|
+
export function buildElectronMain(withSteam, themeColor) {
|
|
164
165
|
const steamInit = withSteam ? STEAM_INIT : '';
|
|
165
166
|
const steamPreload = withSteam ? STEAM_PRELOAD : '';
|
|
166
167
|
const sandbox = withSteam ? 'false' : 'true';
|
|
167
|
-
const mainBody = ELECTRON_MAIN_BODY
|
|
168
|
+
const mainBody = ELECTRON_MAIN_BODY
|
|
169
|
+
.replace('__SANDBOX__', sandbox)
|
|
170
|
+
.replace('__THEME_COLOR__', String(JSON.stringify(themeColor)));
|
|
168
171
|
return ELECTRON_MAIN_HEAD + steamInit + mainBody + steamPreload + ELECTRON_MAIN_TAIL;
|
|
169
172
|
}
|
|
170
173
|
|
|
@@ -206,7 +209,7 @@ export class ElectronPackager extends Packager {
|
|
|
206
209
|
/** @param {string} dest */
|
|
207
210
|
writeElectronMain(dest) {
|
|
208
211
|
const withSteam = this.usesPlugin('steam-auth');
|
|
209
|
-
const mainContent = buildElectronMain(withSteam);
|
|
212
|
+
const mainContent = buildElectronMain(withSteam, this.pkg.config?.themeColor);
|
|
210
213
|
|
|
211
214
|
if (withSteam) {
|
|
212
215
|
fs.writeFileSync(
|
package/lib/node/pipeline.js
CHANGED
|
@@ -2,6 +2,7 @@ import { loadProjectConfig, resolveServerUrl } from './config.js';
|
|
|
2
2
|
import { send } from './deploy.js';
|
|
3
3
|
import { prebuildPlatform } from './prebuild.js';
|
|
4
4
|
import { resolveBuilderForPlatform } from './registrations.js';
|
|
5
|
+
import { steamAuthFromCli } from './steam-auth.js';
|
|
5
6
|
|
|
6
7
|
/** @typedef {'prebuild' | 'build' | 'deploy'} AdaptfullyStage */
|
|
7
8
|
|
|
@@ -50,17 +51,29 @@ export async function runAdaptfullyStage(stage, platformKey, options) {
|
|
|
50
51
|
* @param {string[]} [argv=process.argv]
|
|
51
52
|
*/
|
|
52
53
|
export async function adaptfullyFromCli(argv = process.argv) {
|
|
53
|
-
const
|
|
54
|
+
const command = argv[2];
|
|
55
|
+
|
|
56
|
+
if (command === 'steam-auth') {
|
|
57
|
+
return steamAuthFromCli(argv);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const stage = command;
|
|
54
61
|
const platformKey = argv[3];
|
|
55
62
|
const cliServer = argv[4];
|
|
56
63
|
const mode = argv[5] ?? 'extract';
|
|
57
64
|
|
|
58
65
|
if (!stage || !platformKey) {
|
|
59
66
|
throw new Error(
|
|
60
|
-
'Usage
|
|
67
|
+
'Usage:\n'
|
|
68
|
+
+ ' adaptfully <prebuild|build|deploy> <platform> [server] [mode]\n'
|
|
69
|
+
+ ' adaptfully steam-auth [--username U] [--password P] [--output path]\n'
|
|
70
|
+
+ '\n'
|
|
71
|
+
+ 'Stages:\n'
|
|
61
72
|
+ ' prebuild Copy deploy/ and apply platform registrations → output/<platform>-prebuild/\n'
|
|
62
73
|
+ ' build prebuild + zip and send to Wrapfully\n'
|
|
63
|
-
+ ' deploy
|
|
74
|
+
+ ' deploy prebuild + zip and send to Wrapfully (platform release when credentials are in the zip)\n'
|
|
75
|
+
+ '\n'
|
|
76
|
+
+ ' steam-auth Install steamcmd if needed, log in interactively, write assets/meta/publish/steam.json',
|
|
64
77
|
);
|
|
65
78
|
}
|
|
66
79
|
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { createWriteStream } from 'node:fs';
|
|
3
|
+
import fsp from 'node:fs/promises';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { pipeline } from 'node:stream/promises';
|
|
7
|
+
import readline from 'node:readline/promises';
|
|
8
|
+
import { stdin as input, stdout as output } from 'node:process';
|
|
9
|
+
|
|
10
|
+
const STEAMCMD_INSTALL = {
|
|
11
|
+
win32: {
|
|
12
|
+
url: 'https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip',
|
|
13
|
+
archive: 'zip',
|
|
14
|
+
},
|
|
15
|
+
linux: {
|
|
16
|
+
url: 'https://steamcdn-a.akamaihd.net/client/installer/steamcmd_linux.tar.gz',
|
|
17
|
+
archive: 'tar.gz',
|
|
18
|
+
},
|
|
19
|
+
darwin: {
|
|
20
|
+
url: 'https://steamcdn-a.akamaihd.net/client/installer/steamcmd_osx.tar.gz',
|
|
21
|
+
archive: 'tar.gz',
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {string} [projectRoot]
|
|
27
|
+
*/
|
|
28
|
+
export function defaultSteamPublishPath(projectRoot = '.') {
|
|
29
|
+
return path.resolve(projectRoot, 'assets', 'meta', 'publish', 'steam.json');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {string} [cacheRoot]
|
|
34
|
+
*/
|
|
35
|
+
export function defaultSteamcmdCacheDir(cacheRoot) {
|
|
36
|
+
const root = cacheRoot ?? path.join(os.homedir(), '.adaptfully', 'steamcmd');
|
|
37
|
+
|
|
38
|
+
return path.join(root, process.platform);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @param {{ username: string, password: string, configVdfBuffer?: Buffer, sentryFileName?: string, sentryFileBuffer?: Buffer }} auth
|
|
43
|
+
*/
|
|
44
|
+
export function buildSteamPublishJson(auth) {
|
|
45
|
+
/** @type {Record<string, string>} */
|
|
46
|
+
const result = {
|
|
47
|
+
username: auth.username,
|
|
48
|
+
password: auth.password,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
if (auth.configVdfBuffer) {
|
|
52
|
+
result.configVdf = auth.configVdfBuffer.toString('base64');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (auth.sentryFileName && auth.sentryFileBuffer) {
|
|
56
|
+
result.sentryFileName = auth.sentryFileName;
|
|
57
|
+
result.sentryFile = auth.sentryFileBuffer.toString('base64');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @param {string} steamcmdDir
|
|
65
|
+
*/
|
|
66
|
+
export async function collectSteamAuthFiles(steamcmdDir) {
|
|
67
|
+
const configPath = path.join(steamcmdDir, 'config', 'config.vdf');
|
|
68
|
+
let configVdfBuffer;
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
configVdfBuffer = await fsp.readFile(configPath);
|
|
72
|
+
} catch {
|
|
73
|
+
throw new Error(
|
|
74
|
+
`Steam login did not produce config/config.vdf at "${configPath}". `
|
|
75
|
+
+ 'Check your username, password, and Steam Guard code.',
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const entries = await fsp.readdir(steamcmdDir);
|
|
80
|
+
const sentryFileName = entries.find((name) => name.startsWith('ssfn'));
|
|
81
|
+
let sentryFileBuffer;
|
|
82
|
+
|
|
83
|
+
if (sentryFileName) {
|
|
84
|
+
sentryFileBuffer = await fsp.readFile(path.join(steamcmdDir, sentryFileName));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return { configVdfBuffer, sentryFileName, sentryFileBuffer };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @param {string} command
|
|
92
|
+
* @param {string[]} args
|
|
93
|
+
*/
|
|
94
|
+
function runCommand(command, args) {
|
|
95
|
+
return new Promise((resolve, reject) => {
|
|
96
|
+
const child = spawn(command, args, { stdio: 'inherit' });
|
|
97
|
+
child.on('error', reject);
|
|
98
|
+
child.on('close', (code) => {
|
|
99
|
+
if (code === 0) {
|
|
100
|
+
resolve(code);
|
|
101
|
+
} else {
|
|
102
|
+
reject(new Error(`"${command}" exited with code ${code}`));
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* @param {string} url
|
|
110
|
+
* @param {string} dest
|
|
111
|
+
*/
|
|
112
|
+
async function downloadFile(url, dest) {
|
|
113
|
+
const response = await fetch(url);
|
|
114
|
+
|
|
115
|
+
if (!response.ok || !response.body) {
|
|
116
|
+
throw new Error(`Failed to download ${url}: HTTP ${response.status}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
await pipeline(response.body, createWriteStream(dest));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* @param {string} archivePath
|
|
124
|
+
* @param {string} destDir
|
|
125
|
+
* @param {'zip' | 'tar.gz'} archiveType
|
|
126
|
+
*/
|
|
127
|
+
async function extractArchive(archivePath, destDir, archiveType) {
|
|
128
|
+
await fsp.mkdir(destDir, { recursive: true });
|
|
129
|
+
|
|
130
|
+
if (archiveType === 'tar.gz') {
|
|
131
|
+
await runCommand('tar', ['-xzf', archivePath, '-C', destDir]);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (process.platform === 'win32') {
|
|
136
|
+
const escapedArchive = archivePath.replace(/'/g, "''");
|
|
137
|
+
const escapedDest = destDir.replace(/'/g, "''");
|
|
138
|
+
|
|
139
|
+
await runCommand('powershell', [
|
|
140
|
+
'-NoProfile',
|
|
141
|
+
'-Command',
|
|
142
|
+
`Expand-Archive -LiteralPath '${escapedArchive}' -DestinationPath '${escapedDest}' -Force`,
|
|
143
|
+
]);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
await runCommand('tar', ['-xf', archivePath, '-C', destDir]);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* @param {string} cacheDir
|
|
152
|
+
*/
|
|
153
|
+
export function steamcmdExecutable(cacheDir) {
|
|
154
|
+
if (process.platform === 'win32') {
|
|
155
|
+
return path.join(cacheDir, 'steamcmd.exe');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return path.join(cacheDir, 'steamcmd.sh');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* @param {string} cacheDir
|
|
163
|
+
*/
|
|
164
|
+
async function steamcmdIsInstalled(cacheDir) {
|
|
165
|
+
const executable = steamcmdExecutable(cacheDir);
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
await fsp.access(executable);
|
|
169
|
+
return true;
|
|
170
|
+
} catch {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* @param {string} cacheDir
|
|
177
|
+
* @param {(message: string) => void} [log]
|
|
178
|
+
*/
|
|
179
|
+
export async function ensureSteamcmd(cacheDir, log = console.log) {
|
|
180
|
+
if (await steamcmdIsInstalled(cacheDir)) {
|
|
181
|
+
log(`adaptfully: using steamcmd at ${steamcmdExecutable(cacheDir)}`);
|
|
182
|
+
return cacheDir;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const install = STEAMCMD_INSTALL[process.platform];
|
|
186
|
+
|
|
187
|
+
if (!install) {
|
|
188
|
+
throw new Error(`Steamcmd install is not supported on platform "${process.platform}".`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
await fsp.mkdir(cacheDir, { recursive: true });
|
|
192
|
+
|
|
193
|
+
const archiveName = install.archive === 'zip' ? 'steamcmd.zip' : 'steamcmd.tar.gz';
|
|
194
|
+
const archivePath = path.join(cacheDir, archiveName);
|
|
195
|
+
|
|
196
|
+
log(`adaptfully: downloading steamcmd from ${install.url}`);
|
|
197
|
+
await downloadFile(install.url, archivePath);
|
|
198
|
+
log('adaptfully: extracting steamcmd...');
|
|
199
|
+
await extractArchive(archivePath, cacheDir, install.archive);
|
|
200
|
+
await fsp.unlink(archivePath).catch(() => {});
|
|
201
|
+
|
|
202
|
+
if (!await steamcmdIsInstalled(cacheDir)) {
|
|
203
|
+
throw new Error(`Steamcmd install failed; expected executable at ${steamcmdExecutable(cacheDir)}`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (process.platform !== 'win32') {
|
|
207
|
+
await fsp.chmod(steamcmdExecutable(cacheDir), 0o755);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
log(`adaptfully: installed steamcmd to ${cacheDir}`);
|
|
211
|
+
return cacheDir;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* @param {string} cacheDir
|
|
216
|
+
* @param {string} username
|
|
217
|
+
* @param {string} password
|
|
218
|
+
*/
|
|
219
|
+
export function runSteamcmdLogin(cacheDir, username, password) {
|
|
220
|
+
const executable = steamcmdExecutable(cacheDir);
|
|
221
|
+
const args = ['+login', username, password, '+quit'];
|
|
222
|
+
const command = process.platform === 'win32' ? executable : executable;
|
|
223
|
+
const spawnCommand = process.platform === 'win32' ? command : 'bash';
|
|
224
|
+
const spawnArgs = process.platform === 'win32' ? args : [command, ...args];
|
|
225
|
+
|
|
226
|
+
return new Promise((resolve, reject) => {
|
|
227
|
+
console.log('');
|
|
228
|
+
console.log('Steamcmd is starting. If Steam Guard is enabled, enter the code when prompted.');
|
|
229
|
+
console.log('');
|
|
230
|
+
|
|
231
|
+
const child = spawn(spawnCommand, spawnArgs, {
|
|
232
|
+
cwd: cacheDir,
|
|
233
|
+
stdio: 'inherit',
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
child.on('error', reject);
|
|
237
|
+
child.on('close', (code) => {
|
|
238
|
+
if (code === 0) {
|
|
239
|
+
resolve(code);
|
|
240
|
+
} else {
|
|
241
|
+
reject(new Error(`steamcmd login failed with exit code ${code}`));
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* @param {string[]} argv
|
|
249
|
+
*/
|
|
250
|
+
function parseSteamAuthArgv(argv) {
|
|
251
|
+
/** @type {Record<string, string>} */
|
|
252
|
+
const options = {
|
|
253
|
+
projectRoot: '.',
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
for (let i = 3; i < argv.length; i++) {
|
|
257
|
+
const arg = argv[i];
|
|
258
|
+
|
|
259
|
+
if (arg === '--username' && argv[i + 1]) {
|
|
260
|
+
options.username = argv[++i];
|
|
261
|
+
} else if (arg === '--password' && argv[i + 1]) {
|
|
262
|
+
options.password = argv[++i];
|
|
263
|
+
} else if (arg === '--output' && argv[i + 1]) {
|
|
264
|
+
options.output = argv[++i];
|
|
265
|
+
} else if (arg === '--steamcmd-dir' && argv[i + 1]) {
|
|
266
|
+
options.steamcmdDir = argv[++i];
|
|
267
|
+
} else if (arg === '--project-root' && argv[i + 1]) {
|
|
268
|
+
options.projectRoot = argv[++i];
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return options;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* @param {{ username?: string, password?: string, output?: string, steamcmdDir?: string, projectRoot?: string, log?: (message: string) => void }} [options]
|
|
277
|
+
*/
|
|
278
|
+
export async function runSteamAuth(options = {}) {
|
|
279
|
+
const log = options.log ?? console.log;
|
|
280
|
+
const username = options.username
|
|
281
|
+
?? process.env.STEAM_USERNAME
|
|
282
|
+
?? process.env.STEAM_BUILD_USERNAME;
|
|
283
|
+
const password = options.password
|
|
284
|
+
?? process.env.STEAM_PASSWORD
|
|
285
|
+
?? process.env.STEAM_BUILD_PASSWORD;
|
|
286
|
+
const outputPath = path.resolve(options.output ?? defaultSteamPublishPath(options.projectRoot ?? '.'));
|
|
287
|
+
const cacheDir = options.steamcmdDir ?? defaultSteamcmdCacheDir();
|
|
288
|
+
|
|
289
|
+
let resolvedUsername = username;
|
|
290
|
+
let resolvedPassword = password;
|
|
291
|
+
|
|
292
|
+
if (!resolvedUsername || !resolvedPassword) {
|
|
293
|
+
const rl = readline.createInterface({ input, output });
|
|
294
|
+
|
|
295
|
+
try {
|
|
296
|
+
if (!resolvedUsername) {
|
|
297
|
+
resolvedUsername = await rl.question('Steam build account username: ');
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (!resolvedPassword) {
|
|
301
|
+
resolvedPassword = await rl.question('Steam build account password: ');
|
|
302
|
+
}
|
|
303
|
+
} finally {
|
|
304
|
+
rl.close();
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
resolvedUsername = String(resolvedUsername).trim();
|
|
309
|
+
resolvedPassword = String(resolvedPassword);
|
|
310
|
+
|
|
311
|
+
if (!resolvedUsername || !resolvedPassword) {
|
|
312
|
+
throw new Error('Steam username and password are required.');
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
await ensureSteamcmd(cacheDir, log);
|
|
316
|
+
await runSteamcmdLogin(cacheDir, resolvedUsername, resolvedPassword);
|
|
317
|
+
|
|
318
|
+
const authFiles = await collectSteamAuthFiles(cacheDir);
|
|
319
|
+
const steamJson = buildSteamPublishJson({
|
|
320
|
+
username: resolvedUsername,
|
|
321
|
+
password: resolvedPassword,
|
|
322
|
+
...authFiles,
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
await fsp.mkdir(path.dirname(outputPath), { recursive: true });
|
|
326
|
+
await fsp.writeFile(outputPath, `${JSON.stringify(steamJson, null, 4)}\n`);
|
|
327
|
+
|
|
328
|
+
log(`adaptfully: wrote ${outputPath}`);
|
|
329
|
+
if (authFiles.sentryFileName) {
|
|
330
|
+
log(`adaptfully: included Steam Guard sentry file ${authFiles.sentryFileName}`);
|
|
331
|
+
} else {
|
|
332
|
+
log('adaptfully: no sentry file was created (Steam Guard may be disabled on this account)');
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return { outputPath, steamJson };
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* @param {string[]} [argv]
|
|
340
|
+
*/
|
|
341
|
+
export async function steamAuthFromCli(argv = process.argv) {
|
|
342
|
+
return runSteamAuth(parseSteamAuthArgv(argv));
|
|
343
|
+
}
|
|
@@ -162,6 +162,7 @@
|
|
|
162
162
|
|
|
163
163
|
whenReady(done) {
|
|
164
164
|
if (!this.#hasSteamBridge()) {
|
|
165
|
+
console.warn('[adaptfully steam-auth] Steamworks bridge not found on window');
|
|
165
166
|
this.online = false;
|
|
166
167
|
done({ error: 'Steamworks client not available' });
|
|
167
168
|
return;
|
|
@@ -179,6 +180,7 @@
|
|
|
179
180
|
}
|
|
180
181
|
|
|
181
182
|
if (Date.now() - started >= timeoutMs) {
|
|
183
|
+
console.warn('[adaptfully steam-auth] timed out waiting for Steam identity');
|
|
182
184
|
this.online = false;
|
|
183
185
|
done({ error: 'Steamworks client not available' });
|
|
184
186
|
return;
|
|
@@ -200,10 +202,13 @@
|
|
|
200
202
|
login(callback) {
|
|
201
203
|
this.#resolveIdentity()
|
|
202
204
|
.then((identity) => {
|
|
203
|
-
this.#applyIdentity(identity)
|
|
205
|
+
if (!this.#applyIdentity(identity)) {
|
|
206
|
+
console.warn('[adaptfully steam-auth] login: no Steam identity available', identity);
|
|
207
|
+
}
|
|
204
208
|
this.#complete(callback);
|
|
205
209
|
})
|
|
206
|
-
.catch(() => {
|
|
210
|
+
.catch((err) => {
|
|
211
|
+
console.error('[adaptfully steam-auth] login failed:', err);
|
|
207
212
|
this.user = null;
|
|
208
213
|
this.authenticated = false;
|
|
209
214
|
this.online = false;
|