@puzzmo/cli 1.0.51 → 1.0.52

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.
@@ -0,0 +1,239 @@
1
+ import { execFileSync } from "node:child_process"
2
+ import fs from "node:fs"
3
+ import path from "node:path"
4
+
5
+ import { findTokensForTeam, getTokens, resolveServerForTeam, sourceToURL } from "../util/config.js"
6
+ import { type DiscoveredGame, discoverGames } from "../util/discoverGames.js"
7
+ import { fetchTeamGameVersions, type GameVersions } from "../queries/gameRuntimes.js"
8
+
9
+ /** Which deployed build slot to diff against: the team's latest build, the staged next, or the previous one. */
10
+ export type ChangedAgainst = "latest" | "next" | "previous"
11
+
12
+ export type ChangedOptions = {
13
+ json?: boolean
14
+ list?: boolean
15
+ matrix?: boolean
16
+ ref?: string
17
+ against?: ChangedAgainst
18
+ includeUncommitted?: boolean
19
+ }
20
+
21
+ type GameStatus = "changed" | "unchanged" | "new"
22
+
23
+ type ChangedEntry = {
24
+ slug: string
25
+ displayName: string
26
+ teamID: string
27
+ dir: string
28
+ baseSha: string | null
29
+ ref: string
30
+ status: GameStatus
31
+ changedFiles: number
32
+ }
33
+
34
+ type EvalResult = { entry: ChangedEntry } | { error: string }
35
+
36
+ /** Reports which discovered games changed since their last deployed build, scoped per game folder. */
37
+ export const changed = async (dir: string, options: ChangedOptions = {}) => {
38
+ const { json = false, list = false, matrix = false, against = "latest", includeUncommitted = false } = options
39
+
40
+ if (getTokens().length === 0) {
41
+ console.error("Not logged in. Run `puzzmo login <token>` or set PUZZMO_TOKEN.")
42
+ process.exit(1)
43
+ }
44
+
45
+ const rootDir = path.resolve(dir)
46
+ if (!fs.existsSync(rootDir)) {
47
+ console.error(`Directory not found: ${dir}`)
48
+ process.exit(1)
49
+ }
50
+
51
+ const repoRoot = getRepoRoot(rootDir)
52
+ if (!repoRoot) {
53
+ console.error(`Not a git repository: ${rootDir}`)
54
+ process.exit(1)
55
+ }
56
+
57
+ const ref = options.ref || "HEAD"
58
+ const refSha = revParseShort(ref, repoRoot)
59
+ if (!refSha) {
60
+ console.error(`Could not resolve git ref: ${ref}`)
61
+ process.exit(1)
62
+ }
63
+
64
+ const { games, errors: discoveryErrors } = await discoverGames(rootDir, { requireDist: false })
65
+ if (!games.length && !discoveryErrors.length) {
66
+ console.error(`No puzzmo.json files found under ${rootDir}`)
67
+ process.exit(1)
68
+ }
69
+
70
+ const hardErrors = discoveryErrors.map((err) => `${err.slug ?? path.relative(rootDir, err.puzzmoJsonPath)}: ${err.errors.join("; ")}`)
71
+
72
+ const teamVersions = new Map<string, Promise<Map<string, GameVersions>>>()
73
+ const results = await Promise.all(
74
+ games.map((game) => evaluateGame(game, { repoRoot, ref, refSha, against, includeUncommitted, teamVersions })),
75
+ )
76
+ const report: ChangedEntry[] = []
77
+ for (const result of results) {
78
+ if ("entry" in result) report.push(result.entry)
79
+ else hardErrors.push(result.error)
80
+ }
81
+
82
+ const buildable = report.filter((entry) => entry.status !== "unchanged")
83
+
84
+ if (json) console.log(JSON.stringify(report, null, 2))
85
+ else if (matrix) console.log(JSON.stringify({ include: buildable.map((entry) => ({ dir: entry.dir, slug: entry.slug })) }))
86
+ else if (list) for (const entry of buildable) console.log(entry.dir)
87
+ else if (report.length) printTable(report)
88
+ else if (!hardErrors.length) console.log(`No puzzmo.json files found under ${rootDir}`)
89
+
90
+ if (hardErrors.length) {
91
+ console.error(`\n${hardErrors.length} error${hardErrors.length === 1 ? "" : "s"}:`)
92
+ for (const message of hardErrors) console.error(` ${message}`)
93
+ process.exit(1)
94
+ }
95
+ }
96
+
97
+ type EvalContext = {
98
+ repoRoot: string
99
+ ref: string
100
+ refSha: string
101
+ against: ChangedAgainst
102
+ includeUncommitted: boolean
103
+ // Memoizes the per-team runtime fetch so games sharing a team only hit the API once.
104
+ teamVersions: Map<string, Promise<Map<string, GameVersions>>>
105
+ }
106
+
107
+ /** Resolves one game's deployed SHA and diffs it against the ref. Returns either a report entry or a hard error. */
108
+ const evaluateGame = async (game: DiscoveredGame, ctx: EvalContext): Promise<EvalResult> => {
109
+ const { slug, teamID } = game.puzzmoFile.game
110
+ const dir = toPosix(path.relative(ctx.repoRoot, game.puzzmoJsonDir)) || "."
111
+
112
+ const credential = await resolveServerForTeam(teamID)
113
+ if (!credential) {
114
+ const matches = findTokensForTeam(teamID)
115
+ const message =
116
+ matches.length === 0
117
+ ? `No saved token for team ${teamID}. Run \`puzzmo login <token>\`.`
118
+ : `Token for team ${teamID} is registered against ${matches.map((m) => m.source).join(", ")} but none of those servers are reachable.`
119
+ return { error: `${slug}: ${message}` }
120
+ }
121
+
122
+ let versions: GameVersions | undefined
123
+ try {
124
+ versions = (await getTeamVersions(teamID, credential, ctx)).get(slug)
125
+ } catch (e) {
126
+ return { error: `${slug}: ${e instanceof Error ? e.message : String(e)}` }
127
+ }
128
+
129
+ const baseSha = pickBaseSha(versions ?? null, ctx.against)
130
+
131
+ // Never deployed (or the chosen slot is empty) — nothing to diff against, so it's new and buildable.
132
+ if (!baseSha) return { entry: makeEntry(game, dir, null, ctx.refSha, "new", 0) }
133
+
134
+ // The deployed commit has to exist locally or we can't diff — fail loudly rather than guessing.
135
+ if (!commitExists(baseSha, ctx.repoRoot))
136
+ return {
137
+ error: `${slug}: deployed commit ${baseSha} not found locally. Fetch full git history (e.g. actions/checkout fetch-depth: 0).`,
138
+ }
139
+
140
+ const changedFiles = countChanges(baseSha, ctx.ref, game.puzzmoJsonDir, ctx.includeUncommitted)
141
+ return { entry: makeEntry(game, dir, baseSha, ctx.refSha, changedFiles > 0 ? "changed" : "unchanged", changedFiles) }
142
+ }
143
+
144
+ const makeEntry = (
145
+ game: DiscoveredGame,
146
+ dir: string,
147
+ baseSha: string | null,
148
+ ref: string,
149
+ status: GameStatus,
150
+ changedFiles: number,
151
+ ): ChangedEntry => ({
152
+ slug: game.puzzmoFile.game.slug,
153
+ displayName: game.puzzmoFile.game.displayName,
154
+ teamID: game.puzzmoFile.game.teamID,
155
+ dir,
156
+ baseSha,
157
+ ref,
158
+ status,
159
+ changedFiles,
160
+ })
161
+
162
+ /** Fetches a team's per-game runtime SHAs once, memoizing the in-flight promise so concurrent games share the call. */
163
+ const getTeamVersions = (
164
+ teamID: string,
165
+ credential: { source: string; token: string },
166
+ ctx: EvalContext,
167
+ ): Promise<Map<string, GameVersions>> => {
168
+ let pending = ctx.teamVersions.get(teamID)
169
+ if (!pending) {
170
+ pending = fetchTeamGameVersions(sourceToURL(credential.source), credential.token)
171
+ ctx.teamVersions.set(teamID, pending)
172
+ }
173
+ return pending
174
+ }
175
+
176
+ /** Picks the baseline SHA for the chosen slot. `latest` is the most recent build the team has — the staged next, else the live current. */
177
+ const pickBaseSha = (versions: GameVersions | null, against: ChangedAgainst): string | null => {
178
+ if (!versions) return null
179
+ if (against === "next") return versions.next
180
+ if (against === "previous") return versions.previous
181
+ return versions.next ?? versions.current
182
+ }
183
+
184
+ /** Counts files differing under `dir` between `base` and `ref`, optionally folding in uncommitted working-tree changes. */
185
+ const countChanges = (base: string, ref: string, dir: string, includeUncommitted: boolean): number => {
186
+ const files = new Set(gitLines(["diff", "--name-only", `${base}..${ref}`, "--", "."], dir))
187
+ if (includeUncommitted) {
188
+ for (const file of gitLines(["diff", "--name-only", "--", "."], dir)) files.add(file) // unstaged
189
+ for (const file of gitLines(["diff", "--name-only", "--cached", "--", "."], dir)) files.add(file) // staged
190
+ for (const file of gitLines(["ls-files", "--others", "--exclude-standard", "--", "."], dir)) files.add(file) // untracked
191
+ }
192
+ return files.size
193
+ }
194
+
195
+ /** Prints the human-readable summary table. */
196
+ const printTable = (report: ChangedEntry[]) => {
197
+ const headers = ["GAME", "STATUS", "BASE", "FILES"]
198
+ const rows = report.map((entry) => [entry.slug, entry.status, entry.baseSha ?? "—", String(entry.changedFiles)])
199
+ const widths = headers.map((header, i) => Math.max(header.length, ...rows.map((row) => row[i].length)))
200
+ const format = (cols: string[]) => cols.map((col, i) => col.padEnd(widths[i])).join(" ")
201
+
202
+ console.log(format(headers))
203
+ for (const row of rows) console.log(format(row))
204
+
205
+ const changedCount = report.filter((entry) => entry.status !== "unchanged").length
206
+ console.log(`\n${changedCount} of ${report.length} game${report.length === 1 ? "" : "s"} changed.`)
207
+ }
208
+
209
+ const getRepoRoot = (cwd: string): string | null => tryGit(["rev-parse", "--show-toplevel"], cwd)
210
+
211
+ const revParseShort = (ref: string, cwd: string): string | null => tryGit(["rev-parse", "--short", ref], cwd)
212
+
213
+ /** True if `sha` resolves to a commit object in the repo at `cwd`. */
214
+ const commitExists = (sha: string, cwd: string): boolean => {
215
+ try {
216
+ execFileSync("git", ["rev-parse", "--verify", "--quiet", `${sha}^{commit}`], { cwd, stdio: "ignore" })
217
+ return true
218
+ } catch {
219
+ return false
220
+ }
221
+ }
222
+
223
+ /** Runs a git command and returns its trimmed stdout, or null if it failed (git's own stderr is suppressed). */
224
+ const tryGit = (args: string[], cwd: string): string | null => {
225
+ try {
226
+ return execFileSync("git", args, { encoding: "utf-8", cwd, stdio: ["ignore", "pipe", "ignore"] }).trim()
227
+ } catch {
228
+ return null
229
+ }
230
+ }
231
+
232
+ /** Runs a git command that emits one file path per line and returns the non-empty lines, or [] if it failed. */
233
+ const gitLines = (args: string[], cwd: string): string[] => {
234
+ const out = tryGit(args, cwd)
235
+ if (out === null) return []
236
+ return out.split("\n").filter(Boolean)
237
+ }
238
+
239
+ const toPosix = (p: string): string => p.split(path.sep).join("/")
@@ -3,7 +3,7 @@ import path from "node:path"
3
3
 
4
4
  import { discoverGames } from "../util/discoverGames.js"
5
5
 
6
- /** CLI command: puzzmo validate [dir] — discovers every puzzmo.json under dir and validates each */
6
+ /** CLI command: puzzmo games validate [dir] — discovers every puzzmo.json under dir and validates each */
7
7
  export const validate = async (dir: string) => {
8
8
  const rootDir = path.resolve(dir)
9
9
  if (!fs.existsSync(rootDir)) {
package/src/index.ts CHANGED
@@ -3,6 +3,7 @@
3
3
  import { defineCommand, runMain } from "citty"
4
4
 
5
5
  import { agentTest } from "./commands/agent-test.js"
6
+ import { type ChangedAgainst, changed } from "./commands/changed.js"
6
7
  import { gameCreate } from "./commands/game/create.js"
7
8
  import { login } from "./commands/login.js"
8
9
  import { migrate } from "./commands/migrate.js"
@@ -46,6 +47,38 @@ const validateCommand = defineCommand({
46
47
  run: ({ args }) => validate(args.dir),
47
48
  })
48
49
 
50
+ const changedCommand = defineCommand({
51
+ meta: { name: "changed", description: "Report which games changed since their last deployed build (for CI)." },
52
+ args: {
53
+ dir: { type: "positional", description: "Directory to scan", required: false, default: "." },
54
+ json: { type: "boolean", description: "Output a JSON report for every discovered game" },
55
+ list: { type: "boolean", description: "Output only the changed game folders, one per line" },
56
+ matrix: { type: "boolean", description: "Output a GitHub Actions matrix of the changed games" },
57
+ ref: { type: "string", description: "Git ref to compare against (default HEAD)" },
58
+ against: {
59
+ type: "enum",
60
+ options: ["latest", "next", "previous"],
61
+ description: "Which deployed build to use as the baseline (default latest)",
62
+ },
63
+ "include-uncommitted": { type: "boolean", description: "Also count uncommitted working-tree changes" },
64
+ },
65
+ run: ({ args }) =>
66
+ changed(args.dir, {
67
+ json: args.json,
68
+ list: args.list,
69
+ matrix: args.matrix,
70
+ ref: args.ref,
71
+ against: args.against as ChangedAgainst | undefined,
72
+ includeUncommitted: args["include-uncommitted"],
73
+ }),
74
+ })
75
+
76
+ // Commands that operate across every puzzmo.json in a repo live under `puzzmo games`.
77
+ const gamesCommand = defineCommand({
78
+ meta: { name: "games", description: "Commands that run across all the games in your repo" },
79
+ subCommands: { changed: changedCommand, upload: uploadCommand, validate: validateCommand },
80
+ })
81
+
49
82
  const migrateCommand = defineCommand({
50
83
  meta: { name: "migrate", description: "List and select migration skills from dev.puzzmo.com" },
51
84
  run: () => migrate(),
@@ -83,14 +116,37 @@ const gameCommand = defineCommand({
83
116
  subCommands: { create: gameCreateCommand },
84
117
  })
85
118
 
119
+ /** Warn that a top-level command moved under `puzzmo games`, then run it anyway. */
120
+ const movedToGames = (name: string) => console.warn(`"puzzmo ${name}" is now "puzzmo games ${name}". The old form still works for now.`)
121
+
122
+ // Deprecated top-level aliases, kept hidden so existing scripts (e.g. `puzzmo upload`) keep working.
123
+ const uploadAlias = defineCommand({
124
+ meta: { ...uploadCommand.meta, hidden: true },
125
+ args: uploadCommand.args,
126
+ run: ({ args }) => {
127
+ movedToGames("upload")
128
+ return upload(args.dir, { verbose: args.verbose })
129
+ },
130
+ })
131
+
132
+ const validateAlias = defineCommand({
133
+ meta: { ...validateCommand.meta, hidden: true },
134
+ args: validateCommand.args,
135
+ run: ({ args }) => {
136
+ movedToGames("validate")
137
+ return validate(args.dir)
138
+ },
139
+ })
140
+
86
141
  const main = defineCommand({
87
142
  meta: { name: "puzzmo", description: "Puzzmo CLI" },
88
143
  subCommands: {
89
144
  login: loginCommand,
90
- upload: uploadCommand,
91
- validate: validateCommand,
145
+ games: gamesCommand,
92
146
  migrate: migrateCommand,
93
147
  game: gameCommand,
148
+ upload: uploadAlias,
149
+ validate: validateAlias,
94
150
  "agent-test": defineCommand({
95
151
  meta: { name: "agent-test", description: "Run the agent test harness", hidden: true },
96
152
  run: () => agentTest(),
@@ -0,0 +1,301 @@
1
+ /**
2
+ * @generated SignedSource<<5f5ceed697fe8720947305fca5196bee>>
3
+ * @lightSyntaxTransform
4
+ */
5
+
6
+ /* tslint:disable */
7
+ /* eslint-disable */
8
+ // @ts-nocheck
9
+
10
+ import { ConcreteRequest } from 'relay-runtime';
11
+ export type cliGameRuntimesQuery$variables = {
12
+ token: string;
13
+ };
14
+ export type cliGameRuntimesQuery$data = {
15
+ readonly teamForToken: {
16
+ readonly games: {
17
+ readonly edges: ReadonlyArray<{
18
+ readonly node: {
19
+ readonly runtime: {
20
+ readonly currentVersion: {
21
+ readonly assetsSha: string;
22
+ } | null | undefined;
23
+ readonly nextVersion: {
24
+ readonly assetsSha: string;
25
+ } | null | undefined;
26
+ readonly previousVersion: {
27
+ readonly assetsSha: string;
28
+ } | null | undefined;
29
+ } | null | undefined;
30
+ readonly slug: string;
31
+ };
32
+ } | null | undefined> | null | undefined;
33
+ };
34
+ readonly slug: string;
35
+ } | null | undefined;
36
+ };
37
+ export type cliGameRuntimesQuery = {
38
+ response: cliGameRuntimesQuery$data;
39
+ variables: cliGameRuntimesQuery$variables;
40
+ };
41
+
42
+ const node: ConcreteRequest = (function(){
43
+ var v0 = [
44
+ {
45
+ "defaultValue": null,
46
+ "kind": "LocalArgument",
47
+ "name": "token"
48
+ }
49
+ ],
50
+ v1 = [
51
+ {
52
+ "kind": "Variable",
53
+ "name": "token",
54
+ "variableName": "token"
55
+ }
56
+ ],
57
+ v2 = {
58
+ "alias": null,
59
+ "args": null,
60
+ "kind": "ScalarField",
61
+ "name": "slug",
62
+ "storageKey": null
63
+ },
64
+ v3 = [
65
+ {
66
+ "kind": "Literal",
67
+ "name": "first",
68
+ "value": 500
69
+ }
70
+ ],
71
+ v4 = {
72
+ "alias": null,
73
+ "args": null,
74
+ "kind": "ScalarField",
75
+ "name": "assetsSha",
76
+ "storageKey": null
77
+ },
78
+ v5 = [
79
+ (v4/*:: as any*/)
80
+ ],
81
+ v6 = {
82
+ "alias": null,
83
+ "args": null,
84
+ "kind": "ScalarField",
85
+ "name": "id",
86
+ "storageKey": null
87
+ },
88
+ v7 = [
89
+ (v4/*:: as any*/),
90
+ (v6/*:: as any*/)
91
+ ];
92
+ return {
93
+ "fragment": {
94
+ "argumentDefinitions": (v0/*:: as any*/),
95
+ "kind": "Fragment",
96
+ "metadata": null,
97
+ "name": "cliGameRuntimesQuery",
98
+ "selections": [
99
+ {
100
+ "alias": null,
101
+ "args": (v1/*:: as any*/),
102
+ "concreteType": "Team",
103
+ "kind": "LinkedField",
104
+ "name": "teamForToken",
105
+ "plural": false,
106
+ "selections": [
107
+ (v2/*:: as any*/),
108
+ {
109
+ "alias": null,
110
+ "args": (v3/*:: as any*/),
111
+ "concreteType": "GameConnection",
112
+ "kind": "LinkedField",
113
+ "name": "games",
114
+ "plural": false,
115
+ "selections": [
116
+ {
117
+ "alias": null,
118
+ "args": null,
119
+ "concreteType": "GameEdge",
120
+ "kind": "LinkedField",
121
+ "name": "edges",
122
+ "plural": true,
123
+ "selections": [
124
+ {
125
+ "alias": null,
126
+ "args": null,
127
+ "concreteType": "Game",
128
+ "kind": "LinkedField",
129
+ "name": "node",
130
+ "plural": false,
131
+ "selections": [
132
+ (v2/*:: as any*/),
133
+ {
134
+ "alias": null,
135
+ "args": null,
136
+ "concreteType": "GameRuntime",
137
+ "kind": "LinkedField",
138
+ "name": "runtime",
139
+ "plural": false,
140
+ "selections": [
141
+ {
142
+ "alias": null,
143
+ "args": null,
144
+ "concreteType": "GameRuntimeVersion",
145
+ "kind": "LinkedField",
146
+ "name": "currentVersion",
147
+ "plural": false,
148
+ "selections": (v5/*:: as any*/),
149
+ "storageKey": null
150
+ },
151
+ {
152
+ "alias": null,
153
+ "args": null,
154
+ "concreteType": "GameRuntimeVersion",
155
+ "kind": "LinkedField",
156
+ "name": "nextVersion",
157
+ "plural": false,
158
+ "selections": (v5/*:: as any*/),
159
+ "storageKey": null
160
+ },
161
+ {
162
+ "alias": null,
163
+ "args": null,
164
+ "concreteType": "GameRuntimeVersion",
165
+ "kind": "LinkedField",
166
+ "name": "previousVersion",
167
+ "plural": false,
168
+ "selections": (v5/*:: as any*/),
169
+ "storageKey": null
170
+ }
171
+ ],
172
+ "storageKey": null
173
+ }
174
+ ],
175
+ "storageKey": null
176
+ }
177
+ ],
178
+ "storageKey": null
179
+ }
180
+ ],
181
+ "storageKey": "games(first:500)"
182
+ }
183
+ ],
184
+ "storageKey": null
185
+ }
186
+ ],
187
+ "type": "Query",
188
+ "abstractKey": null
189
+ },
190
+ "kind": "Request",
191
+ "operation": {
192
+ "argumentDefinitions": (v0/*:: as any*/),
193
+ "kind": "Operation",
194
+ "name": "cliGameRuntimesQuery",
195
+ "selections": [
196
+ {
197
+ "alias": null,
198
+ "args": (v1/*:: as any*/),
199
+ "concreteType": "Team",
200
+ "kind": "LinkedField",
201
+ "name": "teamForToken",
202
+ "plural": false,
203
+ "selections": [
204
+ (v2/*:: as any*/),
205
+ {
206
+ "alias": null,
207
+ "args": (v3/*:: as any*/),
208
+ "concreteType": "GameConnection",
209
+ "kind": "LinkedField",
210
+ "name": "games",
211
+ "plural": false,
212
+ "selections": [
213
+ {
214
+ "alias": null,
215
+ "args": null,
216
+ "concreteType": "GameEdge",
217
+ "kind": "LinkedField",
218
+ "name": "edges",
219
+ "plural": true,
220
+ "selections": [
221
+ {
222
+ "alias": null,
223
+ "args": null,
224
+ "concreteType": "Game",
225
+ "kind": "LinkedField",
226
+ "name": "node",
227
+ "plural": false,
228
+ "selections": [
229
+ (v2/*:: as any*/),
230
+ {
231
+ "alias": null,
232
+ "args": null,
233
+ "concreteType": "GameRuntime",
234
+ "kind": "LinkedField",
235
+ "name": "runtime",
236
+ "plural": false,
237
+ "selections": [
238
+ {
239
+ "alias": null,
240
+ "args": null,
241
+ "concreteType": "GameRuntimeVersion",
242
+ "kind": "LinkedField",
243
+ "name": "currentVersion",
244
+ "plural": false,
245
+ "selections": (v7/*:: as any*/),
246
+ "storageKey": null
247
+ },
248
+ {
249
+ "alias": null,
250
+ "args": null,
251
+ "concreteType": "GameRuntimeVersion",
252
+ "kind": "LinkedField",
253
+ "name": "nextVersion",
254
+ "plural": false,
255
+ "selections": (v7/*:: as any*/),
256
+ "storageKey": null
257
+ },
258
+ {
259
+ "alias": null,
260
+ "args": null,
261
+ "concreteType": "GameRuntimeVersion",
262
+ "kind": "LinkedField",
263
+ "name": "previousVersion",
264
+ "plural": false,
265
+ "selections": (v7/*:: as any*/),
266
+ "storageKey": null
267
+ },
268
+ (v6/*:: as any*/)
269
+ ],
270
+ "storageKey": null
271
+ },
272
+ (v6/*:: as any*/)
273
+ ],
274
+ "storageKey": null
275
+ }
276
+ ],
277
+ "storageKey": null
278
+ }
279
+ ],
280
+ "storageKey": "games(first:500)"
281
+ },
282
+ (v6/*:: as any*/)
283
+ ],
284
+ "storageKey": null
285
+ }
286
+ ]
287
+ },
288
+ "params": {
289
+ "cacheID": "169b76dbd259ac615d2b712641a54087",
290
+ "id": null,
291
+ "metadata": {},
292
+ "name": "cliGameRuntimesQuery",
293
+ "operationKind": "query",
294
+ "text": "query cliGameRuntimesQuery(\n $token: String!\n) {\n teamForToken(token: $token) {\n slug\n games(first: 500) {\n edges {\n node {\n slug\n runtime {\n currentVersion {\n assetsSha\n id\n }\n nextVersion {\n assetsSha\n id\n }\n previousVersion {\n assetsSha\n id\n }\n id\n }\n id\n }\n }\n }\n id\n }\n}\n"
295
+ }
296
+ };
297
+ })();
298
+
299
+ (node as any).hash = "6467f74d92adb125c762ed24be7d4ac8";
300
+
301
+ export default node;