@homie-rocks/studio 0.31.0 → 0.32.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.
Files changed (42) hide show
  1. package/CHANGELOG.md +68 -1
  2. package/README.md +44 -6
  3. package/bin/homie-studio.mjs +101 -8
  4. package/lib/build.mjs +1 -1
  5. package/lib/cloudflare.mjs +80 -6
  6. package/lib/directory.mjs +61 -9
  7. package/lib/doctor.mjs +23 -10
  8. package/lib/mcp-tools.mjs +18 -11
  9. package/lib/mcp.mjs +2 -0
  10. package/lib/parts.mjs +27 -0
  11. package/lib/routes.mjs +110 -2
  12. package/lib/scaffold.mjs +3 -3
  13. package/lib/standalone-cli.mjs +106 -0
  14. package/lib/standalone-files.mjs +360 -0
  15. package/lib/standalone-icons.mjs +106 -0
  16. package/lib/standalone-lock.json +3404 -0
  17. package/lib/standalone-pins.json +18 -0
  18. package/lib/standalone-tools.mjs +63 -0
  19. package/lib/standalone.mjs +868 -0
  20. package/lib/template-history.json +80 -0
  21. package/netplay/NETPLAY.md +148 -10
  22. package/netplay/netplay.ts +234 -33
  23. package/package.json +2 -1
  24. package/parts/PARTS.md +8 -2
  25. package/port/index.ts +1 -1
  26. package/port/room.ts +228 -23
  27. package/standalone/STANDALONE.md +313 -0
  28. package/standalone/electron/files.cjs +50 -0
  29. package/standalone/electron/main.cjs +139 -0
  30. package/standalone/electron/pack.mjs +82 -0
  31. package/standalone/shell/index.html +36 -0
  32. package/standalone/shell/shell.css +46 -0
  33. package/standalone/shell/shell.js +346 -0
  34. package/starters/ember-vale/src/main.ts +4 -0
  35. package/starters/gem-rush/src/main.ts +29 -7
  36. package/starters/gem-rush-3d/src/main.ts +36 -7
  37. package/starters/hero-rush-3d/src/main.ts +36 -7
  38. package/worker/index.mjs +55 -8
  39. package/worker/pages.mjs +19 -10
  40. package/worker/parts.mjs +43 -2
  41. package/worker/standalone.mjs +34 -0
  42. package/worker/version.mjs +1 -1
@@ -0,0 +1,18 @@
1
+ {
2
+ "v": 1,
3
+ "desktop": {
4
+ "electron": "44.6.0",
5
+ "@electron/packager": "20.3.0",
6
+ "@shockpkg/icon-encoder": "3.2.3"
7
+ },
8
+ "mobile": {
9
+ "@capacitor/core": "8.5.2",
10
+ "@capacitor/cli": "8.5.2",
11
+ "@capacitor/ios": "8.5.2",
12
+ "@capacitor/android": "8.5.2",
13
+ "@capacitor/assets": "3.0.5"
14
+ },
15
+ "steam": {
16
+ "steamworks-ffi-node": "0.11.3"
17
+ }
18
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * STANDALONE COPIES AS A CHAT TOOL (standalone/STANDALONE.md): game_standalone, one tool. With plan: true it reads
3
+ * and answers at once (what would be built, what each target still needs here, what a standalone copy does not
4
+ * have); without, it builds, which takes minutes, so it runs as a job that studio_job follows.
5
+ *
6
+ * WHICH TOOLKIT RUNS IT. The chat tools run a studio's own pinned toolkit, and one from before 0.32.0 has no
7
+ * `standalone` command. So the plan is made here, by this toolkit; and the build is run by this toolkit's own
8
+ * command when the studio pins an older one, as an upgrade is (lib/mcp-tools.mjs studio_run). The result says so,
9
+ * because that studio's live site cannot answer a standalone copy yet: it must upgrade and deploy first.
10
+ *
11
+ * Nothing is ever uploaded to Steam or a store from here, and no signing key is taken in chat: a release reads the
12
+ * environment only. What the standalone game does not have is said to the person as it is, every time.
13
+ */
14
+ import { join } from 'node:path';
15
+ import { SAY_MISSING, standaloneLines } from './standalone-cli.mjs';
16
+ import { STANDALONE_SINCE, standalonePlan, toolkitNote } from './standalone.mjs';
17
+ import { TARGETS } from './standalone-files.mjs';
18
+ import { PACKAGE_ROOT } from './studio.mjs';
19
+
20
+ export { SAY_MISSING };
21
+
22
+ export function standaloneToolDefs(ctx, h) {
23
+ const { ok, fail, cli, stillRunning, whyOf, needsInstall, str, STUDIO_ARG } = h;
24
+ return [
25
+ {
26
+ name: 'game_standalone', title: 'Make a standalone game',
27
+ description: `Make one of the studio's games into an app of its own: a desktop app for macOS, Windows and Linux (the kind of build Steam accepts for upload) and a phone app for iOS and Android (the files the App Store and Google Play accept for upload; each store's review, fees and rules are its own, and nothing here makes an app pass). It is the same web build of the game in a thin shell (Electron on computers, Capacitor on phones); multiplayer still goes through the studio's own deployed site, and with no connection the game plays offline with its bots. Use it when the person asks for a Steam build, a desktop app, a download, an iPhone or Android app, or "a standalone version". ALWAYS call it with plan: true first and tell the person what it says: which targets this computer can build and what the others need, the address that gets built into every copy, and what a standalone copy does NOT have in this version (no player accounts, no cloud saves: saves stay on the device and are lost when the app is uninstalled, no shop, no chat). Then, when they say go, call it again without plan: it builds as a background job that takes minutes (the first build downloads the wrapper tools into the studio's .studio/ folder): it answers with a job, and studio_job { "job": … } gives the result in words when it is done; read that result to the person, including its list of what the standalone game does not have. A target whose tool is missing on this computer is skipped with its fix and the others still build; only the desktop build for this computer's own system is started once to see that the game loads, and every other build says "built, not started on this computer". It never uploads anything and never signs with a key from the chat: a release (release: true) signs from environment variables the person sets themselves, needs the app's id written in game.json first (the tool gives the exact lines), and says UNSIGNED when it could not sign. The studio must be on @homie-rocks/studio ${STANDALONE_SINCE} or later AND deployed before a copy can find rooms.`,
28
+ inputSchema: {
29
+ type: 'object',
30
+ properties: {
31
+ game: str('The game\'s id (its folder in games/)'),
32
+ for: { type: 'array', items: { type: 'string', enum: [...TARGETS] }, description: 'Which to build (default: every one this computer can make)' },
33
+ plan: { type: 'boolean', description: 'Only say what would be built and what is missing; build nothing, install nothing. Do this first' },
34
+ release: { type: 'boolean', description: 'A build for a store or Steam: needs "standalone.appId" in the game\'s game.json, and signs from the environment. Only when the person asked for a release' },
35
+ ...STUDIO_ARG,
36
+ },
37
+ required: ['game'],
38
+ },
39
+ annotations: { title: 'Make a standalone game', readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
40
+ run: async (a) => {
41
+ const root = ctx.root(a.studio);
42
+ const game = String(a.game ?? '');
43
+ const only = Array.isArray(a.for) && a.for.length ? a.for.map(String).join(',') : null;
44
+ // A studio whose own toolkit is from before standalone copies has no such command: this toolkit's runs.
45
+ const old = toolkitNote(root);
46
+ if (a.plan === true) {
47
+ const r = await standalonePlan(root, game, { for: only, release: a.release === true, ...(ctx.standalone ?? {}) });
48
+ const text = [...standaloneLines(r), ...(r.missing ? ['', SAY_MISSING] : [])].join('\n').trim();
49
+ return r.ok ? ok(text, { kind: 'standalone-plan', ...r }) : fail(text, { kind: 'standalone-plan', ...r });
50
+ }
51
+ const need = needsInstall(ctx, root);
52
+ if (need) return need;
53
+ const args = ['standalone', 'build', game, ...(only ? ['--for', only] : []), ...(a.release === true ? ['--release'] : [])];
54
+ const r = await cli(ctx, root, `standalone build ${game}`, args, old ? { cli: join(PACKAGE_ROOT, 'bin', 'homie-studio.mjs') } : {});
55
+ // The words are in the result itself (`say`), so studio_job says them too when the build outlasts this call.
56
+ if (!r.ended) return stillRunning(r.job, `The standalone build of ${game} (it takes minutes; its last lines say where it is)`);
57
+ if (!r.result?.targets) return fail(`Not built: ${whyOf(r.job)}${r.result?.instead ? `\n\n${r.result.instead}` : ''}`, r.result ? { kind: 'standalone', ...r.result } : undefined);
58
+ const text = (r.result.say ?? [...standaloneLines(r.result), '', SAY_MISSING]).join('\n');
59
+ return r.result.ok ? ok(text, { kind: 'standalone', ...r.result }) : fail(text, { kind: 'standalone', ...r.result });
60
+ },
61
+ },
62
+ ];
63
+ }