@puzzmo/cli 1.0.29 → 1.0.31

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.
package/README.md CHANGED
@@ -18,15 +18,15 @@ Save a CLI auth token. Generate one from studio.puzzmo.com.
18
18
  puzzmo login pzt-your-token-here
19
19
  ```
20
20
 
21
- ### `puzzmo upload <slug> <dir>`
21
+ ### `puzzmo upload <dir>`
22
22
 
23
- Upload a game build directory to Puzzmo.
23
+ Upload a game build directory to Puzzmo. The game slug is read from `puzzmo.json` in the build directory.
24
24
 
25
25
  ```bash
26
- puzzmo upload my-game dist/
26
+ puzzmo upload dist/
27
27
  ```
28
28
 
29
- The command collects all files in the directory, computes a SHA from git (or file contents), and uploads them in batches.
29
+ The command collects all files in the directory, validates the `puzzmo.json`, computes a SHA from git (or file contents), and uploads them in batches.
30
30
 
31
31
  ### `puzzmo game create [token]`
32
32
 
@@ -1,2 +1,2 @@
1
1
  /** Uploads game build artifacts to Puzzmo */
2
- export declare const upload: (gameSlug: string, dir: string) => Promise<void>;
2
+ export declare const upload: (dir: string) => Promise<void>;
@@ -4,6 +4,7 @@ import fs from "node:fs";
4
4
  import path from "node:path";
5
5
  import { uploadFiles } from "../util/api.js";
6
6
  import { getAPIURL, getToken } from "../util/config.js";
7
+ import { validatePuzzmoJson } from "../util/validatePuzzmoFile.js";
7
8
  /** Collects all files in a directory recursively */
8
9
  const collectFiles = (dir) => {
9
10
  const entries = fs.readdirSync(dir, { withFileTypes: true });
@@ -43,7 +44,7 @@ const formatBytes = (bytes) => {
43
44
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
44
45
  };
45
46
  /** Uploads game build artifacts to Puzzmo */
46
- export const upload = async (gameSlug, dir) => {
47
+ export const upload = async (dir) => {
47
48
  const token = getToken();
48
49
  if (!token) {
49
50
  console.error("Not logged in. Run `puzzmo login <token>` or set PUZZMO_TOKEN.");
@@ -59,6 +60,30 @@ export const upload = async (gameSlug, dir) => {
59
60
  console.error(`Directory is empty: ${dir}`);
60
61
  process.exit(1);
61
62
  }
63
+ // Require and validate puzzmo.json
64
+ const puzzmoJsonPath = path.join(distDir, "puzzmo.json");
65
+ if (!fs.existsSync(puzzmoJsonPath)) {
66
+ console.error(`Missing puzzmo.json in ${dir}`);
67
+ console.error("Every game upload must include a puzzmo.json file.");
68
+ process.exit(1);
69
+ }
70
+ let parsed;
71
+ try {
72
+ parsed = JSON.parse(fs.readFileSync(puzzmoJsonPath, "utf-8"));
73
+ }
74
+ catch (e) {
75
+ console.error(`Invalid puzzmo.json: ${e instanceof Error ? e.message : e}`);
76
+ process.exit(1);
77
+ }
78
+ const validation = await validatePuzzmoJson(parsed);
79
+ if (!validation.valid) {
80
+ console.error(`Invalid puzzmo.json:\n`);
81
+ for (const err of validation.errors)
82
+ console.error(` ${err}`);
83
+ process.exit(1);
84
+ }
85
+ const puzzmoFile = validation.data;
86
+ const gameSlug = puzzmoFile.game.slug;
62
87
  // Determine SHA
63
88
  const sha = getGitSHA() || hashFiles(files);
64
89
  console.log(`\nUploading ${gameSlug} (${sha.slice(0, 8)})`);
@@ -78,7 +103,7 @@ export const upload = async (gameSlug, dir) => {
78
103
  console.log(`Uploading to ${apiURL}...`);
79
104
  else
80
105
  console.log("Uploading...");
81
- const result = await uploadFiles(token, gameSlug, sha, files, distDir, (batch, totalBatches, uploaded) => {
106
+ const result = await uploadFiles(token, gameSlug, sha, files, distDir, puzzmoFile, (batch, totalBatches, uploaded) => {
82
107
  console.log(` Batch ${batch}/${totalBatches} done (${uploaded} file(s) uploaded)`);
83
108
  });
84
109
  console.log(`\nDone - ${result.versionID}`);
@@ -1 +1 @@
1
- {"version":3,"file":"upload.js","sourceRoot":"","sources":["../../src/commands/upload.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"upload.js","sourceRoot":"","sources":["../../src/commands/upload.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ /** CLI command: puzzmo validate [dir] */
2
+ export declare const validate: (dir: string) => Promise<void>;
@@ -0,0 +1,43 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { validatePuzzmoJson } from "../util/validatePuzzmoFile.js";
4
+ /** CLI command: puzzmo validate [dir] */
5
+ export const validate = async (dir) => {
6
+ const resolvedDir = path.resolve(dir);
7
+ const puzzmoJsonPath = path.join(resolvedDir, "puzzmo.json");
8
+ if (!fs.existsSync(puzzmoJsonPath)) {
9
+ console.error(`No puzzmo.json found in ${dir}`);
10
+ process.exit(1);
11
+ }
12
+ let raw;
13
+ try {
14
+ raw = fs.readFileSync(puzzmoJsonPath, "utf-8");
15
+ }
16
+ catch (e) {
17
+ console.error(`Could not read puzzmo.json: ${e instanceof Error ? e.message : e}`);
18
+ process.exit(1);
19
+ }
20
+ let data;
21
+ try {
22
+ data = JSON.parse(raw);
23
+ }
24
+ catch (e) {
25
+ console.error(`Invalid JSON in puzzmo.json: ${e instanceof Error ? e.message : e}`);
26
+ process.exit(1);
27
+ }
28
+ const result = await validatePuzzmoJson(data);
29
+ if (!result.valid) {
30
+ console.error(`puzzmo.json has ${result.errors.length} error(s):\n`);
31
+ for (const err of result.errors)
32
+ console.error(` ${err}`);
33
+ process.exit(1);
34
+ }
35
+ const { data: puzzmoFile } = result;
36
+ console.log(`Valid puzzmo.json for "${puzzmoFile.game.displayName}" (${puzzmoFile.game.slug})`);
37
+ if (puzzmoFile.integrations) {
38
+ const keys = Object.keys(puzzmoFile.integrations);
39
+ if (keys.length)
40
+ console.log(`Integrations: ${keys.join(", ")}`);
41
+ }
42
+ };
43
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/commands/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAA;AACxB,OAAO,IAAI,MAAM,WAAW,CAAA;AAE5B,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAA;AAElE,yCAAyC;AACzC,MAAM,CAAC,MAAM,QAAQ,GAAG,KAAK,EAAE,GAAW,EAAE,EAAE,CAAC;IAC7C,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IACrC,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,aAAa,CAAC,CAAA;IAE5D,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC;QACnC,OAAO,CAAC,KAAK,CAAC,2BAA2B,GAAG,EAAE,CAAC,CAAA;QAC/C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;IAED,IAAI,GAAW,CAAA;IACf,IAAI,CAAC;QACH,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,cAAc,EAAE,OAAO,CAAC,CAAA;IAChD,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CAAC,+BAA+B,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;QAClF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;IAED,IAAI,IAAa,CAAA;IACjB,IAAI,CAAC;QACH,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IACxB,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CAAC,gCAAgC,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;QACnF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,kBAAkB,CAAC,IAAI,CAAC,CAAA;IAC7C,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QAClB,OAAO,CAAC,KAAK,CAAC,mBAAmB,MAAM,CAAC,MAAM,CAAC,MAAM,cAAc,CAAC,CAAA;QACpE,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,MAAM;YAAE,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,EAAE,CAAC,CAAA;QAC1D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;IAED,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,MAAM,CAAA;IACnC,OAAO,CAAC,GAAG,CAAC,0BAA0B,UAAU,CAAC,IAAI,CAAC,WAAW,MAAM,UAAU,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,CAAA;IAE/F,IAAI,UAAU,CAAC,YAAY,EAAE,CAAC;QAC5B,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,CAAA;QACjD,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAClE,CAAC;AAAA,CACF,CAAA"}
package/lib/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { login } from "./commands/login.js";
3
3
  import { upload } from "./commands/upload.js";
4
+ import { validate } from "./commands/validate.js";
4
5
  import { gameCreate } from "./commands/game/create.js";
5
6
  import { migrate } from "./commands/migrate.js";
6
7
  const [command, ...args] = process.argv.slice(2);
@@ -9,7 +10,8 @@ const printUsage = () => {
9
10
  puzzmo login <token> Save a CLI auth token
10
11
  puzzmo game create [token] Create a new Puzzmo game project
11
12
 
12
- puzzmo upload <slug> <dir> Upload game build from <dir>
13
+ puzzmo upload <dir> Upload game build from <dir> (slug from puzzmo.json)
14
+ puzzmo validate [dir] Validate puzzmo.json in a directory (default: .)
13
15
  puzzmo migrate List and select migration skills from dev.puzzmo.com`);
14
16
  };
15
17
  const run = async () => {
@@ -24,13 +26,12 @@ const run = async () => {
24
26
  break;
25
27
  }
26
28
  case "upload": {
27
- const gameSlug = args[0];
28
- const dir = args[1];
29
- if (!gameSlug || !dir) {
30
- console.error("Usage: puzzmo upload <gameSlug> <dir>");
29
+ const dir = args[0];
30
+ if (!dir) {
31
+ console.error("Usage: puzzmo upload <dir>");
31
32
  process.exit(1);
32
33
  }
33
- await upload(gameSlug, dir);
34
+ await upload(dir);
34
35
  break;
35
36
  }
36
37
  case "game": {
@@ -44,6 +45,10 @@ const run = async () => {
44
45
  }
45
46
  break;
46
47
  }
48
+ case "validate": {
49
+ validate(args[0] || ".");
50
+ break;
51
+ }
47
52
  case "migrate": {
48
53
  await migrate();
49
54
  break;
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAA;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAA;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAA;AAE/C,MAAM,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;AAEhD,MAAM,UAAU,GAAG,GAAG,EAAE,CAAC;IACvB,OAAO,CAAC,GAAG,CAAC;;;;;uFAKyE,CAAC,CAAA;AAAA,CACvF,CAAA;AAED,MAAM,GAAG,GAAG,KAAK,IAAI,EAAE,CAAC;IACtB,QAAQ,OAAO,EAAE,CAAC;QAChB,KAAK,OAAO,EAAE,CAAC;YACb,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,CAAA;YACrB,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,OAAO,CAAC,KAAK,CAAC,6BAA6B,CAAC,CAAA;gBAC5C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;YACjB,CAAC;YACD,KAAK,CAAC,KAAK,CAAC,CAAA;YACZ,MAAK;QACP,CAAC;QACD,KAAK,QAAQ,EAAE,CAAC;YACd,MAAM,QAAQ,GAAG,IAAI,CAAC,CAAC,CAAC,CAAA;YACxB,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAA;YACnB,IAAI,CAAC,QAAQ,IAAI,CAAC,GAAG,EAAE,CAAC;gBACtB,OAAO,CAAC,KAAK,CAAC,uCAAuC,CAAC,CAAA;gBACtD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;YACjB,CAAC;YACD,MAAM,MAAM,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAA;YAC3B,MAAK;QACP,CAAC;QACD,KAAK,MAAM,EAAE,CAAC;YACZ,MAAM,CAAC,UAAU,EAAE,GAAG,OAAO,CAAC,GAAG,IAAI,CAAA;YACrC,IAAI,UAAU,KAAK,QAAQ,EAAE,CAAC;gBAC5B,MAAM,UAAU,CAAC,OAAO,CAAC,CAAA;YAC3B,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,KAAK,CAAC,0GAA0G,CAAC,CAAA;gBACzH,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;YACjB,CAAC;YACD,MAAK;QACP,CAAC;QACD,KAAK,SAAS,EAAE,CAAC;YACf,MAAM,OAAO,EAAE,CAAA;YACf,MAAK;QACP,CAAC;QACD;YACE,UAAU,EAAE,CAAA;YACZ,MAAK;IACT,CAAC;AAAA,CACF,CAAA;AAED,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC;IACnB,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC,CAAA;IACjC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AAAA,CAChB,CAAC,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAA;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAA;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAA;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAA;AAE/C,MAAM,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;AAEhD,MAAM,UAAU,GAAG,GAAG,EAAE,CAAC;IACvB,OAAO,CAAC,GAAG,CAAC;;;;;;uFAMyE,CAAC,CAAA;AAAA,CACvF,CAAA;AAED,MAAM,GAAG,GAAG,KAAK,IAAI,EAAE,CAAC;IACtB,QAAQ,OAAO,EAAE,CAAC;QAChB,KAAK,OAAO,EAAE,CAAC;YACb,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,CAAA;YACrB,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,OAAO,CAAC,KAAK,CAAC,6BAA6B,CAAC,CAAA;gBAC5C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;YACjB,CAAC;YACD,KAAK,CAAC,KAAK,CAAC,CAAA;YACZ,MAAK;QACP,CAAC;QACD,KAAK,QAAQ,EAAE,CAAC;YACd,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAA;YACnB,IAAI,CAAC,GAAG,EAAE,CAAC;gBACT,OAAO,CAAC,KAAK,CAAC,4BAA4B,CAAC,CAAA;gBAC3C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;YACjB,CAAC;YACD,MAAM,MAAM,CAAC,GAAG,CAAC,CAAA;YACjB,MAAK;QACP,CAAC;QACD,KAAK,MAAM,EAAE,CAAC;YACZ,MAAM,CAAC,UAAU,EAAE,GAAG,OAAO,CAAC,GAAG,IAAI,CAAA;YACrC,IAAI,UAAU,KAAK,QAAQ,EAAE,CAAC;gBAC5B,MAAM,UAAU,CAAC,OAAO,CAAC,CAAA;YAC3B,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,KAAK,CAAC,0GAA0G,CAAC,CAAA;gBACzH,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;YACjB,CAAC;YACD,MAAK;QACP,CAAC;QACD,KAAK,UAAU,EAAE,CAAC;YAChB,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAA;YACxB,MAAK;QACP,CAAC;QACD,KAAK,SAAS,EAAE,CAAC;YACf,MAAM,OAAO,EAAE,CAAA;YACf,MAAK;QACP,CAAC;QACD;YACE,UAAU,EAAE,CAAA;YACZ,MAAK;IACT,CAAC;AAAA,CACF,CAAA;AAED,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC;IACnB,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC,CAAA;IACjC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AAAA,CAChB,CAAC,CAAA"}
package/lib/util/api.d.ts CHANGED
@@ -1,3 +1,12 @@
1
+ /** The schema for a puzzmo.json file - mirrors PuzzmoFile from @puzzmo-com/shared/hostAPI */
2
+ export type PuzzmoFile = {
3
+ game: {
4
+ displayName: string;
5
+ slug: string;
6
+ teamID: string;
7
+ };
8
+ integrations?: Record<string, unknown>;
9
+ };
1
10
  type CompleteResponse = {
2
11
  assetsBase: string;
3
12
  versionID: string;
@@ -6,5 +15,5 @@ type CompleteResponse = {
6
15
  /** Callback for reporting batch upload progress */
7
16
  export type UploadProgress = (batch: number, totalBatches: number, uploaded: number) => void;
8
17
  /** Multi-step upload: init -> batched file uploads -> complete */
9
- export declare const uploadFiles: (token: string, gameSlug: string, sha: string, filePaths: string[], baseDir: string, onProgress?: UploadProgress | undefined) => Promise<CompleteResponse>;
18
+ export declare const uploadFiles: (token: string, gameSlug: string, sha: string, filePaths: string[], baseDir: string, puzzmoFile: PuzzmoFile, onProgress?: UploadProgress | undefined) => Promise<CompleteResponse>;
10
19
  export {};
package/lib/util/api.js CHANGED
@@ -32,10 +32,10 @@ const uploadFile = async (url, token, filePath, baseDir) => {
32
32
  return json;
33
33
  };
34
34
  /** Multi-step upload: init -> batched file uploads -> complete */
35
- export const uploadFiles = async (token, gameSlug, sha, filePaths, baseDir, onProgress) => {
35
+ export const uploadFiles = async (token, gameSlug, sha, filePaths, baseDir, puzzmoFile, onProgress) => {
36
36
  const apiURL = getAPIURL();
37
- // Step 1: Init session
38
- const init = (await jsonPost(`${apiURL}/cliUpload`, token, { gameSlug, sha }));
37
+ // Step 1: Init session (includes puzzmo.json metadata)
38
+ const init = (await jsonPost(`${apiURL}/cliUpload`, token, { gameSlug, sha, puzzmoFile }));
39
39
  // Step 2: Upload files in concurrent batches
40
40
  const fileURL = `${apiURL}/cliUpload/${init.sessionID}/file`;
41
41
  const totalBatches = Math.ceil(filePaths.length / BATCH_SIZE);
@@ -1 +1 @@
1
- {"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/util/api.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/util/api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { PuzzmoFile } from "./api.js";
2
+ type ValidationResult = {
3
+ valid: true;
4
+ data: PuzzmoFile;
5
+ } | {
6
+ valid: false;
7
+ errors: string[];
8
+ };
9
+ /** Validates a parsed object against the puzzmo.json JSON schema */
10
+ export declare const validatePuzzmoJson: (data: unknown) => Promise<ValidationResult>;
11
+ export {};
@@ -0,0 +1,28 @@
1
+ import { Validator } from "@cfworker/json-schema";
2
+ const schemaURL = "https://dev.puzzmo.com/schema/puzzmo-file-schema.json";
3
+ let cachedValidator;
4
+ /** Fetches and caches a JSON schema validator for puzzmo.json files */
5
+ const getValidator = async () => {
6
+ if (cachedValidator)
7
+ return cachedValidator;
8
+ const res = await fetch(schemaURL);
9
+ if (!res.ok)
10
+ throw new Error(`Failed to fetch puzzmo file schema: ${res.status}`);
11
+ const puzzmoSchema = await res.json();
12
+ cachedValidator = new Validator(puzzmoSchema, "7");
13
+ return cachedValidator;
14
+ };
15
+ /** Validates a parsed object against the puzzmo.json JSON schema */
16
+ export const validatePuzzmoJson = async (data) => {
17
+ const validator = await getValidator();
18
+ const result = validator.validate(data);
19
+ if (result.valid)
20
+ return { valid: true, data: data };
21
+ return {
22
+ valid: false,
23
+ errors: result.errors
24
+ .filter((e) => e.keyword !== "if" && !e.error.startsWith("A subschema had errors"))
25
+ .map((e) => `${e.instanceLocation}: ${e.error}`),
26
+ };
27
+ };
28
+ //# sourceMappingURL=validatePuzzmoFile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validatePuzzmoFile.js","sourceRoot":"","sources":["../../src/util/validatePuzzmoFile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AAIjD,MAAM,SAAS,GAAG,uDAAuD,CAAA;AAEzE,IAAI,eAAsC,CAAA;AAE1C,uEAAuE;AACvE,MAAM,YAAY,GAAG,KAAK,IAAI,EAAE,CAAC;IAC/B,IAAI,eAAe;QAAE,OAAO,eAAe,CAAA;IAC3C,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,SAAS,CAAC,CAAA;IAClC,IAAI,CAAC,GAAG,CAAC,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,uCAAuC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAA;IACjF,MAAM,YAAY,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAA;IACrC,eAAe,GAAG,IAAI,SAAS,CAAC,YAAY,EAAE,GAAG,CAAC,CAAA;IAClD,OAAO,eAAe,CAAA;AAAA,CACvB,CAAA;AAID,oEAAoE;AACpE,MAAM,CAAC,MAAM,kBAAkB,GAAG,KAAK,EAAE,IAAa,EAA6B,EAAE,CAAC;IACpF,MAAM,SAAS,GAAG,MAAM,YAAY,EAAE,CAAA;IACtC,MAAM,MAAM,GAAG,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;IACvC,IAAI,MAAM,CAAC,KAAK;QAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,IAAkB,EAAE,CAAA;IAClE,OAAO;QACL,KAAK,EAAE,KAAK;QACZ,MAAM,EAAE,MAAM,CAAC,MAAM;aAClB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,wBAAwB,CAAC,CAAC;aAClF,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,gBAAgB,KAAK,CAAC,CAAC,KAAK,EAAE,CAAC;KACnD,CAAA;AAAA,CACF,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@puzzmo/cli",
3
- "version": "1.0.29",
3
+ "version": "1.0.31",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "puzzmo": "./lib/index.js"
@@ -21,6 +21,7 @@
21
21
  "type-check": "tsgo --noEmit"
22
22
  },
23
23
  "dependencies": {
24
+ "@cfworker/json-schema": "^4.1.1",
24
25
  "@clack/prompts": "^1.1.0",
25
26
  "@puzzmo/agent-cli-detect": "*"
26
27
  }
@@ -5,6 +5,7 @@ import path from "node:path"
5
5
 
6
6
  import { uploadFiles } from "../util/api.js"
7
7
  import { getAPIURL, getToken } from "../util/config.js"
8
+ import { validatePuzzmoJson } from "../util/validatePuzzmoFile.js"
8
9
 
9
10
  /** Collects all files in a directory recursively */
10
11
  const collectFiles = (dir: string): string[] => {
@@ -44,7 +45,7 @@ const formatBytes = (bytes: number): string => {
44
45
  }
45
46
 
46
47
  /** Uploads game build artifacts to Puzzmo */
47
- export const upload = async (gameSlug: string, dir: string) => {
48
+ export const upload = async (dir: string) => {
48
49
  const token = getToken()
49
50
  if (!token) {
50
51
  console.error("Not logged in. Run `puzzmo login <token>` or set PUZZMO_TOKEN.")
@@ -63,6 +64,31 @@ export const upload = async (gameSlug: string, dir: string) => {
63
64
  process.exit(1)
64
65
  }
65
66
 
67
+ // Require and validate puzzmo.json
68
+ const puzzmoJsonPath = path.join(distDir, "puzzmo.json")
69
+ if (!fs.existsSync(puzzmoJsonPath)) {
70
+ console.error(`Missing puzzmo.json in ${dir}`)
71
+ console.error("Every game upload must include a puzzmo.json file.")
72
+ process.exit(1)
73
+ }
74
+
75
+ let parsed: unknown
76
+ try {
77
+ parsed = JSON.parse(fs.readFileSync(puzzmoJsonPath, "utf-8"))
78
+ } catch (e) {
79
+ console.error(`Invalid puzzmo.json: ${e instanceof Error ? e.message : e}`)
80
+ process.exit(1)
81
+ }
82
+
83
+ const validation = await validatePuzzmoJson(parsed)
84
+ if (!validation.valid) {
85
+ console.error(`Invalid puzzmo.json:\n`)
86
+ for (const err of validation.errors) console.error(` ${err}`)
87
+ process.exit(1)
88
+ }
89
+ const puzzmoFile = validation.data
90
+ const gameSlug = puzzmoFile.game.slug
91
+
66
92
  // Determine SHA
67
93
  const sha = getGitSHA() || hashFiles(files)
68
94
 
@@ -85,7 +111,7 @@ export const upload = async (gameSlug: string, dir: string) => {
85
111
  if (apiURL !== defaultURL) console.log(`Uploading to ${apiURL}...`)
86
112
  else console.log("Uploading...")
87
113
 
88
- const result = await uploadFiles(token, gameSlug, sha, files, distDir, (batch, totalBatches, uploaded) => {
114
+ const result = await uploadFiles(token, gameSlug, sha, files, distDir, puzzmoFile, (batch, totalBatches, uploaded) => {
89
115
  console.log(` Batch ${batch}/${totalBatches} done (${uploaded} file(s) uploaded)`)
90
116
  })
91
117
 
@@ -0,0 +1,46 @@
1
+ import fs from "node:fs"
2
+ import path from "node:path"
3
+
4
+ import { validatePuzzmoJson } from "../util/validatePuzzmoFile.js"
5
+
6
+ /** CLI command: puzzmo validate [dir] */
7
+ export const validate = async (dir: string) => {
8
+ const resolvedDir = path.resolve(dir)
9
+ const puzzmoJsonPath = path.join(resolvedDir, "puzzmo.json")
10
+
11
+ if (!fs.existsSync(puzzmoJsonPath)) {
12
+ console.error(`No puzzmo.json found in ${dir}`)
13
+ process.exit(1)
14
+ }
15
+
16
+ let raw: string
17
+ try {
18
+ raw = fs.readFileSync(puzzmoJsonPath, "utf-8")
19
+ } catch (e) {
20
+ console.error(`Could not read puzzmo.json: ${e instanceof Error ? e.message : e}`)
21
+ process.exit(1)
22
+ }
23
+
24
+ let data: unknown
25
+ try {
26
+ data = JSON.parse(raw)
27
+ } catch (e) {
28
+ console.error(`Invalid JSON in puzzmo.json: ${e instanceof Error ? e.message : e}`)
29
+ process.exit(1)
30
+ }
31
+
32
+ const result = await validatePuzzmoJson(data)
33
+ if (!result.valid) {
34
+ console.error(`puzzmo.json has ${result.errors.length} error(s):\n`)
35
+ for (const err of result.errors) console.error(` ${err}`)
36
+ process.exit(1)
37
+ }
38
+
39
+ const { data: puzzmoFile } = result
40
+ console.log(`Valid puzzmo.json for "${puzzmoFile.game.displayName}" (${puzzmoFile.game.slug})`)
41
+
42
+ if (puzzmoFile.integrations) {
43
+ const keys = Object.keys(puzzmoFile.integrations)
44
+ if (keys.length) console.log(`Integrations: ${keys.join(", ")}`)
45
+ }
46
+ }
package/src/index.ts CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { login } from "./commands/login.js"
4
4
  import { upload } from "./commands/upload.js"
5
+ import { validate } from "./commands/validate.js"
5
6
  import { gameCreate } from "./commands/game/create.js"
6
7
  import { migrate } from "./commands/migrate.js"
7
8
 
@@ -12,7 +13,8 @@ const printUsage = () => {
12
13
  puzzmo login <token> Save a CLI auth token
13
14
  puzzmo game create [token] Create a new Puzzmo game project
14
15
 
15
- puzzmo upload <slug> <dir> Upload game build from <dir>
16
+ puzzmo upload <dir> Upload game build from <dir> (slug from puzzmo.json)
17
+ puzzmo validate [dir] Validate puzzmo.json in a directory (default: .)
16
18
  puzzmo migrate List and select migration skills from dev.puzzmo.com`)
17
19
  }
18
20
 
@@ -28,13 +30,12 @@ const run = async () => {
28
30
  break
29
31
  }
30
32
  case "upload": {
31
- const gameSlug = args[0]
32
- const dir = args[1]
33
- if (!gameSlug || !dir) {
34
- console.error("Usage: puzzmo upload <gameSlug> <dir>")
33
+ const dir = args[0]
34
+ if (!dir) {
35
+ console.error("Usage: puzzmo upload <dir>")
35
36
  process.exit(1)
36
37
  }
37
- await upload(gameSlug, dir)
38
+ await upload(dir)
38
39
  break
39
40
  }
40
41
  case "game": {
@@ -47,6 +48,10 @@ const run = async () => {
47
48
  }
48
49
  break
49
50
  }
51
+ case "validate": {
52
+ validate(args[0] || ".")
53
+ break
54
+ }
50
55
  case "migrate": {
51
56
  await migrate()
52
57
  break
package/src/util/api.ts CHANGED
@@ -3,6 +3,16 @@ import path from "node:path"
3
3
 
4
4
  import { getAPIURL } from "./config.js"
5
5
 
6
+ /** The schema for a puzzmo.json file - mirrors PuzzmoFile from @puzzmo-com/shared/hostAPI */
7
+ export type PuzzmoFile = {
8
+ game: {
9
+ displayName: string
10
+ slug: string
11
+ teamID: string
12
+ }
13
+ integrations?: Record<string, unknown>
14
+ }
15
+
6
16
  const BATCH_SIZE = 10
7
17
 
8
18
  type InitResponse = { sessionID: string; basePath: string; error?: string }
@@ -49,12 +59,13 @@ export const uploadFiles = async (
49
59
  sha: string,
50
60
  filePaths: string[],
51
61
  baseDir: string,
62
+ puzzmoFile: PuzzmoFile,
52
63
  onProgress?: UploadProgress,
53
64
  ): Promise<CompleteResponse> => {
54
65
  const apiURL = getAPIURL()
55
66
 
56
- // Step 1: Init session
57
- const init = (await jsonPost(`${apiURL}/cliUpload`, token, { gameSlug, sha })) as InitResponse
67
+ // Step 1: Init session (includes puzzmo.json metadata)
68
+ const init = (await jsonPost(`${apiURL}/cliUpload`, token, { gameSlug, sha, puzzmoFile })) as InitResponse
58
69
 
59
70
  // Step 2: Upload files in concurrent batches
60
71
  const fileURL = `${apiURL}/cliUpload/${init.sessionID}/file`
@@ -0,0 +1,32 @@
1
+ import { Validator } from "@cfworker/json-schema"
2
+
3
+ import type { PuzzmoFile } from "./api.js"
4
+
5
+ const schemaURL = "https://dev.puzzmo.com/schema/puzzmo-file-schema.json"
6
+
7
+ let cachedValidator: Validator | undefined
8
+
9
+ /** Fetches and caches a JSON schema validator for puzzmo.json files */
10
+ const getValidator = async () => {
11
+ if (cachedValidator) return cachedValidator
12
+ const res = await fetch(schemaURL)
13
+ if (!res.ok) throw new Error(`Failed to fetch puzzmo file schema: ${res.status}`)
14
+ const puzzmoSchema = await res.json()
15
+ cachedValidator = new Validator(puzzmoSchema, "7")
16
+ return cachedValidator
17
+ }
18
+
19
+ type ValidationResult = { valid: true; data: PuzzmoFile } | { valid: false; errors: string[] }
20
+
21
+ /** Validates a parsed object against the puzzmo.json JSON schema */
22
+ export const validatePuzzmoJson = async (data: unknown): Promise<ValidationResult> => {
23
+ const validator = await getValidator()
24
+ const result = validator.validate(data)
25
+ if (result.valid) return { valid: true, data: data as PuzzmoFile }
26
+ return {
27
+ valid: false,
28
+ errors: result.errors
29
+ .filter((e) => e.keyword !== "if" && !e.error.startsWith("A subschema had errors"))
30
+ .map((e) => `${e.instanceLocation}: ${e.error}`),
31
+ }
32
+ }