@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,64 @@
1
+ import { graphql, query } from "../util/belay.js"
2
+ import type { cliGameRuntimesQuery } from "./__generated__/cliGameRuntimesQuery.graphql.js"
3
+
4
+ const gameRuntimesQuery = graphql`
5
+ query cliGameRuntimesQuery($token: String!) {
6
+ teamForToken(token: $token) {
7
+ slug
8
+ games(first: 500) {
9
+ edges {
10
+ node {
11
+ slug
12
+ runtime {
13
+ currentVersion {
14
+ assetsSha
15
+ }
16
+ nextVersion {
17
+ assetsSha
18
+ }
19
+ previousVersion {
20
+ assetsSha
21
+ }
22
+ }
23
+ }
24
+ }
25
+ }
26
+ }
27
+ }
28
+ `
29
+
30
+ /** The deployed runtime build SHAs for a game, read from its runtime's current/next/previous version slots. */
31
+ export type GameVersions = {
32
+ /** SHA of the live/current runtime build */
33
+ current: string | null
34
+ /** SHA of the staged "next" runtime build, if one exists */
35
+ next: string | null
36
+ /** SHA of the previous runtime build, if one exists */
37
+ previous: string | null
38
+ }
39
+
40
+ /**
41
+ * Fetches the runtime build SHAs for every game in the token's team, keyed by game slug. The token is sent both as
42
+ * the `teamForToken` argument and as the Bearer header — the latter is what unlocks the team-gated `runtime` field.
43
+ */
44
+ export const fetchTeamGameVersions = async (apiURL: string, token: string): Promise<Map<string, GameVersions>> => {
45
+ const { data, errors } = await query<cliGameRuntimesQuery>(gameRuntimesQuery, {
46
+ variables: { token },
47
+ url: `${apiURL}/graphql`,
48
+ headers: { Authorization: `Bearer ${token}` },
49
+ })
50
+
51
+ if (errors?.length) throw new Error(`teamForToken failed: ${errors.map((e) => e.message).join("; ")}`)
52
+
53
+ const versions = new Map<string, GameVersions>()
54
+ for (const edge of data?.teamForToken?.games?.edges ?? []) {
55
+ const game = edge?.node
56
+ if (!game) continue
57
+ versions.set(game.slug, {
58
+ current: game.runtime?.currentVersion?.assetsSha || null,
59
+ next: game.runtime?.nextVersion?.assetsSha || null,
60
+ previous: game.runtime?.previousVersion?.assetsSha || null,
61
+ })
62
+ }
63
+ return versions
64
+ }
@@ -0,0 +1,56 @@
1
+ // "belay lite" — a local copy of the parts of @puzzmo-com/belay the CLI needs.
2
+ // The CLI is published to npm and ships unbundled, so it can't depend on the private,
3
+ // source-only belay package; this vendors the tiny query/graphql helpers instead.
4
+ // The `graphql` tag lets the Relay compiler validate these queries and generate types
5
+ // (see the "cli" project in relay.config.json). Keep this in sync with packages/belay.
6
+
7
+ /** Standard GraphQL error shape */
8
+ export type GraphQLError = {
9
+ message: string
10
+ locations?: Array<{ line: number; column: number }>
11
+ path?: Array<string | number>
12
+ extensions?: Record<string, unknown>
13
+ }
14
+
15
+ /** GraphQL response shape */
16
+ export type GraphQLResponse<TData> = {
17
+ data?: TData
18
+ errors?: GraphQLError[]
19
+ }
20
+
21
+ /** Operation type with variables and response — matches Relay's generated operation types */
22
+ export type OperationType = {
23
+ variables: Record<string, unknown>
24
+ response: unknown
25
+ }
26
+
27
+ /** Request options for the query function */
28
+ export type RequestOptions<TVariables> = {
29
+ variables: TVariables
30
+ url: string
31
+ headers?: HeadersInit
32
+ signal?: AbortSignal
33
+ }
34
+
35
+ /**
36
+ * A graphql tagged template literal that returns the query string. This lets the Relay
37
+ * compiler pick up and validate the operation (and generate its types) without bundling relay-runtime.
38
+ */
39
+ export const graphql = (strings: TemplateStringsArray): string => strings[0]!
40
+
41
+ /** Execute a GraphQL query via fetch, typed with a Relay-generated operation type. */
42
+ export const query = async <T extends OperationType>(
43
+ queryString: string,
44
+ options: RequestOptions<T["variables"]>,
45
+ ): Promise<GraphQLResponse<T["response"]>> => {
46
+ const { variables, url, headers, signal } = options
47
+
48
+ const response = await fetch(url, {
49
+ method: "POST",
50
+ headers: { "Content-Type": "application/json", ...headers },
51
+ signal,
52
+ body: JSON.stringify({ query: queryString, variables }),
53
+ })
54
+
55
+ return response.json() as Promise<GraphQLResponse<T["response"]>>
56
+ }
@@ -20,7 +20,10 @@ export type DiscoveredGame = {
20
20
  puzzmoJsonDir: string
21
21
  /** Validated puzzmo.json contents */
22
22
  puzzmoFile: PuzzmoFile
23
- /** Absolute path to the dist directory containing the build artifacts */
23
+ /**
24
+ * Absolute path to the dist directory containing the build artifacts (empty string when discovered with requireDist: false and none was
25
+ * found)
26
+ */
24
27
  distDir: string
25
28
  }
26
29
 
@@ -37,8 +40,15 @@ export type DiscoveryResult = {
37
40
  errors: DiscoveryError[]
38
41
  }
39
42
 
43
+ /** Options for {@link discoverGames} */
44
+ export type DiscoverOptions = {
45
+ /** Require each game to have a non-empty dist folder (default true). Set false for commands that run before a build. */
46
+ requireDist?: boolean
47
+ }
48
+
40
49
  /** Walks `rootDir` looking for puzzmo.json files, validates each, and resolves their dist directory */
41
- export const discoverGames = async (rootDir: string): Promise<DiscoveryResult> => {
50
+ export const discoverGames = async (rootDir: string, options: DiscoverOptions = {}): Promise<DiscoveryResult> => {
51
+ const { requireDist = true } = options
42
52
  const root = path.resolve(rootDir)
43
53
  const puzzmoJsonPaths = findPuzzmoJsonFiles(root)
44
54
 
@@ -66,18 +76,20 @@ export const discoverGames = async (rootDir: string): Promise<DiscoveryResult> =
66
76
  const puzzmoJsonDir = path.dirname(puzzmoJsonPath)
67
77
  const distDir = resolveDistDir(puzzmoFile, puzzmoJsonDir, root)
68
78
 
69
- if (!distDir) {
70
- fileErrors.push(`Could not find a dist/build folder for ${puzzmoFile.game.slug}. Set "output.dir" in puzzmo.json.`)
71
- } else if (!hasFiles(distDir)) {
72
- fileErrors.push(`Dist folder is empty: ${distDir}`)
73
- }
79
+ if (requireDist) {
80
+ if (!distDir) {
81
+ fileErrors.push(`Could not find a dist/build folder for ${puzzmoFile.game.slug}. Set "output.dir" in puzzmo.json.`)
82
+ } else if (!hasFiles(distDir)) {
83
+ fileErrors.push(`Dist folder is empty: ${distDir}`)
84
+ }
74
85
 
75
- if (fileErrors.length) {
76
- errors.push({ puzzmoJsonPath, slug: puzzmoFile.game.slug, errors: fileErrors })
77
- continue
86
+ if (fileErrors.length) {
87
+ errors.push({ puzzmoJsonPath, slug: puzzmoFile.game.slug, errors: fileErrors })
88
+ continue
89
+ }
78
90
  }
79
91
 
80
- games.push({ puzzmoJsonPath, puzzmoJsonDir, puzzmoFile, distDir: distDir as string })
92
+ games.push({ puzzmoJsonPath, puzzmoJsonDir, puzzmoFile, distDir: distDir ?? "" })
81
93
  }
82
94
 
83
95
  return { games, errors }
@@ -15,5 +15,5 @@ Edit puzzles in `fixtures/puzzles/`. Game logic is in `src/main.ts`.
15
15
  ## Deployment
16
16
 
17
17
  ```
18
- yarn puzzmo upload
18
+ yarn puzzmo games upload
19
19
  ```