@makefully/adaptfully 3.3.0 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +14 -4
- package/lib/node/index.js +9 -0
- package/lib/node/packagers/electron.js +17 -6
- 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,20 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented in this file.
|
|
4
4
|
|
|
5
|
+
## 3.5.0 — 2026-06-27
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **`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.
|
|
10
|
+
- Exported helpers: `runSteamAuth`, `buildSteamPublishJson`, `collectSteamAuthFiles`, `ensureSteamcmd`.
|
|
11
|
+
|
|
12
|
+
## 3.4.0 — 2026-06-25
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- Electron **`main.js`** allows same-app `file://` navigation while still opening external http(s) links in the system browser.
|
|
17
|
+
- Steam builds (`steam-auth`) set renderer **`sandbox: false`** so the steamworks preload bridge can reach the main process; non-Steam Electron builds keep `sandbox: true`.
|
|
18
|
+
|
|
5
19
|
## 3.3.0 — 2026-06-23
|
|
6
20
|
|
|
7
21
|
### Added
|
package/README.md
CHANGED
|
@@ -24,6 +24,7 @@ Games register platform services before load and retrieve them in-game. Adaptful
|
|
|
24
24
|
adaptfully prebuild web # deploy/ → output/web-prebuild/
|
|
25
25
|
adaptfully build steam # prebuild + zip and send to Wrapfully
|
|
26
26
|
adaptfully deploy steam # build + platform release when credentials are present
|
|
27
|
+
adaptfully steam-auth # one-time: log in with steamcmd → assets/meta/publish/steam.json
|
|
27
28
|
```
|
|
28
29
|
|
|
29
30
|
| Stage | What it does |
|
|
@@ -31,6 +32,7 @@ adaptfully deploy steam # build + platform release when credentials are pres
|
|
|
31
32
|
| `prebuild` | Copy `deploy/` to `output/<platform>-prebuild/` and inject registrations into `config.htmlInjections` |
|
|
32
33
|
| `build` | Prebuild, then POST the result to Wrapfully |
|
|
33
34
|
| `deploy` | Build, then release to the target platform (Steam upload, webapp SFTP, etc. via Wrapfully when credentials are in `assets/meta/publish/`) |
|
|
35
|
+
| `steam-auth` | One-time local setup: install steamcmd, interactive login, write `assets/meta/publish/steam.json` |
|
|
34
36
|
|
|
35
37
|
`wrapfully-deploy` is a compatibility alias for `adaptfully deploy` when invoked with a Wrapfully builder name (`steam`, `win`, `android`, etc.).
|
|
36
38
|
|
|
@@ -199,6 +201,7 @@ Run from your project root:
|
|
|
199
201
|
|
|
200
202
|
```bash
|
|
201
203
|
npx adaptfully <prebuild|build|deploy> <platform> [server] [mode]
|
|
204
|
+
npx adaptfully steam-auth [--username U] [--password P] [--output path]
|
|
202
205
|
```
|
|
203
206
|
|
|
204
207
|
| Stage | Description |
|
|
@@ -375,7 +378,7 @@ Standard npm fields (`name`, `version`, `description`) are used directly. Add a
|
|
|
375
378
|
| `publisherWebsite` | Cordova, web | Company URL |
|
|
376
379
|
| `publisherEmailAddress` | Cordova | Contact email |
|
|
377
380
|
| `scope` | Web/PWA | Base URL scope for the web app |
|
|
378
|
-
| `themeColor` | Cordova, UWP, web | Loading screen / theme color |
|
|
381
|
+
| `themeColor` | Cordova, Electron, UWP, web | Loading screen / theme color |
|
|
379
382
|
| `twitterId` | Web | Twitter handle for meta tags |
|
|
380
383
|
| `steamId` | Steam | Steam app ID |
|
|
381
384
|
| `deployFolder` | Client | Neutral deploy directory staged before prebuild (default: `deploy`) |
|
|
@@ -522,15 +525,22 @@ Requires the Android and Apple package requirements above.
|
|
|
522
525
|
|
|
523
526
|
`steam-dev` builds debug Electron binaries for Windows, Mac, and Linux without uploading to Steam. No `steam.json` credentials are required.
|
|
524
527
|
|
|
525
|
-
For release uploads,
|
|
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/publish/steam.json`:
|
|
526
529
|
|
|
527
530
|
```json
|
|
528
531
|
{
|
|
529
|
-
"username": "(your
|
|
530
|
-
"password": "(your password)"
|
|
532
|
+
"username": "(your Steam build account)",
|
|
533
|
+
"password": "(your password)",
|
|
534
|
+
"configVdf": "(base64 login token from steamcmd)",
|
|
535
|
+
"sentryFileName": "(only when Steam Guard is enabled)",
|
|
536
|
+
"sentryFile": "(base64 ssfn file — only when Steam Guard is enabled)"
|
|
531
537
|
}
|
|
532
538
|
```
|
|
533
539
|
|
|
540
|
+
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.
|
|
541
|
+
|
|
542
|
+
Re-run `adaptfully steam-auth` if Steam invalidates the token (new machine, password change, or expired guard).
|
|
543
|
+
|
|
534
544
|
Also set `steamId` in your `config` block.
|
|
535
545
|
|
|
536
546
|
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.
|
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';
|
|
@@ -24,16 +24,22 @@ const
|
|
|
24
24
|
|
|
25
25
|
const ELECTRON_MAIN_BODY = `
|
|
26
26
|
/**
|
|
27
|
-
* Opens
|
|
27
|
+
* Opens http(s) links in the system browser; allows same-app file:// navigation.
|
|
28
28
|
* @param {Electron.BrowserWindow} win
|
|
29
29
|
*/
|
|
30
30
|
function attachExternalLinkHandlers(win) {
|
|
31
31
|
win.webContents.setWindowOpenHandler(({url}) => {
|
|
32
|
+
if (url.startsWith('file:')) {
|
|
33
|
+
return {action: 'deny'};
|
|
34
|
+
}
|
|
32
35
|
shell.openExternal(url);
|
|
33
36
|
return {action: 'deny'};
|
|
34
37
|
});
|
|
35
38
|
|
|
36
39
|
win.webContents.on('will-navigate', (event, url) => {
|
|
40
|
+
if (url.startsWith('file:')) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
37
43
|
event.preventDefault();
|
|
38
44
|
shell.openExternal(url);
|
|
39
45
|
});
|
|
@@ -63,7 +69,7 @@ function createWindow () {
|
|
|
63
69
|
};
|
|
64
70
|
})(),
|
|
65
71
|
win = new BrowserWindow({
|
|
66
|
-
backgroundColor:
|
|
72
|
+
backgroundColor: __THEME_COLOR__,
|
|
67
73
|
width: lastState.bounds.width,
|
|
68
74
|
height: lastState.bounds.height,
|
|
69
75
|
minWidth: 200,
|
|
@@ -72,7 +78,7 @@ function createWindow () {
|
|
|
72
78
|
devTools: debugMode,
|
|
73
79
|
nodeIntegration: false,
|
|
74
80
|
contextIsolation: true,
|
|
75
|
-
sandbox:
|
|
81
|
+
sandbox: __SANDBOX__`;
|
|
76
82
|
|
|
77
83
|
const ELECTRON_MAIN_TAIL = `
|
|
78
84
|
},
|
|
@@ -153,11 +159,16 @@ if (steamClient) {
|
|
|
153
159
|
|
|
154
160
|
/**
|
|
155
161
|
* @param {boolean} withSteam
|
|
162
|
+
* @param {string} [themeColor]
|
|
156
163
|
*/
|
|
157
|
-
export function buildElectronMain(withSteam) {
|
|
164
|
+
export function buildElectronMain(withSteam, themeColor) {
|
|
158
165
|
const steamInit = withSteam ? STEAM_INIT : '';
|
|
159
166
|
const steamPreload = withSteam ? STEAM_PRELOAD : '';
|
|
160
|
-
|
|
167
|
+
const sandbox = withSteam ? 'false' : 'true';
|
|
168
|
+
const mainBody = ELECTRON_MAIN_BODY
|
|
169
|
+
.replace('__SANDBOX__', sandbox)
|
|
170
|
+
.replace('__THEME_COLOR__', String(JSON.stringify(themeColor)));
|
|
171
|
+
return ELECTRON_MAIN_HEAD + steamInit + mainBody + steamPreload + ELECTRON_MAIN_TAIL;
|
|
161
172
|
}
|
|
162
173
|
|
|
163
174
|
/**
|
|
@@ -198,7 +209,7 @@ export class ElectronPackager extends Packager {
|
|
|
198
209
|
/** @param {string} dest */
|
|
199
210
|
writeElectronMain(dest) {
|
|
200
211
|
const withSteam = this.usesPlugin('steam-auth');
|
|
201
|
-
const mainContent = buildElectronMain(withSteam);
|
|
212
|
+
const mainContent = buildElectronMain(withSteam, this.pkg.config?.themeColor);
|
|
202
213
|
|
|
203
214
|
if (withSteam) {
|
|
204
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 build + platform release when credentials are present (via Wrapfully)'
|
|
74
|
+
+ ' deploy build + platform release when credentials are present (via Wrapfully)\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;
|