@zaisma/cli 0.8.0 → 0.10.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 (2) hide show
  1. package/package.json +1 -1
  2. package/zaisma.mjs +39 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zaisma/cli",
3
- "version": "0.8.0",
3
+ "version": "0.10.0",
4
4
  "description": "Upload and publish browser games on Zaisma from the command line.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://zaisma.com",
package/zaisma.mjs CHANGED
@@ -49,6 +49,7 @@ const COMMAND_ERROR_CODES = {
49
49
  file_exists: "database export won't write over a file that's already there.",
50
50
  needs_newer_node: "Reading or writing a .sqlite file needs Node 22.13 or newer.",
51
51
  not_a_database: "The .sqlite file to import isn't a SQLite database.",
52
+ unknown_link: "links remove: the game has no link by that name.",
52
53
  server_unreachable: "Zaisma couldn't be reached: no connection, or a wrong ZAISMA_API.",
53
54
  unexpected_answer: "Zaisma answered without JSON, like a page from a network in between.",
54
55
  };
@@ -87,6 +88,13 @@ const COMMANDS = {
87
88
  arguments: [{ name: "slug" }, { name: "hosts", isRest: true, isOptional: true }],
88
89
  flags: { none: { hasValue: false, about: "Set the list to none: the game server calls no other servers" } },
89
90
  },
91
+ links: { about: "A game's registered links, which it opens with Zaisma.links.open: each name, address and who it opens for", arguments: [{ name: "slug" }], flags: {} },
92
+ "links set": {
93
+ about: "Add a link, or change the one with the same name",
94
+ arguments: [{ name: "slug" }, { name: "link_name" }, { name: "destination_url" }],
95
+ flags: { "on-game-page": { hasValue: false, about: "Show it on the game page too" } },
96
+ },
97
+ "links remove": { about: "Remove a link: the game can't open it any more", arguments: [{ name: "slug" }, { name: "link_name" }], flags: {} },
90
98
  secrets: { about: "A game's secrets, which its game server reads as server.secrets and room.secrets: their names and when each was set, never the values", arguments: [{ name: "slug" }], flags: {} },
91
99
  "secrets set": { about: "Set a secret for a game's server, replacing one of the same name: the value piped in, or typed without showing; it's never shown again", arguments: [{ name: "slug" }, { name: "secret_name" }], flags: {} },
92
100
  "secrets delete": { about: "Delete one of a game's secrets", arguments: [{ name: "slug" }, { name: "secret_name" }], flags: {} },
@@ -167,7 +175,7 @@ const COMMANDS = {
167
175
  help: { about: "This help; with --json, every command, flag, exit code and error code", arguments: [], flags: {} },
168
176
  };
169
177
  /** Commands with a second word, like zaisma forum post. */
170
- const SUBCOMMANDS = { forum: ["post", "message", "reply"], database: ["query", "batch", "export", "import"], saves: ["get", "change", "replace", "reset", "copies", "copy", "restore"], secrets: ["set", "delete"] };
178
+ const SUBCOMMANDS = { links: ["set", "remove"], forum: ["post", "message", "reply"], database: ["query", "batch", "export", "import"], saves: ["get", "change", "replace", "reset", "copies", "copy", "restore"], secrets: ["set", "delete"] };
171
179
  /** Flags every command takes. */
172
180
  const GLOBAL_FLAGS = {
173
181
  json: { hasValue: false, about: "Print the API's answer as JSON, as-is (on failure its { error }), and nothing else" },
@@ -857,6 +865,35 @@ async function allowedHosts(values, flags) {
857
865
  if (given.length || flags.none) console.log("It works within a minute.");
858
866
  }
859
867
 
868
+ /** Who a link opens for, in words (who_can_open). */
869
+ const LINK_AUDIENCE_WORDS = { everyone: "opens for everyone", signed_in_adults: "opens for signed-in adults", nobody: "opens for nobody" };
870
+
871
+ /**
872
+ * A game's registered links (links on the game), as the MCP's get_game and update_game: the API takes
873
+ * the whole list, so set and remove change it and send it back.
874
+ */
875
+ async function links(command, values, flags) {
876
+ let answer = await api("GET", `/games/${values.slug}`);
877
+ if (command !== "links") {
878
+ const kept = answer.game.links.map((link) => ({ link_name: link.link_name, destination_url: link.destination_url, is_on_game_page: link.is_on_game_page }));
879
+ const others = kept.filter((link) => link.link_name !== values.link_name);
880
+ if (command === "links remove" && others.length === kept.length) {
881
+ refuse("unknown_link", { message: `${values.slug} has no link named ${values.link_name}.`, field: "link_name", fix: `zaisma links ${values.slug} lists its links.` }, EXIT_CODES.failed);
882
+ }
883
+ const changed = command === "links set" ? kept.map((link) => (link.link_name === values.link_name ? { ...link, destination_url: values.destination_url, is_on_game_page: Boolean(flags["on-game-page"]) } : link)) : others;
884
+ if (command === "links set" && !kept.some((link) => link.link_name === values.link_name)) changed.push({ link_name: values.link_name, destination_url: values.destination_url, is_on_game_page: Boolean(flags["on-game-page"]) });
885
+ answer = await api("PATCH", `/games/${values.slug}`, { links: changed });
886
+ }
887
+ if (isJson) return printJson(answer);
888
+ const gameLinks = answer.game.links;
889
+ if (!gameLinks.length) return console.log(`${values.slug} has no links. Add one with: zaisma links set ${values.slug} store https://kit.itch.io/${values.slug}`);
890
+ for (const link of gameLinks) {
891
+ console.log(`${link.link_name.padEnd(20)} ${link.destination_url} (${LINK_AUDIENCE_WORDS[link.who_can_open]}${link.is_on_game_page ? ", on the game page" : ""})`);
892
+ if (link.review_reason) console.log(`${"".padEnd(20)} ${link.review_reason.message} ${link.review_reason.fix}`);
893
+ }
894
+ if (command === "links set") console.log(`The game opens it with Zaisma.links.open("${values.link_name}").`);
895
+ }
896
+
860
897
  /** A game's secrets (…/games/<slug>/secrets…), as the MCP's list_secrets, set_secret and delete_secret. */
861
898
  async function secrets(command, values) {
862
899
  if (command === "secrets") {
@@ -1062,6 +1099,7 @@ async function run({ command, values, flags, globalFlags, rest }) {
1062
1099
  return EXIT_CODES.done;
1063
1100
  }
1064
1101
  if (command === "allowed-hosts") await allowedHosts(values, flags);
1102
+ else if (command === "links" || command.startsWith("links ")) await links(command, values, flags);
1065
1103
  else if (command === "secrets" || command.startsWith("secrets ")) await secrets(command, values);
1066
1104
  else if (command === "database" || command.startsWith("database ")) await database(command, values, flags);
1067
1105
  else if (command === "saves" || command.startsWith("saves ")) await saves(command, values, flags);