@caleb-collar/steamcmd 1.0.0-alpha.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 +141 -0
- package/LICENSE +21 -0
- package/README.md +385 -0
- package/bin/steamcmd +35 -0
- package/package.json +71 -0
- package/src/download.js +220 -0
- package/src/env.js +66 -0
- package/src/install.js +356 -0
- package/src/steamcmd.js +404 -0
- package/src/steamcmd.mjs +28 -0
- package/types/steamcmd.d.ts +466 -0
package/src/steamcmd.js
ADDED
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module steamcmd
|
|
3
|
+
* @description Node.js wrapper for SteamCMD - download, install, and manage Steam applications
|
|
4
|
+
* @author Björn Dahlgren
|
|
5
|
+
* @license MIT
|
|
6
|
+
* @see https://github.com/dahlgren/node-steamcmd
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs')
|
|
10
|
+
const path = require('path')
|
|
11
|
+
const { promisify } = require('util')
|
|
12
|
+
const { EventEmitter } = require('events')
|
|
13
|
+
|
|
14
|
+
const download = require('./download')
|
|
15
|
+
const env = require('./env')
|
|
16
|
+
const install = require('./install')
|
|
17
|
+
|
|
18
|
+
const access = promisify(fs.access)
|
|
19
|
+
const readdir = promisify(fs.readdir)
|
|
20
|
+
const readFile = promisify(fs.readFile)
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Custom error class for SteamCMD operations
|
|
24
|
+
*/
|
|
25
|
+
class SteamCmdError extends Error {
|
|
26
|
+
constructor (message, code, cause) {
|
|
27
|
+
super(message)
|
|
28
|
+
this.name = 'SteamCmdError'
|
|
29
|
+
this.code = code
|
|
30
|
+
if (cause) {
|
|
31
|
+
this.cause = cause
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Check if SteamCMD is installed and executable
|
|
38
|
+
* @returns {Promise<boolean>} True if SteamCMD is available
|
|
39
|
+
*/
|
|
40
|
+
async function isInstalled () {
|
|
41
|
+
const executablePath = env.executable()
|
|
42
|
+
if (!executablePath) return false
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
await access(executablePath, (fs.constants || fs).X_OK)
|
|
46
|
+
return true
|
|
47
|
+
} catch {
|
|
48
|
+
return false
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Ensure SteamCMD is installed, downloading if necessary
|
|
54
|
+
* @param {Object} [options] - Options
|
|
55
|
+
* @param {Function} [options.onProgress] - Download progress callback
|
|
56
|
+
* @returns {Promise<void>}
|
|
57
|
+
* @throws {SteamCmdError} If download fails
|
|
58
|
+
*/
|
|
59
|
+
async function ensureInstalled (options) {
|
|
60
|
+
options = options || {}
|
|
61
|
+
const installed = await isInstalled()
|
|
62
|
+
if (installed) return
|
|
63
|
+
|
|
64
|
+
console.log('SteamCMD needs to be installed')
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
await download({ onProgress: options.onProgress })
|
|
68
|
+
console.log('SteamCMD was installed')
|
|
69
|
+
} catch (err) {
|
|
70
|
+
throw new SteamCmdError(
|
|
71
|
+
'Failed to install SteamCMD',
|
|
72
|
+
'INSTALL_FAILED',
|
|
73
|
+
err
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Install a Steam application or Workshop item
|
|
80
|
+
*
|
|
81
|
+
* @param {Object} options - Installation options
|
|
82
|
+
* @param {number|string} [options.applicationId] - Steam application ID to install
|
|
83
|
+
* @param {number|string} [options.workshopId] - Workshop item ID to install (requires applicationId)
|
|
84
|
+
* @param {string} [options.path] - Installation directory
|
|
85
|
+
* @param {string} [options.username] - Steam username for authentication
|
|
86
|
+
* @param {string} [options.password] - Steam password for authentication
|
|
87
|
+
* @param {string} [options.steamGuardCode] - Steam Guard code for two-factor authentication
|
|
88
|
+
* @param {string} [options.platform] - Target platform ('windows', 'macos', 'linux')
|
|
89
|
+
* @param {Function} [options.onProgress] - Progress callback(progress) with { phase, percent, bytesDownloaded, totalBytes }
|
|
90
|
+
* @param {Function} [options.onOutput] - Output callback(data, type) where type is 'stdout' or 'stderr'
|
|
91
|
+
* @param {Function} [callback] - Optional callback(err). If omitted, returns a Promise.
|
|
92
|
+
* @returns {Promise<void>|undefined} Promise if no callback provided
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* // Promise-based usage with progress
|
|
96
|
+
* await steamcmd.install({
|
|
97
|
+
* applicationId: 740,
|
|
98
|
+
* path: './server',
|
|
99
|
+
* onProgress: (p) => console.log(`${p.phase}: ${p.percent}%`)
|
|
100
|
+
* });
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* // Callback-based usage (legacy)
|
|
104
|
+
* steamcmd.install({ applicationId: 740 }, (err) => {
|
|
105
|
+
* if (err) console.error(err);
|
|
106
|
+
* });
|
|
107
|
+
*/
|
|
108
|
+
function steamCmdInstall (options, callback) {
|
|
109
|
+
// Support Promise-based usage
|
|
110
|
+
if (typeof callback !== 'function') {
|
|
111
|
+
return steamCmdInstallAsync(options)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Legacy callback-based usage
|
|
115
|
+
steamCmdInstallAsync(options)
|
|
116
|
+
.then(() => callback(null))
|
|
117
|
+
.catch((err) => callback(err))
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Async implementation of install
|
|
122
|
+
* @private
|
|
123
|
+
*/
|
|
124
|
+
async function steamCmdInstallAsync (options) {
|
|
125
|
+
// Validate options early
|
|
126
|
+
if (!options || typeof options !== 'object') {
|
|
127
|
+
throw new SteamCmdError('Options must be an object', 'INVALID_OPTIONS')
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Ensure SteamCMD is installed (pass download progress)
|
|
131
|
+
await ensureInstalled({ onProgress: options.onProgress })
|
|
132
|
+
|
|
133
|
+
// Run installation
|
|
134
|
+
const executablePath = env.executable()
|
|
135
|
+
try {
|
|
136
|
+
await install(executablePath, options)
|
|
137
|
+
} catch (err) {
|
|
138
|
+
throw new SteamCmdError(
|
|
139
|
+
`Installation failed: ${err.message}`,
|
|
140
|
+
'RUN_FAILED',
|
|
141
|
+
err
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Get information about the SteamCMD installation
|
|
148
|
+
* @returns {Object} SteamCMD paths and status
|
|
149
|
+
*/
|
|
150
|
+
function getInfo () {
|
|
151
|
+
return {
|
|
152
|
+
directory: env.directory(),
|
|
153
|
+
executable: env.executable(),
|
|
154
|
+
platform: env.platform(),
|
|
155
|
+
isSupported: env.isPlatformSupported()
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Parse Steam app manifest file (.acf)
|
|
161
|
+
* @param {string} manifestPath - Path to the manifest file
|
|
162
|
+
* @returns {Promise<Object>} Parsed manifest data
|
|
163
|
+
* @private
|
|
164
|
+
*/
|
|
165
|
+
async function parseAppManifest (manifestPath) {
|
|
166
|
+
const content = await readFile(manifestPath, 'utf8')
|
|
167
|
+
const result = {}
|
|
168
|
+
|
|
169
|
+
// Simple VDF parser for app manifests
|
|
170
|
+
const lines = content.split('\n')
|
|
171
|
+
for (const line of lines) {
|
|
172
|
+
const match = line.match(/"(\w+)"\s+"([^"]*)"/)
|
|
173
|
+
if (match) {
|
|
174
|
+
result[match[1]] = match[2]
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return result
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Get a list of installed Steam applications in a directory
|
|
183
|
+
* @param {Object} options - Options
|
|
184
|
+
* @param {string} options.path - Installation directory to scan
|
|
185
|
+
* @returns {Promise<Array<Object>>} Array of installed app information
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* const apps = await steamcmd.getInstalledApps({ path: './server' });
|
|
189
|
+
* // [{ appId: 740, name: 'Counter-Strike Global Offensive - Dedicated Server', ... }]
|
|
190
|
+
*/
|
|
191
|
+
async function getInstalledApps (options) {
|
|
192
|
+
if (!options || !options.path) {
|
|
193
|
+
throw new SteamCmdError('path option is required', 'INVALID_OPTIONS')
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const steamappsDir = path.join(options.path, 'steamapps')
|
|
197
|
+
const apps = []
|
|
198
|
+
|
|
199
|
+
try {
|
|
200
|
+
await access(steamappsDir, fs.constants.R_OK)
|
|
201
|
+
} catch {
|
|
202
|
+
// No steamapps directory, return empty array
|
|
203
|
+
return apps
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
const files = await readdir(steamappsDir)
|
|
208
|
+
const manifestFiles = files.filter(
|
|
209
|
+
(f) => f.startsWith('appmanifest_') && f.endsWith('.acf')
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
for (const file of manifestFiles) {
|
|
213
|
+
try {
|
|
214
|
+
const manifest = await parseAppManifest(path.join(steamappsDir, file))
|
|
215
|
+
apps.push({
|
|
216
|
+
appId: parseInt(manifest.appid, 10),
|
|
217
|
+
name: manifest.name || 'Unknown',
|
|
218
|
+
installDir: manifest.installdir || null,
|
|
219
|
+
sizeOnDisk: parseInt(manifest.SizeOnDisk || '0', 10),
|
|
220
|
+
buildId: parseInt(manifest.buildid || '0', 10),
|
|
221
|
+
lastUpdated: manifest.LastUpdated
|
|
222
|
+
? new Date(parseInt(manifest.LastUpdated, 10) * 1000)
|
|
223
|
+
: null,
|
|
224
|
+
state: parseInt(manifest.StateFlags || '0', 10)
|
|
225
|
+
})
|
|
226
|
+
} catch {
|
|
227
|
+
// Skip invalid manifest files
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
} catch {
|
|
231
|
+
// Error reading directory
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return apps
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Update an installed Steam application
|
|
239
|
+
* @param {Object} options - Update options
|
|
240
|
+
* @param {number|string} options.applicationId - Steam application ID to update
|
|
241
|
+
* @param {string} [options.path] - Installation directory
|
|
242
|
+
* @param {string} [options.username] - Steam username for authentication
|
|
243
|
+
* @param {string} [options.password] - Steam password for authentication
|
|
244
|
+
* @param {string} [options.steamGuardCode] - Steam Guard code
|
|
245
|
+
* @param {string} [options.platform] - Target platform
|
|
246
|
+
* @param {Function} [options.onProgress] - Progress callback
|
|
247
|
+
* @param {Function} [options.onOutput] - Output callback
|
|
248
|
+
* @returns {Promise<void>}
|
|
249
|
+
*
|
|
250
|
+
* @example
|
|
251
|
+
* await steamcmd.update({
|
|
252
|
+
* applicationId: 740,
|
|
253
|
+
* path: './server',
|
|
254
|
+
* onProgress: (p) => console.log(`${p.phase}: ${p.percent}%`)
|
|
255
|
+
* });
|
|
256
|
+
*/
|
|
257
|
+
async function update (options) {
|
|
258
|
+
if (!options || !options.applicationId) {
|
|
259
|
+
throw new SteamCmdError(
|
|
260
|
+
'applicationId option is required',
|
|
261
|
+
'INVALID_OPTIONS'
|
|
262
|
+
)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// update is essentially the same as install - SteamCMD handles both
|
|
266
|
+
return steamCmdInstallAsync(options)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Validate an installed Steam application
|
|
271
|
+
* @param {Object} options - Validation options
|
|
272
|
+
* @param {number|string} options.applicationId - Steam application ID to validate
|
|
273
|
+
* @param {string} [options.path] - Installation directory
|
|
274
|
+
* @param {string} [options.username] - Steam username for authentication
|
|
275
|
+
* @param {string} [options.password] - Steam password for authentication
|
|
276
|
+
* @param {string} [options.steamGuardCode] - Steam Guard code
|
|
277
|
+
* @param {Function} [options.onProgress] - Progress callback
|
|
278
|
+
* @param {Function} [options.onOutput] - Output callback
|
|
279
|
+
* @returns {Promise<void>}
|
|
280
|
+
*
|
|
281
|
+
* @example
|
|
282
|
+
* await steamcmd.validate({
|
|
283
|
+
* applicationId: 740,
|
|
284
|
+
* path: './server'
|
|
285
|
+
* });
|
|
286
|
+
*/
|
|
287
|
+
async function validate (options) {
|
|
288
|
+
if (!options || !options.applicationId) {
|
|
289
|
+
throw new SteamCmdError(
|
|
290
|
+
'applicationId option is required',
|
|
291
|
+
'INVALID_OPTIONS'
|
|
292
|
+
)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// validate is the same as install - SteamCMD always validates
|
|
296
|
+
return steamCmdInstallAsync(options)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Get the installed version (build ID) of a Steam application
|
|
301
|
+
* @param {Object} options - Options
|
|
302
|
+
* @param {number|string} options.applicationId - Steam application ID
|
|
303
|
+
* @param {string} options.path - Installation directory
|
|
304
|
+
* @returns {Promise<Object|null>} Version information or null if not installed
|
|
305
|
+
*
|
|
306
|
+
* @example
|
|
307
|
+
* const version = await steamcmd.getInstalledVersion({
|
|
308
|
+
* applicationId: 740,
|
|
309
|
+
* path: './server'
|
|
310
|
+
* });
|
|
311
|
+
* // { appId: 740, buildId: 12345678, lastUpdated: Date }
|
|
312
|
+
*/
|
|
313
|
+
async function getInstalledVersion (options) {
|
|
314
|
+
if (!options || !options.applicationId || !options.path) {
|
|
315
|
+
throw new SteamCmdError(
|
|
316
|
+
'applicationId and path options are required',
|
|
317
|
+
'INVALID_OPTIONS'
|
|
318
|
+
)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const appId = Number(options.applicationId)
|
|
322
|
+
const manifestPath = path.join(
|
|
323
|
+
options.path,
|
|
324
|
+
'steamapps',
|
|
325
|
+
`appmanifest_${appId}.acf`
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
try {
|
|
329
|
+
await access(manifestPath, fs.constants.R_OK)
|
|
330
|
+
const manifest = await parseAppManifest(manifestPath)
|
|
331
|
+
|
|
332
|
+
return {
|
|
333
|
+
appId: parseInt(manifest.appid, 10),
|
|
334
|
+
name: manifest.name || 'Unknown',
|
|
335
|
+
buildId: parseInt(manifest.buildid || '0', 10),
|
|
336
|
+
lastUpdated: manifest.LastUpdated
|
|
337
|
+
? new Date(parseInt(manifest.LastUpdated, 10) * 1000)
|
|
338
|
+
: null
|
|
339
|
+
}
|
|
340
|
+
} catch {
|
|
341
|
+
return null
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Create an EventEmitter for SteamCMD operations with real-time progress
|
|
347
|
+
* @param {string} operation - Operation type ('install', 'update', 'validate')
|
|
348
|
+
* @param {Object} options - Operation options
|
|
349
|
+
* @returns {EventEmitter} Emitter that fires 'progress', 'output', 'error', and 'complete' events
|
|
350
|
+
*
|
|
351
|
+
* @example
|
|
352
|
+
* const emitter = steamcmd.createProgressEmitter('install', { applicationId: 740 });
|
|
353
|
+
* emitter.on('progress', (p) => console.log(`${p.phase}: ${p.percent}%`));
|
|
354
|
+
* emitter.on('output', (data, type) => console.log(`[${type}] ${data}`));
|
|
355
|
+
* emitter.on('complete', () => console.log('Done!'));
|
|
356
|
+
* emitter.on('error', (err) => console.error(err));
|
|
357
|
+
*/
|
|
358
|
+
function createProgressEmitter (operation, options) {
|
|
359
|
+
const emitter = new EventEmitter()
|
|
360
|
+
|
|
361
|
+
process.nextTick(async () => {
|
|
362
|
+
try {
|
|
363
|
+
// Ensure SteamCMD is installed first
|
|
364
|
+
await ensureInstalled({
|
|
365
|
+
onProgress: (progress) => emitter.emit('progress', progress)
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
// Run the operation
|
|
369
|
+
const executablePath = env.executable()
|
|
370
|
+
const operationOptions = {
|
|
371
|
+
...options,
|
|
372
|
+
onProgress: (progress) => emitter.emit('progress', progress),
|
|
373
|
+
onOutput: (data, type) => emitter.emit('output', data, type)
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
await install(executablePath, operationOptions)
|
|
377
|
+
emitter.emit('complete')
|
|
378
|
+
} catch (err) {
|
|
379
|
+
emitter.emit('error', err)
|
|
380
|
+
}
|
|
381
|
+
})
|
|
382
|
+
|
|
383
|
+
return emitter
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
module.exports = {
|
|
387
|
+
install: steamCmdInstall,
|
|
388
|
+
isInstalled,
|
|
389
|
+
ensureInstalled,
|
|
390
|
+
getInfo,
|
|
391
|
+
SteamCmdError,
|
|
392
|
+
// New functions
|
|
393
|
+
getInstalledApps,
|
|
394
|
+
update,
|
|
395
|
+
validate,
|
|
396
|
+
getInstalledVersion,
|
|
397
|
+
createProgressEmitter,
|
|
398
|
+
// Re-export error classes for consumers
|
|
399
|
+
DownloadError: download.DownloadError,
|
|
400
|
+
InstallError: install.InstallError,
|
|
401
|
+
// Re-export progress helpers
|
|
402
|
+
downloadWithProgress: download.downloadWithProgress,
|
|
403
|
+
installWithProgress: install.installWithProgress
|
|
404
|
+
}
|
package/src/steamcmd.mjs
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module steamcmd
|
|
3
|
+
* @description ESM wrapper for steamcmd module
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createRequire } from "module";
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
|
|
9
|
+
const steamcmd = require("./steamcmd.js");
|
|
10
|
+
|
|
11
|
+
// Named exports
|
|
12
|
+
export const install = steamcmd.install;
|
|
13
|
+
export const isInstalled = steamcmd.isInstalled;
|
|
14
|
+
export const ensureInstalled = steamcmd.ensureInstalled;
|
|
15
|
+
export const getInfo = steamcmd.getInfo;
|
|
16
|
+
export const SteamCmdError = steamcmd.SteamCmdError;
|
|
17
|
+
export const DownloadError = steamcmd.DownloadError;
|
|
18
|
+
export const InstallError = steamcmd.InstallError;
|
|
19
|
+
export const downloadWithProgress = steamcmd.downloadWithProgress;
|
|
20
|
+
export const installWithProgress = steamcmd.installWithProgress;
|
|
21
|
+
export const getInstalledApps = steamcmd.getInstalledApps;
|
|
22
|
+
export const update = steamcmd.update;
|
|
23
|
+
export const validate = steamcmd.validate;
|
|
24
|
+
export const getInstalledVersion = steamcmd.getInstalledVersion;
|
|
25
|
+
export const createProgressEmitter = steamcmd.createProgressEmitter;
|
|
26
|
+
|
|
27
|
+
// Default export
|
|
28
|
+
export default steamcmd;
|