@bitmagic/cli 0.1.83-dev.1 → 0.1.83-dev.3

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.
Files changed (206) hide show
  1. package/README.md +69 -0
  2. package/assets/skills/publishing-to-steam/SKILL.md +218 -0
  3. package/dist/assets/vxl-summary.d.ts +6 -20
  4. package/dist/assets/vxl-summary.js +9 -100
  5. package/dist/assets/vxl-summary.js.map +1 -1
  6. package/dist/cli.d.ts +1 -0
  7. package/dist/cli.js +2 -0
  8. package/dist/cli.js.map +1 -1
  9. package/dist/commands/check.js +5 -0
  10. package/dist/commands/check.js.map +1 -1
  11. package/dist/commands/forge.js +1 -1
  12. package/dist/commands/forge.js.map +1 -1
  13. package/dist/commands/ios.js +88 -136
  14. package/dist/commands/ios.js.map +1 -1
  15. package/dist/commands/steam-build.d.ts +67 -0
  16. package/dist/commands/steam-build.js +286 -0
  17. package/dist/commands/steam-build.js.map +1 -0
  18. package/dist/commands/steam-portal.d.ts +1 -0
  19. package/dist/commands/steam-portal.js +319 -0
  20. package/dist/commands/steam-portal.js.map +1 -0
  21. package/dist/commands/steam-upload.d.ts +37 -0
  22. package/dist/commands/steam-upload.js +174 -0
  23. package/dist/commands/steam-upload.js.map +1 -0
  24. package/dist/commands/steam.d.ts +1 -0
  25. package/dist/commands/steam.js +504 -0
  26. package/dist/commands/steam.js.map +1 -0
  27. package/dist/commands/verify.js +6 -0
  28. package/dist/commands/verify.js.map +1 -1
  29. package/dist/editor/asset-viewer.js +5 -4
  30. package/dist/editor/asset-viewer.js.map +1 -1
  31. package/dist/editor/server.js +1 -1
  32. package/dist/editor/server.js.map +1 -1
  33. package/dist/editor/shell-page.js +3 -3
  34. package/dist/forge/browser-host.d.ts +1 -1
  35. package/dist/forge/browser-host.js +1 -1
  36. package/dist/forge/browser-host.js.map +1 -1
  37. package/dist/ios/native-overrides.d.ts +27 -0
  38. package/dist/ios/native-overrides.js +87 -0
  39. package/dist/ios/native-overrides.js.map +1 -0
  40. package/dist/ios/record-validate.d.ts +1 -33
  41. package/dist/ios/record-validate.js +2 -100
  42. package/dist/ios/record-validate.js.map +1 -1
  43. package/dist/ios/store-bridge.d.ts +26 -0
  44. package/dist/ios/store-bridge.js +224 -0
  45. package/dist/ios/store-bridge.js.map +1 -0
  46. package/dist/ios/storekit-config.d.ts +15 -0
  47. package/dist/ios/storekit-config.js +106 -0
  48. package/dist/ios/storekit-config.js.map +1 -0
  49. package/dist/ios/xcode-template.d.ts +12 -3
  50. package/dist/ios/xcode-template.js +158 -27
  51. package/dist/ios/xcode-template.js.map +1 -1
  52. package/dist/package/collect-urls.d.ts +1 -1
  53. package/dist/package/collect-urls.js +10 -2
  54. package/dist/package/collect-urls.js.map +1 -1
  55. package/dist/package/offline-payload.d.ts +35 -0
  56. package/dist/package/offline-payload.js +142 -0
  57. package/dist/package/offline-payload.js.map +1 -0
  58. package/dist/package/packaged-check.d.ts +39 -0
  59. package/dist/package/packaged-check.js +104 -0
  60. package/dist/package/packaged-check.js.map +1 -0
  61. package/dist/package/transform.d.ts +4 -4
  62. package/dist/package/transform.js +3 -3
  63. package/dist/project/published-three.d.ts +14 -0
  64. package/dist/project/published-three.js +91 -0
  65. package/dist/project/published-three.js.map +1 -0
  66. package/dist/publish/bundle.js +6 -0
  67. package/dist/publish/bundle.js.map +1 -1
  68. package/dist/scaffold/project-files.js +7 -4
  69. package/dist/scaffold/project-files.js.map +1 -1
  70. package/dist/scaffold/project.js +2 -0
  71. package/dist/scaffold/project.js.map +1 -1
  72. package/dist/scaffold/skill-files.js +3 -1
  73. package/dist/scaffold/skill-files.js.map +1 -1
  74. package/dist/scaffold/steam-skill-files.d.ts +2 -0
  75. package/dist/scaffold/steam-skill-files.js +7 -0
  76. package/dist/scaffold/steam-skill-files.js.map +1 -0
  77. package/dist/steam/art-generate.d.ts +55 -0
  78. package/dist/steam/art-generate.js +174 -0
  79. package/dist/steam/art-generate.js.map +1 -0
  80. package/dist/steam/art-render.d.ts +25 -0
  81. package/dist/steam/art-render.js +157 -0
  82. package/dist/steam/art-render.js.map +1 -0
  83. package/dist/steam/art-use.d.ts +20 -0
  84. package/dist/steam/art-use.js +84 -0
  85. package/dist/steam/art-use.js.map +1 -0
  86. package/dist/steam/art.d.ts +98 -0
  87. package/dist/steam/art.js +243 -0
  88. package/dist/steam/art.js.map +1 -0
  89. package/dist/steam/check.d.ts +21 -0
  90. package/dist/steam/check.js +113 -0
  91. package/dist/steam/check.js.map +1 -0
  92. package/dist/steam/config.d.ts +57 -0
  93. package/dist/steam/config.js +145 -0
  94. package/dist/steam/config.js.map +1 -0
  95. package/dist/steam/electron-cache.d.ts +11 -0
  96. package/dist/steam/electron-cache.js +105 -0
  97. package/dist/steam/electron-cache.js.map +1 -0
  98. package/dist/steam/fuses.d.ts +16 -0
  99. package/dist/steam/fuses.js +75 -0
  100. package/dist/steam/fuses.js.map +1 -0
  101. package/dist/steam/icon-files.d.ts +16 -0
  102. package/dist/steam/icon-files.js +70 -0
  103. package/dist/steam/icon-files.js.map +1 -0
  104. package/dist/steam/image-info.d.ts +16 -0
  105. package/dist/steam/image-info.js +89 -0
  106. package/dist/steam/image-info.js.map +1 -0
  107. package/dist/steam/launch-check.d.ts +12 -0
  108. package/dist/steam/launch-check.js +101 -0
  109. package/dist/steam/launch-check.js.map +1 -0
  110. package/dist/steam/media.d.ts +48 -0
  111. package/dist/steam/media.js +155 -0
  112. package/dist/steam/media.js.map +1 -0
  113. package/dist/steam/package-macos.d.ts +52 -0
  114. package/dist/steam/package-macos.js +268 -0
  115. package/dist/steam/package-macos.js.map +1 -0
  116. package/dist/steam/package-windows.d.ts +23 -0
  117. package/dist/steam/package-windows.js +83 -0
  118. package/dist/steam/package-windows.js.map +1 -0
  119. package/dist/steam/paths.d.ts +35 -0
  120. package/dist/steam/paths.js +44 -0
  121. package/dist/steam/paths.js.map +1 -0
  122. package/dist/steam/playtest-shots.d.ts +18 -0
  123. package/dist/steam/playtest-shots.js +83 -0
  124. package/dist/steam/playtest-shots.js.map +1 -0
  125. package/dist/steam/portal-recipes.d.ts +25 -0
  126. package/dist/steam/portal-recipes.js +61 -0
  127. package/dist/steam/portal-recipes.js.map +1 -0
  128. package/dist/steam/portal.d.ts +58 -0
  129. package/dist/steam/portal.js +340 -0
  130. package/dist/steam/portal.js.map +1 -0
  131. package/dist/steam/raster.d.ts +58 -0
  132. package/dist/steam/raster.js +285 -0
  133. package/dist/steam/raster.js.map +1 -0
  134. package/dist/steam/shell-files.d.ts +17 -0
  135. package/dist/steam/shell-files.js +268 -0
  136. package/dist/steam/shell-files.js.map +1 -0
  137. package/dist/steam/specs.d.ts +61 -0
  138. package/dist/steam/specs.js +103 -0
  139. package/dist/steam/specs.js.map +1 -0
  140. package/dist/steam/steampipe.d.ts +46 -0
  141. package/dist/steam/steampipe.js +170 -0
  142. package/dist/steam/steampipe.js.map +1 -0
  143. package/dist/steam/store-copy.d.ts +88 -0
  144. package/dist/steam/store-copy.js +214 -0
  145. package/dist/steam/store-copy.js.map +1 -0
  146. package/dist/steam/tags.d.ts +9 -0
  147. package/dist/steam/tags.js +52 -0
  148. package/dist/steam/tags.js.map +1 -0
  149. package/dist/steam/trailer.d.ts +34 -0
  150. package/dist/steam/trailer.js +141 -0
  151. package/dist/steam/trailer.js.map +1 -0
  152. package/dist/theme/kernel.d.ts +14 -0
  153. package/dist/theme/kernel.js +15 -0
  154. package/dist/theme/kernel.js.map +1 -0
  155. package/dist/theme/theme-io.js +11 -16
  156. package/dist/theme/theme-io.js.map +1 -1
  157. package/dist/vendor/hud-theme/catalog.js +71 -0
  158. package/dist/vendor/hud-theme/engine/hud/ThemeTokens.js +115 -0
  159. package/dist/{theme/theme-color-math.js → vendor/hud-theme/engine/hud/colorMath.js} +13 -14
  160. package/dist/vendor/hud-theme/engine/hud/decorations.js +179 -0
  161. package/dist/vendor/hud-theme/engine/hud/fonts.js +147 -0
  162. package/dist/vendor/hud-theme/engine/hud/presets.js +80 -0
  163. package/dist/vendor/hud-theme/engine/hud/resolveWorldTheme.js +66 -0
  164. package/dist/vendor/hud-theme/engine/hud/themes/bitmagic.js +29 -0
  165. package/dist/vendor/hud-theme/engine/hud/themes/nexus.js +69 -0
  166. package/dist/vendor/hud-theme/engine/hud/themes/racing.js +57 -0
  167. package/dist/vendor/hud-theme/engine/hud/themes/rift-raider.js +63 -0
  168. package/dist/vendor/hud-theme/engine/hud/themes/simulator.js +55 -0
  169. package/dist/vendor/hud-theme/engine/hud/themes/village-keep.js +108 -0
  170. package/dist/vendor/hud-theme/engine/hud/validateTheme.js +535 -0
  171. package/dist/vendor/hud-theme/index.js +26 -0
  172. package/dist/{theme/theme-merge.js → vendor/hud-theme/merge.js} +0 -1
  173. package/dist/{theme/theme-report.js → vendor/hud-theme/report.js} +8 -9
  174. package/dist/{theme/theme-stored.js → vendor/hud-theme/stored.js} +6 -9
  175. package/dist/{theme/theme-validate.js → vendor/hud-theme/validate-strict.js} +102 -164
  176. package/dist/verify/browser.d.ts +11 -0
  177. package/dist/verify/browser.js +222 -155
  178. package/dist/verify/browser.js.map +1 -1
  179. package/dist/verify/categories.d.ts +1 -1
  180. package/dist/verify/categories.js +1 -0
  181. package/dist/verify/categories.js.map +1 -1
  182. package/dist/verify/classify.d.ts +14 -2
  183. package/dist/verify/classify.js +23 -4
  184. package/dist/verify/classify.js.map +1 -1
  185. package/dist/verify/hud-theme.d.ts +19 -14
  186. package/dist/verify/hud-theme.js +23 -17
  187. package/dist/verify/hud-theme.js.map +1 -1
  188. package/dist/verify/mobile-parity.d.ts +1 -1
  189. package/dist/verify/mobile-parity.js +1 -1
  190. package/dist/verify/start.d.ts +3 -2
  191. package/dist/verify/start.js +1 -1
  192. package/dist/verify/start.js.map +1 -1
  193. package/package.json +8 -6
  194. package/dist/theme/hud-theme-catalog.d.ts +0 -235
  195. package/dist/theme/hud-theme-catalog.js +0 -389
  196. package/dist/theme/hud-theme-catalog.js.map +0 -1
  197. package/dist/theme/theme-color-math.d.ts +0 -21
  198. package/dist/theme/theme-color-math.js.map +0 -1
  199. package/dist/theme/theme-merge.d.ts +0 -36
  200. package/dist/theme/theme-merge.js.map +0 -1
  201. package/dist/theme/theme-report.d.ts +0 -28
  202. package/dist/theme/theme-report.js.map +0 -1
  203. package/dist/theme/theme-stored.d.ts +0 -21
  204. package/dist/theme/theme-stored.js.map +0 -1
  205. package/dist/theme/theme-validate.d.ts +0 -41
  206. package/dist/theme/theme-validate.js.map +0 -1
@@ -0,0 +1,35 @@
1
+ export interface SteamPaths {
2
+ /** `.bitmagic/steam` — the lane's root. */
3
+ steamDir: string;
4
+ /** `.bitmagic/steam.json` — the app id, depots, builder account, version. */
5
+ configPath: string;
6
+ /** The store page copy the agent writes and `steam check` validates. */
7
+ storePath: string;
8
+ /** Which image is the wide/tall/hero key art, the logo and the icon, and each capsule's layout. */
9
+ artConfigPath: string;
10
+ /** The chosen sources: keyart-wide.png, keyart-tall.png, keyart-hero.png, logo.png, icon.png. */
11
+ artDir: string;
12
+ /** Generated candidates, one PNG + one JSON record each. */
13
+ candidatesDir: string;
14
+ /** Everything uploaded to Steamworks, at its exact size. */
15
+ kitDir: string;
16
+ /** Gameplay screenshots, 1920x1080. */
17
+ screenshotsDir: string;
18
+ /** The trailer as uploaded. */
19
+ trailerDir: string;
20
+ /** The game payload the desktop shell loads: index.html + vendor tree. */
21
+ payloadDir: string;
22
+ /** Downloaded-asset cache for the payload, keyed by URL hash — survives payload rebuilds. */
23
+ cacheDir: string;
24
+ /** Validation artifacts (console logs, screenshots), one subdirectory per pass. */
25
+ checkDir: string;
26
+ /** Depot content roots: build/windows/ and build/macos/. */
27
+ buildDir: string;
28
+ /** Generated SteamPipe scripts and steamcmd's build output. */
29
+ steampipeDir: string;
30
+ }
31
+ export declare function steamPaths(root: string): SteamPaths;
32
+ /** The Windows depot's content root: the folder that holds `<Game>.exe`. */
33
+ export declare function windowsContentRoot(paths: SteamPaths): string;
34
+ /** The macOS depot's content root: the folder that holds `<Game>.app`. */
35
+ export declare function macosContentRoot(paths: SteamPaths): string;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Where the Steam lane keeps everything: under `.bitmagic/`, which the publish fingerprint
3
+ * excludes, so nothing `bitmagic steam` writes can flip verify/publish staleness. Same reasoning
4
+ * as the iOS lane's `.bitmagic/ios/` (see ios/paths.ts).
5
+ *
6
+ * Two kinds of file live here and they are treated differently:
7
+ * - INPUTS the agent and creator author — `steam.json` (the app's identity on Steam),
8
+ * `steam/store.json` (the store page copy), `steam/art.json` and `steam/art/` (the key art,
9
+ * the logo and how each capsule is laid out). Nothing regenerates these.
10
+ * - OUTPUTS the CLI derives — `steam/store-kit/` (every image, screenshot and the trailer at the
11
+ * exact size Steamworks asks for), `steam/payload/`, `steam/build/`, `steam/steampipe/`.
12
+ * Rebuilt on every run; safe to delete.
13
+ */
14
+ import * as path from 'path';
15
+ export function steamPaths(root) {
16
+ const steamDir = path.join(root, '.bitmagic', 'steam');
17
+ const kitDir = path.join(steamDir, 'store-kit');
18
+ const artDir = path.join(steamDir, 'art');
19
+ return {
20
+ steamDir,
21
+ configPath: path.join(root, '.bitmagic', 'steam.json'),
22
+ storePath: path.join(steamDir, 'store.json'),
23
+ artConfigPath: path.join(steamDir, 'art.json'),
24
+ artDir,
25
+ candidatesDir: path.join(artDir, 'candidates'),
26
+ kitDir,
27
+ screenshotsDir: path.join(kitDir, 'screenshots'),
28
+ trailerDir: path.join(kitDir, 'trailer'),
29
+ payloadDir: path.join(steamDir, 'payload'),
30
+ cacheDir: path.join(steamDir, 'cache'),
31
+ checkDir: path.join(steamDir, 'check'),
32
+ buildDir: path.join(steamDir, 'build'),
33
+ steampipeDir: path.join(steamDir, 'steampipe'),
34
+ };
35
+ }
36
+ /** The Windows depot's content root: the folder that holds `<Game>.exe`. */
37
+ export function windowsContentRoot(paths) {
38
+ return path.join(paths.buildDir, 'windows');
39
+ }
40
+ /** The macOS depot's content root: the folder that holds `<Game>.app`. */
41
+ export function macosContentRoot(paths) {
42
+ return path.join(paths.buildDir, 'macos');
43
+ }
44
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/steam/paths.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAiC7B,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC;IACvD,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;IAChD,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC1C,OAAO;QACL,QAAQ;QACR,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,YAAY,CAAC;QACtD,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,YAAY,CAAC;QAC5C,aAAa,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC;QAC9C,MAAM;QACN,aAAa,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC;QAC9C,MAAM;QACN,cAAc,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,aAAa,CAAC;QAChD,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC;QACxC,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC;QAC1C,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC;QACtC,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC;QACtC,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC;QACtC,YAAY,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC;KAC/C,CAAC;AACJ,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,kBAAkB,CAAC,KAAiB;IAClD,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;AAC9C,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,gBAAgB,CAAC,KAAiB;IAChD,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;AAC5C,CAAC"}
@@ -0,0 +1,18 @@
1
+ import { type RendererChoice } from '../browser-gl.js';
2
+ export interface PlaytestShotsOptions {
3
+ payloadDir: string;
4
+ script: string;
5
+ /** Where screenshot_NN.png files go; numbering continues after `startIndex`. */
6
+ outDir: string;
7
+ startIndex: number;
8
+ renderer: RendererChoice;
9
+ /** Hard cap for the whole script. */
10
+ timeoutMs: number;
11
+ log: (message: string) => void;
12
+ }
13
+ export interface PlaytestShotsResult {
14
+ written: string[];
15
+ /** Remote URLs the game asked for that the payload does not carry — missing in the shipped build. */
16
+ refused: string[];
17
+ }
18
+ export declare function captureWithPlaytest(options: PlaytestShotsOptions): Promise<PlaytestShotsResult>;
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Screenshots of real play: the packaged payload booted in headless Chrome at exactly 1920x1080,
3
+ * offline, as the Steam build runs it, with an agent-written script driving the game and calling
4
+ * `shot()` at the moments worth showing. The HUD, the menus the player sees, the actual camera —
5
+ * what a Steam reviewer means by "gameplay" — rather than frames lifted from a cinematic cut.
6
+ *
7
+ * The script is an ES module the agent writes for this game:
8
+ *
9
+ * export default async ({ page, shot, wait }) => {
10
+ * await page.keyboard.press('Enter'); // through the game's own menus
11
+ * await wait(4000);
12
+ * await page.keyboard.down('ArrowUp'); // play
13
+ * await wait(1500);
14
+ * await shot('first stage, rolling');
15
+ * await page.keyboard.up('ArrowUp');
16
+ * };
17
+ *
18
+ * `page` is a Playwright Page. The run sets `navigator.webdriver`, as verify does, so a game's own
19
+ * start screen behaves the way it does under `bitmagic verify`.
20
+ */
21
+ import * as fs from 'fs';
22
+ import * as path from 'path';
23
+ import { pathToFileURL } from 'url';
24
+ import { browserLaunchPlan } from '../browser-gl.js';
25
+ import { launchSystemChrome } from '../chrome-launch.js';
26
+ import { CliError } from '../errors.js';
27
+ import { startPayloadServer } from '../package/local-server.js';
28
+ import { isOnlineOnlyUrl } from '../package/packaged-check.js';
29
+ import { SCREENSHOT_HEIGHT, SCREENSHOT_WIDTH } from './specs.js';
30
+ export async function captureWithPlaytest(options) {
31
+ if (!fs.existsSync(path.join(options.payloadDir, 'index.html'))) {
32
+ throw new CliError('No packaged payload yet — run `bitmagic steam build` first (the shots are taken from what ships).', 3);
33
+ }
34
+ const loaded = await import(pathToFileURL(path.resolve(options.script)).href);
35
+ if (typeof loaded.default !== 'function') {
36
+ throw new CliError(`${options.script} must export a default async function ({ page, shot, wait }).`);
37
+ }
38
+ const play = loaded.default;
39
+ const server = await startPayloadServer(options.payloadDir);
40
+ const plan = browserLaunchPlan(process.env, options.renderer);
41
+ const browser = await launchSystemChrome({ headless: plan.headless, args: ['--mute-audio', ...plan.args], command: 'bitmagic steam screenshots' });
42
+ const written = [];
43
+ const refused = new Set();
44
+ try {
45
+ const context = await browser.newContext({ viewport: { width: SCREENSHOT_WIDTH, height: SCREENSHOT_HEIGHT }, deviceScaleFactor: 1 });
46
+ // Offline, like the Steam build: anything not in the payload is refused — and remembered, because
47
+ // a scene the validation passes never reached may ask for an asset that was never packaged.
48
+ await context.route('**/*', (route) => {
49
+ const url = route.request().url();
50
+ if (url.startsWith(server.origin))
51
+ return route.continue();
52
+ if (!isOnlineOnlyUrl(url))
53
+ refused.add(url);
54
+ return route.abort('internetdisconnected');
55
+ });
56
+ const page = await context.newPage();
57
+ await page.goto(`${server.origin}/index.html?renderer=${options.renderer}`, { waitUntil: 'load', timeout: 60_000 });
58
+ let index = options.startIndex;
59
+ const shot = async (label) => {
60
+ index += 1;
61
+ const file = path.join(options.outDir, `screenshot_${String(index).padStart(2, '0')}.png`);
62
+ fs.mkdirSync(options.outDir, { recursive: true });
63
+ await page.screenshot({ path: file, clip: { x: 0, y: 0, width: SCREENSHOT_WIDTH, height: SCREENSHOT_HEIGHT } });
64
+ written.push(file);
65
+ options.log(` ${path.basename(file)}${label ? ` ← ${label}` : ''}`);
66
+ return file;
67
+ };
68
+ const wait = (ms) => page.waitForTimeout(ms);
69
+ let timer;
70
+ await Promise.race([
71
+ play({ page, shot, wait }),
72
+ new Promise((_, reject) => {
73
+ timer = setTimeout(() => reject(new CliError(`The playtest ran past ${Math.round(options.timeoutMs / 1000)} s and was stopped.`)), options.timeoutMs);
74
+ }),
75
+ ]).finally(() => clearTimeout(timer));
76
+ return { written, refused: [...refused] };
77
+ }
78
+ finally {
79
+ await browser.close();
80
+ await server.close();
81
+ }
82
+ }
83
+ //# sourceMappingURL=playtest-shots.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"playtest-shots.js","sourceRoot":"","sources":["../../src/steam/playtest-shots.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Scripted Steamworks steps: currently the About and short-description forms. Each recipe saves a
3
+ * DRAFT; none submits for review, publishes or releases, which stay the creator's (with the
4
+ * agent clicking only after an explicit yes, through `portal click --approved`).
5
+ *
6
+ * Steamworks changes its pages without notice. A recipe that cannot find what it expects stops
7
+ * and says what it was looking for, and the agent finishes that step by hand with `portal look`
8
+ * and `portal fill` — the recipes are the fast path, never the only one.
9
+ */
10
+ import type { Page } from 'playwright-core';
11
+ export interface RecipeContext {
12
+ page: Page;
13
+ root: string;
14
+ gameId: string;
15
+ log: (message: string) => void;
16
+ }
17
+ export interface RecipeResult {
18
+ recipe: string;
19
+ done: string[];
20
+ /** What the agent still has to do by hand on this page. */
21
+ remaining: string[];
22
+ }
23
+ export type Recipe = (context: RecipeContext) => Promise<RecipeResult>;
24
+ export declare const RECIPES: Record<string, Recipe>;
25
+ export declare function runPortalRecipe(name: string, context: RecipeContext): Promise<RecipeResult>;
@@ -0,0 +1,61 @@
1
+ import { CliError } from '../errors.js';
2
+ import { requireAppId, resolveSteamConfig } from './config.js';
3
+ import { steamPaths } from './paths.js';
4
+ import { readStoreCopy, SHORT_DESCRIPTION_MAX } from './store-copy.js';
5
+ /** Only description drafts: surveys and release actions need decisions, not canned answers. */
6
+ const descriptions = async ({ page, root, gameId }) => {
7
+ const paths = steamPaths(root);
8
+ const appId = requireAppId(resolveSteamConfig({ root, gameId, configPath: paths.configPath }));
9
+ const copy = readStoreCopy(paths.storePath);
10
+ if (copy === null || copy.about.trim().length === 0 || copy.shortDescription.trim().length === 0) {
11
+ throw new CliError('Write about and shortDescription in .bitmagic/steam/store.json first.');
12
+ }
13
+ if (copy.shortDescription.length > SHORT_DESCRIPTION_MAX) {
14
+ throw new CliError(`shortDescription exceeds ${SHORT_DESCRIPTION_MAX} characters.`);
15
+ }
16
+ const url = `https://partner.steamgames.com/admin/game/editbyappid/${appId}?activetab=tab_description`;
17
+ await page.goto(url, { waitUntil: 'domcontentloaded' });
18
+ const visual = page.getByRole('checkbox', { name: 'Use visual editor', exact: true });
19
+ await visual.waitFor({ state: 'visible', timeout: 15000 });
20
+ await visual.uncheck();
21
+ const short = page.locator('#app_content_short_description__textarea');
22
+ // The BBCode editor has no stable ID; fail if the page exposes an ambiguous editor.
23
+ const source = page.locator('textarea:visible:not([id]), textarea[id=""]:visible');
24
+ if (await source.count() !== 1)
25
+ throw new CliError('Steam description editor changed. Use portal look to finish the draft.');
26
+ await source.fill(copy.about);
27
+ // The legacy localized field commits through change/blur; exercise normal editing events.
28
+ await short.fill('');
29
+ await short.pressSequentially(copy.shortDescription);
30
+ await short.press('Tab');
31
+ await Promise.all([
32
+ page.waitForNavigation({ waitUntil: 'domcontentloaded', timeout: 15000 }),
33
+ page.getByRole('button', { name: 'Save', exact: true }).click(),
34
+ ]);
35
+ // Reopen to prove the server retained the values instead of only echoing local inputs.
36
+ await page.goto(url, { waitUntil: 'domcontentloaded' });
37
+ await visual.waitFor({ state: 'visible', timeout: 15000 });
38
+ await visual.uncheck();
39
+ if (await source.inputValue() !== copy.about || await short.inputValue() !== copy.shortDescription) {
40
+ throw new CliError('Steam did not retain the complete descriptions. Inspect the page notice before retrying.');
41
+ }
42
+ return {
43
+ recipe: 'descriptions',
44
+ done: ['Saved and reopened the About This Game and short description drafts.'],
45
+ remaining: ['Inspect the store beta preview with the creator before publishing.'],
46
+ };
47
+ };
48
+ export const RECIPES = { descriptions };
49
+ export async function runPortalRecipe(name, context) {
50
+ const recipe = RECIPES[name];
51
+ if (recipe === undefined) {
52
+ throw new CliError(`No portal recipe called "${name}". Known: ${Object.keys(RECIPES).join(', ') || 'none'}.`);
53
+ }
54
+ const result = await recipe(context);
55
+ for (const line of result.done)
56
+ context.log(` done: ${line}`);
57
+ for (const line of result.remaining)
58
+ context.log(` by hand: ${line}`);
59
+ return result;
60
+ }
61
+ //# sourceMappingURL=portal-recipes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"portal-recipes.js","sourceRoot":"","sources":["../../src/steam/portal-recipes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ import { type Browser, type Locator, type Page } from 'playwright-core';
2
+ export declare const STEAMWORKS_HOME = "https://partner.steamgames.com/";
3
+ export declare function portalDir(baseDir?: string): string;
4
+ export declare function isValveUrl(url: string): boolean;
5
+ /** The system Chrome binary for this OS, or null when none is installed where Chrome installs. */
6
+ export declare function findChromeExecutable(env?: Record<string, string | undefined>): string | null;
7
+ export interface OpenResult {
8
+ reused: boolean;
9
+ port: number;
10
+ }
11
+ /** Start the Steamworks window, or reuse the one already open. */
12
+ export declare function openPortal(options?: {
13
+ url?: string;
14
+ dir?: string;
15
+ }): Promise<OpenResult>;
16
+ /** Disconnect from the window without closing it. */
17
+ export declare function detach(browser: Browser): Promise<void>;
18
+ /** Attach to the open window and pick the tab to work in: the newest Valve tab, else any tab. */
19
+ export declare function attachPortal(options?: {
20
+ dir?: string;
21
+ }): Promise<{
22
+ browser: Browser;
23
+ page: Page;
24
+ }>;
25
+ /** Close the window and forget the session. The profile (and its login) stays for next time. */
26
+ export declare function closePortal(options?: {
27
+ dir?: string;
28
+ }): Promise<boolean>;
29
+ export interface PageElement {
30
+ ref: string;
31
+ role: string;
32
+ label: string;
33
+ value?: string;
34
+ checked?: boolean;
35
+ disabled?: boolean;
36
+ }
37
+ export interface PageLook {
38
+ url: string;
39
+ title: string;
40
+ loggedIn: boolean;
41
+ headings: string[];
42
+ elements: PageElement[];
43
+ /** Status and error banners the page is showing, verbatim. */
44
+ notices: string[];
45
+ }
46
+ export declare function lookAtPage(page: Page): Promise<PageLook>;
47
+ /** The visible text of the page, for reading what it says. */
48
+ export declare function pageText(page: Page, maxChars?: number): Promise<string>;
49
+ export declare function isCommittingLabel(label: string): boolean;
50
+ export declare function resolveRef(page: Page, ref: string): Promise<Locator>;
51
+ export declare function clickElement(page: Page, ref: string, options?: {
52
+ approved?: boolean;
53
+ }): Promise<string>;
54
+ export declare function fillElement(page: Page, ref: string, value: string): Promise<void>;
55
+ export declare function uploadToElement(page: Page, ref: string, files: readonly string[]): Promise<void>;
56
+ export declare function selectOption(page: Page, ref: string, option: string): Promise<string[]>;
57
+ export declare function setChecked(page: Page, ref: string, checked: boolean): Promise<void>;
58
+ export declare function gotoUrl(page: Page, url: string): Promise<void>;