@pipelab/plugin-steam 1.0.0-beta.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 +12 -0
- package/LICENSE +110 -0
- package/README.md +3 -0
- package/dist/index.cjs +273 -0
- package/dist/index.d.cts +4 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +275 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +48 -0
- package/src/declarations.d.ts +1 -0
- package/src/index.ts +19 -0
- package/src/steam.webp +0 -0
- package/src/upload-to-steam.ts +299 -0
- package/src/utils.ts +125 -0
- package/tests/e2e/steam.spec.ts +103 -0
- package/tests/e2e/vitest.config.mts +20 -0
- package/tsconfig.json +8 -0
- package/tsdown.config.ts +15 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/utils.ts","../src/upload-to-steam.ts","../src/index.ts"],"sourcesContent":["import { runWithLiveLogs } from \"@pipelab/plugin-core\";\nimport { execa, Options as ExecaOptions } from \"execa\";\nimport os from \"node:os\";\n\nexport type Options = {\n steamcmdPath: string;\n username: string;\n scriptPath: string;\n context: {\n log: typeof console.log;\n abortSignal: AbortSignal;\n };\n};\n\nexport const checkSteamAuth = async (options: Options) => {\n let error: \"LOGGED_OUT\" | \"UNKNOWN\" | undefined = undefined;\n\n try {\n await runWithLiveLogs(\n options.steamcmdPath,\n [\"+login\", options.username, \"+quit\"],\n {\n shell: process.platform === \"win32\",\n },\n options.context.log,\n {\n onStdout: (data, subprocess) => {\n options.context.log(\"[Steam Cmd]\", data);\n // TODO: handle password input dynamically\n if (data.includes(\"Cached credentials not found\")) {\n error = \"LOGGED_OUT\";\n\n subprocess.kill();\n }\n },\n },\n options.context.abortSignal,\n );\n } catch (e) {\n console.error(\"e\", e);\n if (!error) {\n error = \"UNKNOWN\";\n }\n }\n\n if (error) {\n return {\n success: false,\n error,\n };\n }\n\n return {\n success: true,\n };\n};\n\nexport const openExternalTerminal = async (\n command: string,\n args: string[] = [],\n options: ExecaOptions = {},\n keepOpen = false,\n) => {\n const platform = process.platform;\n\n if (platform === \"darwin\") {\n // macOS: open in Terminal.app\n const shellCommand = `${command} ${args.join(\" \")}; echo \"You can now close this window\"`;\n // Escape for AppleScript string literal\n const escapedShellCommand = shellCommand\n .replace(/\\\\/g, \"\\\\\\\\\") // Must escape backslashes first\n .replace(/\"/g, '\\\\\"'); // Then escape double quotes\n\n let osaScript: string;\n if (keepOpen) {\n // If keepOpen is true, just run the command and leave the terminal open.\n // Added activate to bring Terminal to front.\n osaScript = `tell application \"Terminal\"\\nactivate\\ndo script \"${escapedShellCommand}\"\\nend tell`;\n } else {\n // If keepOpen is false (default), run the command, wait for it to finish, then close the tab.\n osaScript = `tell application \"Terminal\"\n activate\n set targetTab to do script \"${escapedShellCommand}\"\n -- Wait for the command to complete by checking the 'busy' status of the tab\n delay 0.5 -- Initial delay to allow the process to start and tab to become busy\n repeat while busy of targetTab\n delay 0.5 -- Check every 0.5 seconds\n end repeat\nend tell`;\n }\n\n return execa(\"osascript\", [\"-e\", osaScript], options);\n } else if (platform === \"linux\") {\n // Linux: use $TERMINAL, $TERM, or fallback to xterm\n const terminal = process.env.TERMINAL ?? process.env.TERM ?? \"xterm\";\n return execa(terminal, [\"-e\", command, ...args], options);\n } else if (platform === \"win32\") {\n // Windows: try PowerShell by default, fallback to CMD if needed\n // try {\n // console.log('verifying')\n // // Try a harmless PowerShell command. We ignore stdio.\n // await execa(\n // 'powershell.exe',\n // ['-Command', 'Write-Output \"PowerShell available\"; exit'],\n // {\n // all: true\n // }\n // )\n // return execa(\n // 'cmd.exe',\n // [keepOpen ? '/k' : '/c', 'start', 'powershell.exe', '-Command', command, ...args],\n // options\n // )\n // } catch (error) {\n // Oops! No PowerShell? Fallback to CMD.\n return execa(\n \"cmd.exe\",\n [keepOpen ? \"/k\" : \"/c\", \"start\", \"cmd.exe\", \"/c\", command, ...args],\n options,\n );\n // }\n } else {\n throw new Error(\"Unsupported platform: \" + platform);\n }\n};\n","import { join, dirname, basename } from \"node:path\";\nimport { platform } from \"node:os\";\nimport { chmod, mkdir, writeFile, cp } from \"node:fs/promises\";\nimport {\n createAction,\n createActionRunner,\n createPathParam,\n createStringParam,\n fileExists,\n runWithLiveLogs,\n} from \"@pipelab/plugin-core\";\nimport { checkSteamAuth, openExternalTerminal } from \"./utils\";\nimport { ExternalCommandError } from \"@pipelab/plugin-core\";\n\n// https://github.com/ztgasdf/steampkg?tab=readme-ov-file#account-management\n\n// How to login\n// Do it at least once\n// sdk/tools/ContentBuilder/builder_linux/steamcmd.sh +login User +quit\n\nexport const ID = \"steam-upload\";\n\nexport const uploadToSteam = createAction({\n id: ID,\n name: \"Upload to Steam\",\n description: \"Upload a folder to Steam\",\n icon: \"\",\n displayString: \"`Upload ${fmt.param(params['folder'], 'primary')} to steam`\",\n meta: {},\n params: {\n sdk: createPathParam(\"\", {\n required: true,\n label: \"Steam Sdk path\",\n control: {\n type: \"path\",\n options: {\n properties: [\"openDirectory\"],\n },\n },\n }),\n username: createStringParam(\"\", {\n required: true,\n label: \"Steam username\",\n }),\n appId: createStringParam(\"\", {\n required: true,\n label: \"App Id\",\n }),\n depotId: createStringParam(\"\", {\n required: true,\n label: \"Depot Id\",\n }),\n description: createStringParam(\"\", {\n required: true,\n label: \"Description\",\n }),\n folder: createPathParam(\"\", {\n required: true,\n label: \"Folder to upload\",\n control: {\n type: \"path\",\n options: {\n properties: [\"openDirectory\"],\n },\n },\n }),\n // enableDRM: {\n // value: false,\n // label: 'Enable DRM',\n // control: {\n // type: 'boolean'\n // }\n // },\n // binaryToPatch: createPathParam('', {\n // label: 'Binary to patch',\n // control: {\n // type: 'path',\n // options: {\n // properties: ['openFile']\n // }\n // }\n // })\n },\n outputs: {\n \"script-path\": {\n label: \"Script path\",\n value: \"\",\n },\n \"output-folder\": {\n label: \"Output folder\",\n value: \"\",\n },\n status: {\n label: \"Status\",\n value: \"\",\n },\n },\n});\n\nexport const uploadToSteamRunner = createActionRunner<typeof uploadToSteam>(\n async ({ log, inputs, cwd, abortSignal, setOutput }) => {\n const folder = inputs.folder as string;\n const appId = inputs.appId as string;\n const sdk = inputs.sdk as string;\n const depotId = inputs.depotId as string;\n const username = inputs.username as string;\n const description = inputs.description as string;\n\n log(`uploading \"${folder}\" to steam`);\n\n const errorMap = {\n 6: `No connection to content server. Your depot id (${depotId}) may be invalid`,\n };\n\n const isSDKExisting = await fileExists(sdk);\n if (!isSDKExisting) {\n throw new Error(`You must enter a valid path to the Steam SDK`);\n }\n\n let builderFolder = \"builder\";\n if (platform() === \"linux\") {\n builderFolder += \"_linux\";\n } else if (platform() === \"darwin\") {\n builderFolder += \"_osx\";\n }\n\n const cmd = \"steamcmd\";\n const extensions = platform() === \"win32\" ? [\".exe\", \".cmd\", \".bat\"] : [\".sh\"];\n\n let cmdFinal = \"\";\n let steamcmdPath = \"\";\n\n for (const ext of extensions) {\n const p = join(sdk as string, \"tools\", \"ContentBuilder\", builderFolder, cmd + ext);\n if (await fileExists(p)) {\n steamcmdPath = p;\n cmdFinal = cmd + ext;\n break;\n }\n }\n\n // Fallback if none found (to maintain previous behavior of joining default)\n if (!steamcmdPath) {\n if (platform() === \"linux\" || platform() === \"darwin\") {\n cmdFinal = \"steamcmd.sh\";\n } else if (platform() === \"win32\") {\n cmdFinal = \"steamcmd.exe\";\n }\n steamcmdPath = join(sdk as string, \"tools\", \"ContentBuilder\", builderFolder, cmdFinal);\n }\n\n console.log(\"steamcmdPath\", steamcmdPath);\n\n if (platform() === \"linux\" || platform() === \"darwin\") {\n if (platform() === \"linux\") {\n log('Adding \"execute\" permissions to linux binary');\n const steamcmdBinaryPath = join(\n sdk,\n \"tools\",\n \"ContentBuilder\",\n builderFolder,\n \"linux32\",\n cmd,\n );\n await chmod(steamcmdBinaryPath, 0o755);\n const steamcmdBinaryErrorReporterPath = join(\n sdk,\n \"tools\",\n \"ContentBuilder\",\n builderFolder,\n \"linux32\",\n \"steamerrorreporter\",\n );\n await chmod(steamcmdBinaryErrorReporterPath, 0o755);\n }\n\n if (platform() === \"darwin\") {\n const steamcmdBinaryPath = join(sdk, \"tools\", \"ContentBuilder\", builderFolder, cmd);\n log('Adding \"execute\" permissions to darwin binary');\n await chmod(steamcmdBinaryPath, 0o755);\n }\n\n log('Adding \"execute\" permissions to binary');\n await chmod(steamcmdPath, 0o755);\n }\n\n const buildOutput = join(cwd, \"steam\", \"output\");\n const scriptPath = join(cwd, \"steam\", \"script.vdf\");\n\n setOutput(\"script-path\", scriptPath);\n setOutput(\"output-folder\", buildOutput);\n\n await mkdir(buildOutput, {\n recursive: true,\n });\n\n await mkdir(dirname(scriptPath), {\n recursive: true,\n });\n\n const script = `\"AppBuild\"\n{\n\t\"AppID\" \"${appId}\" // your AppID\n\t\"Desc\" \"${description}\" // internal description for this build\n\n\t\"ContentRoot\" \"${folder}\" // root content folder, relative to location of this file\n\t\"BuildOutput\" \"${buildOutput}\" // build output folder for build logs and build cache files\n\n\t\"Depots\"\n\t{\n\t\t\"${depotId}\" // your DepotID\n\t\t{\n\t\t\t\"FileMapping\"\n\t\t\t{\n\t\t\t\t\"LocalPath\" \"*\" // all files from contentroot folder\n\t\t\t\t\"DepotPath\" \".\" // mapped into the root of the depot\n\t\t\t\t\"recursive\" \"1\" // include all subfolders\n\t\t\t}\n\t\t}\n\t}\n}`;\n\n console.log(\"script\", script);\n const isAuthenticated = await checkSteamAuth({\n context: {\n log,\n abortSignal,\n },\n scriptPath,\n steamcmdPath,\n username,\n });\n\n log(\"isAuthenticated\", JSON.stringify(isAuthenticated));\n\n if (isAuthenticated.success === false) {\n log(\"Opening terminal with interactive login\");\n await openExternalTerminal(steamcmdPath, [\"+login\", username, \"+quit\"], {\n cancelSignal: abortSignal,\n });\n const isAuthenticatedNow = await checkSteamAuth({\n context: {\n log,\n abortSignal,\n },\n scriptPath,\n steamcmdPath,\n username,\n });\n if (isAuthenticatedNow.success === false) {\n throw new Error(\"Not authenticated\");\n }\n }\n\n log(\"Writing script\");\n await writeFile(scriptPath, script, {\n encoding: \"utf8\",\n signal: abortSignal,\n });\n\n log(\"Executing steamcmd\");\n\n // Should be authed here\n try {\n await runWithLiveLogs(\n steamcmdPath,\n [\"+login\", username, \"+run_app_build\", scriptPath, \"+quit\"],\n {\n shell: platform() === \"win32\",\n },\n log,\n {\n onStdout: (data) => {\n log(\"[steamcmd]\", data);\n },\n onStderr: (data) => {\n log(\"[steamcmd]\", data);\n },\n },\n abortSignal,\n );\n } catch (e) {\n if (e instanceof ExternalCommandError) {\n const code = e.code as keyof typeof errorMap;\n const message =\n code in errorMap ? errorMap[code] : \"SteamCmd error:\" + e.code + \" \" + e.message;\n throw new Error(message);\n } else if (e instanceof Error) {\n console.error(e);\n throw new Error(\"Error:\" + e.message);\n } else {\n throw new Error(\"unknwon error\");\n }\n }\n\n setOutput(\"status\", \"success\");\n log(\"Done uploading\");\n },\n);\n","import { uploadToSteam, uploadToSteamRunner } from \"./upload-to-steam\";\nimport { createNodeDefinition } from \"@pipelab/plugin-core\";\nconst icon = new URL(\"./steam.webp\", import.meta.url).href;\n\nexport default createNodeDefinition({\n description: \"Steam\",\n id: \"steam\",\n name: \"Steam\",\n icon: {\n type: \"image\",\n image: icon,\n },\n nodes: [\n {\n node: uploadToSteam,\n runner: uploadToSteamRunner,\n },\n ],\n});\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pipelab/plugin-steam",
|
|
3
|
+
"version": "1.0.0-beta.0",
|
|
4
|
+
"license": "FSL-1.1-MIT",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/CynToolkit/pipelab.git",
|
|
8
|
+
"directory": "plugins/plugin-steam"
|
|
9
|
+
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"main": "./dist/index.cjs",
|
|
12
|
+
"module": "./dist/index.mjs",
|
|
13
|
+
"types": "./dist/index.d.mts",
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@types/node": "^24.12.2",
|
|
19
|
+
"execa": "9.4.1",
|
|
20
|
+
"@pipelab/plugin-core": "1.0.0-beta.0"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"tsdown": "0.21.2",
|
|
24
|
+
"typescript": "5.9.3",
|
|
25
|
+
"vitest": "3.1.4",
|
|
26
|
+
"@pipelab/test-utils": "1.0.0-beta.0",
|
|
27
|
+
"@pipelab/tsconfig": "1.0.0-beta.0"
|
|
28
|
+
},
|
|
29
|
+
"test": {
|
|
30
|
+
"env": {
|
|
31
|
+
"NODE_ENV": "development"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"format": "oxfmt .",
|
|
36
|
+
"lint": "oxlint .",
|
|
37
|
+
"build": "tsdown",
|
|
38
|
+
"test": "vitest run -c tests/e2e/vitest.config.mts"
|
|
39
|
+
},
|
|
40
|
+
"exports": {
|
|
41
|
+
".": {
|
|
42
|
+
"types": "./dist/index.d.mts",
|
|
43
|
+
"import": "./dist/index.mjs",
|
|
44
|
+
"require": "./dist/index.cjs",
|
|
45
|
+
"default": "./dist/index.mjs"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
declare module "*.webp";
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { uploadToSteam, uploadToSteamRunner } from "./upload-to-steam";
|
|
2
|
+
import { createNodeDefinition } from "@pipelab/plugin-core";
|
|
3
|
+
const icon = new URL("./steam.webp", import.meta.url).href;
|
|
4
|
+
|
|
5
|
+
export default createNodeDefinition({
|
|
6
|
+
description: "Steam",
|
|
7
|
+
id: "steam",
|
|
8
|
+
name: "Steam",
|
|
9
|
+
icon: {
|
|
10
|
+
type: "image",
|
|
11
|
+
image: icon,
|
|
12
|
+
},
|
|
13
|
+
nodes: [
|
|
14
|
+
{
|
|
15
|
+
node: uploadToSteam,
|
|
16
|
+
runner: uploadToSteamRunner,
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
});
|
package/src/steam.webp
ADDED
|
Binary file
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { join, dirname, basename } from "node:path";
|
|
2
|
+
import { platform } from "node:os";
|
|
3
|
+
import { chmod, mkdir, writeFile, cp } from "node:fs/promises";
|
|
4
|
+
import {
|
|
5
|
+
createAction,
|
|
6
|
+
createActionRunner,
|
|
7
|
+
createPathParam,
|
|
8
|
+
createStringParam,
|
|
9
|
+
fileExists,
|
|
10
|
+
runWithLiveLogs,
|
|
11
|
+
} from "@pipelab/plugin-core";
|
|
12
|
+
import { checkSteamAuth, openExternalTerminal } from "./utils";
|
|
13
|
+
import { ExternalCommandError } from "@pipelab/plugin-core";
|
|
14
|
+
|
|
15
|
+
// https://github.com/ztgasdf/steampkg?tab=readme-ov-file#account-management
|
|
16
|
+
|
|
17
|
+
// How to login
|
|
18
|
+
// Do it at least once
|
|
19
|
+
// sdk/tools/ContentBuilder/builder_linux/steamcmd.sh +login User +quit
|
|
20
|
+
|
|
21
|
+
export const ID = "steam-upload";
|
|
22
|
+
|
|
23
|
+
export const uploadToSteam = createAction({
|
|
24
|
+
id: ID,
|
|
25
|
+
name: "Upload to Steam",
|
|
26
|
+
description: "Upload a folder to Steam",
|
|
27
|
+
icon: "",
|
|
28
|
+
displayString: "`Upload ${fmt.param(params['folder'], 'primary')} to steam`",
|
|
29
|
+
meta: {},
|
|
30
|
+
params: {
|
|
31
|
+
sdk: createPathParam("", {
|
|
32
|
+
required: true,
|
|
33
|
+
label: "Steam Sdk path",
|
|
34
|
+
control: {
|
|
35
|
+
type: "path",
|
|
36
|
+
options: {
|
|
37
|
+
properties: ["openDirectory"],
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
}),
|
|
41
|
+
username: createStringParam("", {
|
|
42
|
+
required: true,
|
|
43
|
+
label: "Steam username",
|
|
44
|
+
}),
|
|
45
|
+
appId: createStringParam("", {
|
|
46
|
+
required: true,
|
|
47
|
+
label: "App Id",
|
|
48
|
+
}),
|
|
49
|
+
depotId: createStringParam("", {
|
|
50
|
+
required: true,
|
|
51
|
+
label: "Depot Id",
|
|
52
|
+
}),
|
|
53
|
+
description: createStringParam("", {
|
|
54
|
+
required: true,
|
|
55
|
+
label: "Description",
|
|
56
|
+
}),
|
|
57
|
+
folder: createPathParam("", {
|
|
58
|
+
required: true,
|
|
59
|
+
label: "Folder to upload",
|
|
60
|
+
control: {
|
|
61
|
+
type: "path",
|
|
62
|
+
options: {
|
|
63
|
+
properties: ["openDirectory"],
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
}),
|
|
67
|
+
// enableDRM: {
|
|
68
|
+
// value: false,
|
|
69
|
+
// label: 'Enable DRM',
|
|
70
|
+
// control: {
|
|
71
|
+
// type: 'boolean'
|
|
72
|
+
// }
|
|
73
|
+
// },
|
|
74
|
+
// binaryToPatch: createPathParam('', {
|
|
75
|
+
// label: 'Binary to patch',
|
|
76
|
+
// control: {
|
|
77
|
+
// type: 'path',
|
|
78
|
+
// options: {
|
|
79
|
+
// properties: ['openFile']
|
|
80
|
+
// }
|
|
81
|
+
// }
|
|
82
|
+
// })
|
|
83
|
+
},
|
|
84
|
+
outputs: {
|
|
85
|
+
"script-path": {
|
|
86
|
+
label: "Script path",
|
|
87
|
+
value: "",
|
|
88
|
+
},
|
|
89
|
+
"output-folder": {
|
|
90
|
+
label: "Output folder",
|
|
91
|
+
value: "",
|
|
92
|
+
},
|
|
93
|
+
status: {
|
|
94
|
+
label: "Status",
|
|
95
|
+
value: "",
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
export const uploadToSteamRunner = createActionRunner<typeof uploadToSteam>(
|
|
101
|
+
async ({ log, inputs, cwd, abortSignal, setOutput }) => {
|
|
102
|
+
const folder = inputs.folder as string;
|
|
103
|
+
const appId = inputs.appId as string;
|
|
104
|
+
const sdk = inputs.sdk as string;
|
|
105
|
+
const depotId = inputs.depotId as string;
|
|
106
|
+
const username = inputs.username as string;
|
|
107
|
+
const description = inputs.description as string;
|
|
108
|
+
|
|
109
|
+
log(`uploading "${folder}" to steam`);
|
|
110
|
+
|
|
111
|
+
const errorMap = {
|
|
112
|
+
6: `No connection to content server. Your depot id (${depotId}) may be invalid`,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const isSDKExisting = await fileExists(sdk);
|
|
116
|
+
if (!isSDKExisting) {
|
|
117
|
+
throw new Error(`You must enter a valid path to the Steam SDK`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let builderFolder = "builder";
|
|
121
|
+
if (platform() === "linux") {
|
|
122
|
+
builderFolder += "_linux";
|
|
123
|
+
} else if (platform() === "darwin") {
|
|
124
|
+
builderFolder += "_osx";
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const cmd = "steamcmd";
|
|
128
|
+
const extensions = platform() === "win32" ? [".exe", ".cmd", ".bat"] : [".sh"];
|
|
129
|
+
|
|
130
|
+
let cmdFinal = "";
|
|
131
|
+
let steamcmdPath = "";
|
|
132
|
+
|
|
133
|
+
for (const ext of extensions) {
|
|
134
|
+
const p = join(sdk as string, "tools", "ContentBuilder", builderFolder, cmd + ext);
|
|
135
|
+
if (await fileExists(p)) {
|
|
136
|
+
steamcmdPath = p;
|
|
137
|
+
cmdFinal = cmd + ext;
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Fallback if none found (to maintain previous behavior of joining default)
|
|
143
|
+
if (!steamcmdPath) {
|
|
144
|
+
if (platform() === "linux" || platform() === "darwin") {
|
|
145
|
+
cmdFinal = "steamcmd.sh";
|
|
146
|
+
} else if (platform() === "win32") {
|
|
147
|
+
cmdFinal = "steamcmd.exe";
|
|
148
|
+
}
|
|
149
|
+
steamcmdPath = join(sdk as string, "tools", "ContentBuilder", builderFolder, cmdFinal);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
console.log("steamcmdPath", steamcmdPath);
|
|
153
|
+
|
|
154
|
+
if (platform() === "linux" || platform() === "darwin") {
|
|
155
|
+
if (platform() === "linux") {
|
|
156
|
+
log('Adding "execute" permissions to linux binary');
|
|
157
|
+
const steamcmdBinaryPath = join(
|
|
158
|
+
sdk,
|
|
159
|
+
"tools",
|
|
160
|
+
"ContentBuilder",
|
|
161
|
+
builderFolder,
|
|
162
|
+
"linux32",
|
|
163
|
+
cmd,
|
|
164
|
+
);
|
|
165
|
+
await chmod(steamcmdBinaryPath, 0o755);
|
|
166
|
+
const steamcmdBinaryErrorReporterPath = join(
|
|
167
|
+
sdk,
|
|
168
|
+
"tools",
|
|
169
|
+
"ContentBuilder",
|
|
170
|
+
builderFolder,
|
|
171
|
+
"linux32",
|
|
172
|
+
"steamerrorreporter",
|
|
173
|
+
);
|
|
174
|
+
await chmod(steamcmdBinaryErrorReporterPath, 0o755);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (platform() === "darwin") {
|
|
178
|
+
const steamcmdBinaryPath = join(sdk, "tools", "ContentBuilder", builderFolder, cmd);
|
|
179
|
+
log('Adding "execute" permissions to darwin binary');
|
|
180
|
+
await chmod(steamcmdBinaryPath, 0o755);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
log('Adding "execute" permissions to binary');
|
|
184
|
+
await chmod(steamcmdPath, 0o755);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const buildOutput = join(cwd, "steam", "output");
|
|
188
|
+
const scriptPath = join(cwd, "steam", "script.vdf");
|
|
189
|
+
|
|
190
|
+
setOutput("script-path", scriptPath);
|
|
191
|
+
setOutput("output-folder", buildOutput);
|
|
192
|
+
|
|
193
|
+
await mkdir(buildOutput, {
|
|
194
|
+
recursive: true,
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
await mkdir(dirname(scriptPath), {
|
|
198
|
+
recursive: true,
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const script = `"AppBuild"
|
|
202
|
+
{
|
|
203
|
+
"AppID" "${appId}" // your AppID
|
|
204
|
+
"Desc" "${description}" // internal description for this build
|
|
205
|
+
|
|
206
|
+
"ContentRoot" "${folder}" // root content folder, relative to location of this file
|
|
207
|
+
"BuildOutput" "${buildOutput}" // build output folder for build logs and build cache files
|
|
208
|
+
|
|
209
|
+
"Depots"
|
|
210
|
+
{
|
|
211
|
+
"${depotId}" // your DepotID
|
|
212
|
+
{
|
|
213
|
+
"FileMapping"
|
|
214
|
+
{
|
|
215
|
+
"LocalPath" "*" // all files from contentroot folder
|
|
216
|
+
"DepotPath" "." // mapped into the root of the depot
|
|
217
|
+
"recursive" "1" // include all subfolders
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}`;
|
|
222
|
+
|
|
223
|
+
console.log("script", script);
|
|
224
|
+
const isAuthenticated = await checkSteamAuth({
|
|
225
|
+
context: {
|
|
226
|
+
log,
|
|
227
|
+
abortSignal,
|
|
228
|
+
},
|
|
229
|
+
scriptPath,
|
|
230
|
+
steamcmdPath,
|
|
231
|
+
username,
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
log("isAuthenticated", JSON.stringify(isAuthenticated));
|
|
235
|
+
|
|
236
|
+
if (isAuthenticated.success === false) {
|
|
237
|
+
log("Opening terminal with interactive login");
|
|
238
|
+
await openExternalTerminal(steamcmdPath, ["+login", username, "+quit"], {
|
|
239
|
+
cancelSignal: abortSignal,
|
|
240
|
+
});
|
|
241
|
+
const isAuthenticatedNow = await checkSteamAuth({
|
|
242
|
+
context: {
|
|
243
|
+
log,
|
|
244
|
+
abortSignal,
|
|
245
|
+
},
|
|
246
|
+
scriptPath,
|
|
247
|
+
steamcmdPath,
|
|
248
|
+
username,
|
|
249
|
+
});
|
|
250
|
+
if (isAuthenticatedNow.success === false) {
|
|
251
|
+
throw new Error("Not authenticated");
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
log("Writing script");
|
|
256
|
+
await writeFile(scriptPath, script, {
|
|
257
|
+
encoding: "utf8",
|
|
258
|
+
signal: abortSignal,
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
log("Executing steamcmd");
|
|
262
|
+
|
|
263
|
+
// Should be authed here
|
|
264
|
+
try {
|
|
265
|
+
await runWithLiveLogs(
|
|
266
|
+
steamcmdPath,
|
|
267
|
+
["+login", username, "+run_app_build", scriptPath, "+quit"],
|
|
268
|
+
{
|
|
269
|
+
shell: platform() === "win32",
|
|
270
|
+
},
|
|
271
|
+
log,
|
|
272
|
+
{
|
|
273
|
+
onStdout: (data) => {
|
|
274
|
+
log("[steamcmd]", data);
|
|
275
|
+
},
|
|
276
|
+
onStderr: (data) => {
|
|
277
|
+
log("[steamcmd]", data);
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
abortSignal,
|
|
281
|
+
);
|
|
282
|
+
} catch (e) {
|
|
283
|
+
if (e instanceof ExternalCommandError) {
|
|
284
|
+
const code = e.code as keyof typeof errorMap;
|
|
285
|
+
const message =
|
|
286
|
+
code in errorMap ? errorMap[code] : "SteamCmd error:" + e.code + " " + e.message;
|
|
287
|
+
throw new Error(message);
|
|
288
|
+
} else if (e instanceof Error) {
|
|
289
|
+
console.error(e);
|
|
290
|
+
throw new Error("Error:" + e.message);
|
|
291
|
+
} else {
|
|
292
|
+
throw new Error("unknwon error");
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
setOutput("status", "success");
|
|
297
|
+
log("Done uploading");
|
|
298
|
+
},
|
|
299
|
+
);
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { runWithLiveLogs } from "@pipelab/plugin-core";
|
|
2
|
+
import { execa, Options as ExecaOptions } from "execa";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
|
|
5
|
+
export type Options = {
|
|
6
|
+
steamcmdPath: string;
|
|
7
|
+
username: string;
|
|
8
|
+
scriptPath: string;
|
|
9
|
+
context: {
|
|
10
|
+
log: typeof console.log;
|
|
11
|
+
abortSignal: AbortSignal;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const checkSteamAuth = async (options: Options) => {
|
|
16
|
+
let error: "LOGGED_OUT" | "UNKNOWN" | undefined = undefined;
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
await runWithLiveLogs(
|
|
20
|
+
options.steamcmdPath,
|
|
21
|
+
["+login", options.username, "+quit"],
|
|
22
|
+
{
|
|
23
|
+
shell: process.platform === "win32",
|
|
24
|
+
},
|
|
25
|
+
options.context.log,
|
|
26
|
+
{
|
|
27
|
+
onStdout: (data, subprocess) => {
|
|
28
|
+
options.context.log("[Steam Cmd]", data);
|
|
29
|
+
// TODO: handle password input dynamically
|
|
30
|
+
if (data.includes("Cached credentials not found")) {
|
|
31
|
+
error = "LOGGED_OUT";
|
|
32
|
+
|
|
33
|
+
subprocess.kill();
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
options.context.abortSignal,
|
|
38
|
+
);
|
|
39
|
+
} catch (e) {
|
|
40
|
+
console.error("e", e);
|
|
41
|
+
if (!error) {
|
|
42
|
+
error = "UNKNOWN";
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (error) {
|
|
47
|
+
return {
|
|
48
|
+
success: false,
|
|
49
|
+
error,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
success: true,
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const openExternalTerminal = async (
|
|
59
|
+
command: string,
|
|
60
|
+
args: string[] = [],
|
|
61
|
+
options: ExecaOptions = {},
|
|
62
|
+
keepOpen = false,
|
|
63
|
+
) => {
|
|
64
|
+
const platform = process.platform;
|
|
65
|
+
|
|
66
|
+
if (platform === "darwin") {
|
|
67
|
+
// macOS: open in Terminal.app
|
|
68
|
+
const shellCommand = `${command} ${args.join(" ")}; echo "You can now close this window"`;
|
|
69
|
+
// Escape for AppleScript string literal
|
|
70
|
+
const escapedShellCommand = shellCommand
|
|
71
|
+
.replace(/\\/g, "\\\\") // Must escape backslashes first
|
|
72
|
+
.replace(/"/g, '\\"'); // Then escape double quotes
|
|
73
|
+
|
|
74
|
+
let osaScript: string;
|
|
75
|
+
if (keepOpen) {
|
|
76
|
+
// If keepOpen is true, just run the command and leave the terminal open.
|
|
77
|
+
// Added activate to bring Terminal to front.
|
|
78
|
+
osaScript = `tell application "Terminal"\nactivate\ndo script "${escapedShellCommand}"\nend tell`;
|
|
79
|
+
} else {
|
|
80
|
+
// If keepOpen is false (default), run the command, wait for it to finish, then close the tab.
|
|
81
|
+
osaScript = `tell application "Terminal"
|
|
82
|
+
activate
|
|
83
|
+
set targetTab to do script "${escapedShellCommand}"
|
|
84
|
+
-- Wait for the command to complete by checking the 'busy' status of the tab
|
|
85
|
+
delay 0.5 -- Initial delay to allow the process to start and tab to become busy
|
|
86
|
+
repeat while busy of targetTab
|
|
87
|
+
delay 0.5 -- Check every 0.5 seconds
|
|
88
|
+
end repeat
|
|
89
|
+
end tell`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return execa("osascript", ["-e", osaScript], options);
|
|
93
|
+
} else if (platform === "linux") {
|
|
94
|
+
// Linux: use $TERMINAL, $TERM, or fallback to xterm
|
|
95
|
+
const terminal = process.env.TERMINAL ?? process.env.TERM ?? "xterm";
|
|
96
|
+
return execa(terminal, ["-e", command, ...args], options);
|
|
97
|
+
} else if (platform === "win32") {
|
|
98
|
+
// Windows: try PowerShell by default, fallback to CMD if needed
|
|
99
|
+
// try {
|
|
100
|
+
// console.log('verifying')
|
|
101
|
+
// // Try a harmless PowerShell command. We ignore stdio.
|
|
102
|
+
// await execa(
|
|
103
|
+
// 'powershell.exe',
|
|
104
|
+
// ['-Command', 'Write-Output "PowerShell available"; exit'],
|
|
105
|
+
// {
|
|
106
|
+
// all: true
|
|
107
|
+
// }
|
|
108
|
+
// )
|
|
109
|
+
// return execa(
|
|
110
|
+
// 'cmd.exe',
|
|
111
|
+
// [keepOpen ? '/k' : '/c', 'start', 'powershell.exe', '-Command', command, ...args],
|
|
112
|
+
// options
|
|
113
|
+
// )
|
|
114
|
+
// } catch (error) {
|
|
115
|
+
// Oops! No PowerShell? Fallback to CMD.
|
|
116
|
+
return execa(
|
|
117
|
+
"cmd.exe",
|
|
118
|
+
[keepOpen ? "/k" : "/c", "start", "cmd.exe", "/c", command, ...args],
|
|
119
|
+
options,
|
|
120
|
+
);
|
|
121
|
+
// }
|
|
122
|
+
} else {
|
|
123
|
+
throw new Error("Unsupported platform: " + platform);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { expect, test, describe, afterEach } from "vitest";
|
|
2
|
+
import { mkdir, writeFile, access } from "node:fs/promises";
|
|
3
|
+
import { join, dirname } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { createSandbox, runAction, isWindows, isLinux, isMac } from "@pipelab/test-utils";
|
|
6
|
+
import { uploadToSteamRunner } from "../../src/upload-to-steam";
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = dirname(__filename);
|
|
10
|
+
|
|
11
|
+
describe("End-to-End: Steam Integration", () => {
|
|
12
|
+
let sandbox: Awaited<ReturnType<typeof createSandbox>>;
|
|
13
|
+
|
|
14
|
+
afterEach(async () => {
|
|
15
|
+
if (sandbox) {
|
|
16
|
+
await sandbox.remove();
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test(
|
|
21
|
+
"should mock a steam upload action using a single file",
|
|
22
|
+
async () => {
|
|
23
|
+
sandbox = await createSandbox("steam-e2e");
|
|
24
|
+
// 1. Setup mock Steam SDK
|
|
25
|
+
const mockSdkPath = join(sandbox.path, "mock-steam-sdk");
|
|
26
|
+
let builderFolder = "builder";
|
|
27
|
+
if (process.platform === "linux") {
|
|
28
|
+
builderFolder += "_linux";
|
|
29
|
+
} else if (process.platform === "darwin") {
|
|
30
|
+
builderFolder += "_osx";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const relativeSteamCmd = join(
|
|
34
|
+
"mock-steam-sdk",
|
|
35
|
+
"tools",
|
|
36
|
+
"ContentBuilder",
|
|
37
|
+
builderFolder,
|
|
38
|
+
"steamcmd",
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
// Create the mock steamcmd script that simulates a successful login and build
|
|
42
|
+
const mockScript = isWindows
|
|
43
|
+
? `@echo off\necho Authenticated\nexit /b 0`
|
|
44
|
+
: `#!/bin/bash\necho "Authenticated"\nexit 0`;
|
|
45
|
+
|
|
46
|
+
await sandbox.mockBinary(relativeSteamCmd, mockScript);
|
|
47
|
+
|
|
48
|
+
if (isLinux) {
|
|
49
|
+
const linux32Dir = join(
|
|
50
|
+
"mock-steam-sdk",
|
|
51
|
+
"tools",
|
|
52
|
+
"ContentBuilder",
|
|
53
|
+
builderFolder,
|
|
54
|
+
"linux32",
|
|
55
|
+
);
|
|
56
|
+
await sandbox.mockBinary(join(linux32Dir, "steamcmd"), undefined, { extension: false });
|
|
57
|
+
await sandbox.mockBinary(join(linux32Dir, "steamerrorreporter"), undefined, {
|
|
58
|
+
extension: false,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (isMac) {
|
|
63
|
+
await sandbox.mockBinary(
|
|
64
|
+
join("mock-steam-sdk", "tools", "ContentBuilder", builderFolder, "steamcmd"),
|
|
65
|
+
undefined,
|
|
66
|
+
{ extension: false },
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 2. Setup a dummy single file to upload
|
|
71
|
+
const uploadFolder = join(sandbox.path, "to-upload");
|
|
72
|
+
await mkdir(uploadFolder, { recursive: true });
|
|
73
|
+
await writeFile(join(uploadFolder, "test-file.txt"), "This is a test file for steam upload.");
|
|
74
|
+
|
|
75
|
+
// 3. Run the action directly
|
|
76
|
+
const inputs = {
|
|
77
|
+
sdk: mockSdkPath,
|
|
78
|
+
username: "testuser",
|
|
79
|
+
appId: "123456",
|
|
80
|
+
depotId: "654321",
|
|
81
|
+
description: "Test Build",
|
|
82
|
+
folder: uploadFolder,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const result = await runAction(uploadToSteamRunner, {
|
|
86
|
+
inputs,
|
|
87
|
+
sandboxPath: sandbox.path,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// 4. Verification
|
|
91
|
+
const outputs = result.outputs;
|
|
92
|
+
expect(outputs).toBeDefined();
|
|
93
|
+
expect(outputs["script-path"]).toBeDefined();
|
|
94
|
+
expect(outputs["output-folder"]).toBeDefined();
|
|
95
|
+
expect(outputs["status"]).toBe("success");
|
|
96
|
+
|
|
97
|
+
// Verify files exist
|
|
98
|
+
await expect(access(outputs["script-path"] as string)).resolves.not.toThrow();
|
|
99
|
+
await expect(access(outputs["output-folder"] as string)).resolves.not.toThrow();
|
|
100
|
+
},
|
|
101
|
+
5 * 60 * 1000,
|
|
102
|
+
); // 5 minutes timeout for real build
|
|
103
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { defineConfig } from "vitest/config";
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
test: {
|
|
5
|
+
fileParallelism: false,
|
|
6
|
+
testTimeout: 60000,
|
|
7
|
+
hookTimeout: 60000,
|
|
8
|
+
maxWorkers: 1,
|
|
9
|
+
minWorkers: 1,
|
|
10
|
+
poolOptions: {
|
|
11
|
+
forks: {
|
|
12
|
+
singleFork: true,
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
include: ["**/*.spec.ts"],
|
|
16
|
+
root: "tests/e2e",
|
|
17
|
+
environment: "node",
|
|
18
|
+
env: { NODE_ENV: "development", PIPELAB_DISABLE_HISTORY: "true" },
|
|
19
|
+
},
|
|
20
|
+
});
|