@caleb-collar/steamcmd 1.0.0-alpha.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -3
- package/LICENSE +1 -1
- package/README.md +24 -12
- package/bin/steamcmd +19 -19
- package/dist/download.d.ts +85 -0
- package/dist/download.d.ts.map +1 -0
- package/dist/download.js +213 -0
- package/dist/download.js.map +1 -0
- package/dist/env.d.ts +38 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +70 -0
- package/dist/env.js.map +1 -0
- package/dist/install.d.ts +128 -0
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +297 -0
- package/dist/install.js.map +1 -0
- package/dist/steamcmd.d.ts +263 -0
- package/dist/steamcmd.d.ts.map +1 -0
- package/dist/steamcmd.js +389 -0
- package/dist/steamcmd.js.map +1 -0
- package/dist/steamcmd.mjs +28 -0
- package/package.json +24 -13
- package/src/download.js +0 -220
- package/src/env.js +0 -66
- package/src/install.js +0 -356
- package/src/steamcmd.js +0 -404
- package/src/steamcmd.mjs +0 -28
- package/types/steamcmd.d.ts +0 -466
package/src/install.js
DELETED
|
@@ -1,356 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module steamcmd/install
|
|
3
|
-
* @description Spawns SteamCMD processes to install applications and workshop items
|
|
4
|
-
* @private
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const childProcess = require('child_process')
|
|
8
|
-
const { EventEmitter } = require('events')
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Custom error class for installation failures
|
|
12
|
-
* @extends Error
|
|
13
|
-
*/
|
|
14
|
-
class InstallError extends Error {
|
|
15
|
-
constructor (message, code, exitCode) {
|
|
16
|
-
super(message)
|
|
17
|
-
this.name = 'InstallError'
|
|
18
|
-
this.code = code
|
|
19
|
-
this.exitCode = exitCode
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Validate installation options
|
|
25
|
-
* @param {Object} options - Installation options
|
|
26
|
-
* @throws {InstallError} If options are invalid
|
|
27
|
-
*/
|
|
28
|
-
function validateOptions (options) {
|
|
29
|
-
if (!options || typeof options !== 'object') {
|
|
30
|
-
throw new InstallError('Options must be an object', 'INVALID_OPTIONS')
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (options.applicationId !== undefined) {
|
|
34
|
-
const appId = Number(options.applicationId)
|
|
35
|
-
if (isNaN(appId) || appId <= 0 || !Number.isInteger(appId)) {
|
|
36
|
-
throw new InstallError(
|
|
37
|
-
'applicationId must be a positive integer',
|
|
38
|
-
'INVALID_APP_ID'
|
|
39
|
-
)
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (options.workshopId !== undefined) {
|
|
44
|
-
if (!options.applicationId) {
|
|
45
|
-
throw new InstallError(
|
|
46
|
-
'workshopId requires applicationId to be specified',
|
|
47
|
-
'MISSING_APP_ID'
|
|
48
|
-
)
|
|
49
|
-
}
|
|
50
|
-
const workshopId = Number(options.workshopId)
|
|
51
|
-
if (isNaN(workshopId) || workshopId <= 0 || !Number.isInteger(workshopId)) {
|
|
52
|
-
throw new InstallError(
|
|
53
|
-
'workshopId must be a positive integer',
|
|
54
|
-
'INVALID_WORKSHOP_ID'
|
|
55
|
-
)
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (options.platform !== undefined) {
|
|
60
|
-
const validPlatforms = ['windows', 'macos', 'linux']
|
|
61
|
-
if (!validPlatforms.includes(options.platform)) {
|
|
62
|
-
throw new InstallError(
|
|
63
|
-
`platform must be one of: ${validPlatforms.join(', ')}`,
|
|
64
|
-
'INVALID_PLATFORM'
|
|
65
|
-
)
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (options.password && !options.username) {
|
|
70
|
-
throw new InstallError(
|
|
71
|
-
'password requires username to be specified',
|
|
72
|
-
'MISSING_USERNAME'
|
|
73
|
-
)
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (options.steamGuardCode && !options.username) {
|
|
77
|
-
throw new InstallError(
|
|
78
|
-
'steamGuardCode requires username to be specified',
|
|
79
|
-
'MISSING_USERNAME'
|
|
80
|
-
)
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Build SteamCMD command line arguments
|
|
86
|
-
* @param {Object} options - Installation options
|
|
87
|
-
* @returns {string[]} Array of command line arguments
|
|
88
|
-
*/
|
|
89
|
-
function createArguments (options) {
|
|
90
|
-
const args = []
|
|
91
|
-
|
|
92
|
-
// Force platform type for download
|
|
93
|
-
if (options.platform) {
|
|
94
|
-
args.push('+@sSteamCmdForcePlatformType ' + options.platform)
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Use supplied password
|
|
98
|
-
args.push('+@NoPromptForPassword 1')
|
|
99
|
-
|
|
100
|
-
// Quit on fail
|
|
101
|
-
args.push('+@ShutdownOnFailedCommand 1')
|
|
102
|
-
|
|
103
|
-
if (options.steamGuardCode) {
|
|
104
|
-
args.push('+set_steam_guard_code ' + options.steamGuardCode)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Authentication
|
|
108
|
-
if (options.username && options.password) {
|
|
109
|
-
args.push('+login ' + options.username + ' ' + options.password)
|
|
110
|
-
} else if (options.username) {
|
|
111
|
-
args.push('+login ' + options.username)
|
|
112
|
-
} else {
|
|
113
|
-
args.push('+login anonymous')
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Installation directory
|
|
117
|
-
if (options.path) {
|
|
118
|
-
args.push('+force_install_dir "' + options.path + '"')
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// App id to install and/or validate
|
|
122
|
-
if (options.applicationId && !options.workshopId) {
|
|
123
|
-
args.push('+app_update ' + options.applicationId + ' validate')
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Workshop id to install and/or validate
|
|
127
|
-
if (options.applicationId && options.workshopId) {
|
|
128
|
-
args.push(
|
|
129
|
-
'+workshop_download_item ' +
|
|
130
|
-
options.applicationId +
|
|
131
|
-
' ' +
|
|
132
|
-
options.workshopId
|
|
133
|
-
)
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Quit when done
|
|
137
|
-
args.push('+quit')
|
|
138
|
-
|
|
139
|
-
return args
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Parse SteamCMD output for progress information
|
|
144
|
-
* @param {string} data - Raw output from SteamCMD
|
|
145
|
-
* @returns {Object|null} Parsed progress info or null if not progress data
|
|
146
|
-
*/
|
|
147
|
-
function parseProgress (data) {
|
|
148
|
-
const str = data.toString()
|
|
149
|
-
|
|
150
|
-
// Match update/download progress: "Update state (0x61) downloading, progress: 45.23 (1234567890 / 2732853760)"
|
|
151
|
-
const updateMatch = str.match(
|
|
152
|
-
/Update state \(0x[\da-f]+\) (\w+), progress: ([\d.]+) \((\d+) \/ (\d+)\)/i
|
|
153
|
-
)
|
|
154
|
-
if (updateMatch) {
|
|
155
|
-
return {
|
|
156
|
-
phase: updateMatch[1].toLowerCase(),
|
|
157
|
-
percent: Math.round(parseFloat(updateMatch[2])),
|
|
158
|
-
bytesDownloaded: parseInt(updateMatch[3], 10),
|
|
159
|
-
totalBytes: parseInt(updateMatch[4], 10)
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Match validation progress: "Validating: 45%"
|
|
164
|
-
const validateMatch = str.match(/Validating[^\d]*(\d+)%/i)
|
|
165
|
-
if (validateMatch) {
|
|
166
|
-
return {
|
|
167
|
-
phase: 'validating',
|
|
168
|
-
percent: parseInt(validateMatch[1], 10),
|
|
169
|
-
bytesDownloaded: 0,
|
|
170
|
-
totalBytes: 0
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Match download progress: "[#### ] 45%"
|
|
175
|
-
const percentMatch = str.match(/\[(#+\s*)\]\s*(\d+)%/i)
|
|
176
|
-
if (percentMatch) {
|
|
177
|
-
return {
|
|
178
|
-
phase: 'downloading',
|
|
179
|
-
percent: parseInt(percentMatch[2], 10),
|
|
180
|
-
bytesDownloaded: 0,
|
|
181
|
-
totalBytes: 0
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
return null
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Run SteamCMD with the given options
|
|
190
|
-
* @param {string} steamCmdPath - Path to SteamCMD executable
|
|
191
|
-
* @param {Object} options - Installation options
|
|
192
|
-
* @param {Function} [options.onProgress] - Progress callback(progress) with { phase, percent, bytesDownloaded, totalBytes }
|
|
193
|
-
* @param {Function} [options.onOutput] - Output callback(data, type) where type is 'stdout' or 'stderr'
|
|
194
|
-
* @param {Function} [callback] - Optional callback(err). If omitted, returns a Promise.
|
|
195
|
-
* @returns {Promise<void>|undefined} Promise if no callback provided
|
|
196
|
-
*
|
|
197
|
-
* @example
|
|
198
|
-
* // With progress callback
|
|
199
|
-
* await install(execPath, {
|
|
200
|
-
* applicationId: 740,
|
|
201
|
-
* onProgress: (p) => console.log(`${p.phase}: ${p.percent}%`),
|
|
202
|
-
* onOutput: (data, type) => console.log(`[${type}] ${data}`)
|
|
203
|
-
* });
|
|
204
|
-
*/
|
|
205
|
-
function install (steamCmdPath, options, callback) {
|
|
206
|
-
// Support Promise-based usage
|
|
207
|
-
if (typeof callback !== 'function') {
|
|
208
|
-
return new Promise((resolve, reject) => {
|
|
209
|
-
install(steamCmdPath, options, (err) => {
|
|
210
|
-
if (err) reject(err)
|
|
211
|
-
else resolve()
|
|
212
|
-
})
|
|
213
|
-
})
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Validate options
|
|
217
|
-
try {
|
|
218
|
-
validateOptions(options)
|
|
219
|
-
} catch (err) {
|
|
220
|
-
callback(err)
|
|
221
|
-
return
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Validate steamCmdPath
|
|
225
|
-
if (!steamCmdPath || typeof steamCmdPath !== 'string') {
|
|
226
|
-
callback(
|
|
227
|
-
new InstallError(
|
|
228
|
-
'steamCmdPath must be a non-empty string',
|
|
229
|
-
'INVALID_PATH'
|
|
230
|
-
)
|
|
231
|
-
)
|
|
232
|
-
return
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
const onProgress =
|
|
236
|
-
typeof options.onProgress === 'function' ? options.onProgress : () => {}
|
|
237
|
-
const onOutput =
|
|
238
|
-
typeof options.onOutput === 'function' ? options.onOutput : null
|
|
239
|
-
|
|
240
|
-
const proc = childProcess.execFile(steamCmdPath, createArguments(options))
|
|
241
|
-
|
|
242
|
-
let stdoutData = ''
|
|
243
|
-
let stderrData = ''
|
|
244
|
-
|
|
245
|
-
onProgress({
|
|
246
|
-
phase: 'starting',
|
|
247
|
-
percent: 0,
|
|
248
|
-
bytesDownloaded: 0,
|
|
249
|
-
totalBytes: 0
|
|
250
|
-
})
|
|
251
|
-
|
|
252
|
-
proc.stdout.on('data', (data) => {
|
|
253
|
-
stdoutData += data
|
|
254
|
-
if (onOutput) {
|
|
255
|
-
onOutput(data.toString(), 'stdout')
|
|
256
|
-
} else {
|
|
257
|
-
console.log('stdout: ' + data)
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Parse progress from output
|
|
261
|
-
const progress = parseProgress(data)
|
|
262
|
-
if (progress) {
|
|
263
|
-
onProgress(progress)
|
|
264
|
-
}
|
|
265
|
-
})
|
|
266
|
-
|
|
267
|
-
proc.stderr.on('data', (data) => {
|
|
268
|
-
stderrData += data
|
|
269
|
-
if (onOutput) {
|
|
270
|
-
onOutput(data.toString(), 'stderr')
|
|
271
|
-
} else {
|
|
272
|
-
console.log('stderr: ' + data)
|
|
273
|
-
}
|
|
274
|
-
})
|
|
275
|
-
|
|
276
|
-
proc.on('error', (err) => {
|
|
277
|
-
callback(
|
|
278
|
-
new InstallError(
|
|
279
|
-
`Failed to spawn SteamCMD: ${err.message}`,
|
|
280
|
-
'SPAWN_ERROR'
|
|
281
|
-
)
|
|
282
|
-
)
|
|
283
|
-
})
|
|
284
|
-
|
|
285
|
-
proc.on('close', (code) => {
|
|
286
|
-
if (onOutput) {
|
|
287
|
-
onOutput(`Process exited with code ${code}\n`, 'stdout')
|
|
288
|
-
} else {
|
|
289
|
-
console.log('child process exited with code ' + code)
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
if (code > 0) {
|
|
293
|
-
const err = new InstallError(
|
|
294
|
-
`SteamCMD exited with code ${code}`,
|
|
295
|
-
'EXIT_ERROR',
|
|
296
|
-
code
|
|
297
|
-
)
|
|
298
|
-
err.stdout = stdoutData
|
|
299
|
-
err.stderr = stderrData
|
|
300
|
-
callback(err)
|
|
301
|
-
} else {
|
|
302
|
-
onProgress({
|
|
303
|
-
phase: 'complete',
|
|
304
|
-
percent: 100,
|
|
305
|
-
bytesDownloaded: 0,
|
|
306
|
-
totalBytes: 0
|
|
307
|
-
})
|
|
308
|
-
callback(null)
|
|
309
|
-
}
|
|
310
|
-
})
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* Run SteamCMD with EventEmitter-based progress
|
|
315
|
-
* @param {string} steamCmdPath - Path to SteamCMD executable
|
|
316
|
-
* @param {Object} options - Installation options
|
|
317
|
-
* @returns {EventEmitter} Emitter that fires 'progress', 'output', 'error', and 'complete' events
|
|
318
|
-
*
|
|
319
|
-
* @example
|
|
320
|
-
* const emitter = installWithProgress(execPath, { applicationId: 740 });
|
|
321
|
-
* emitter.on('progress', (p) => console.log(`${p.percent}%`));
|
|
322
|
-
* emitter.on('output', (data, type) => console.log(`[${type}] ${data}`));
|
|
323
|
-
* emitter.on('complete', () => console.log('Done!'));
|
|
324
|
-
* emitter.on('error', (err) => console.error(err));
|
|
325
|
-
*/
|
|
326
|
-
function installWithProgress (steamCmdPath, options) {
|
|
327
|
-
const emitter = new EventEmitter()
|
|
328
|
-
|
|
329
|
-
// Run install in next tick to allow event binding
|
|
330
|
-
process.nextTick(() => {
|
|
331
|
-
install(
|
|
332
|
-
steamCmdPath,
|
|
333
|
-
{
|
|
334
|
-
...options,
|
|
335
|
-
onProgress: (progress) => emitter.emit('progress', progress),
|
|
336
|
-
onOutput: (data, type) => emitter.emit('output', data, type)
|
|
337
|
-
},
|
|
338
|
-
(err) => {
|
|
339
|
-
if (err) {
|
|
340
|
-
emitter.emit('error', err)
|
|
341
|
-
} else {
|
|
342
|
-
emitter.emit('complete')
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
)
|
|
346
|
-
})
|
|
347
|
-
|
|
348
|
-
return emitter
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
module.exports = install
|
|
352
|
-
module.exports.installWithProgress = installWithProgress
|
|
353
|
-
module.exports.createArguments = createArguments
|
|
354
|
-
module.exports.validateOptions = validateOptions
|
|
355
|
-
module.exports.parseProgress = parseProgress
|
|
356
|
-
module.exports.InstallError = InstallError
|