@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 +4 -4
- package/lib/commands/upload.d.ts +1 -1
- package/lib/commands/upload.js +27 -2
- package/lib/commands/upload.js.map +1 -1
- package/lib/commands/validate.d.ts +2 -0
- package/lib/commands/validate.js +43 -0
- package/lib/commands/validate.js.map +1 -0
- package/lib/index.js +11 -6
- package/lib/index.js.map +1 -1
- package/lib/util/api.d.ts +10 -1
- package/lib/util/api.js +3 -3
- package/lib/util/api.js.map +1 -1
- package/lib/util/validatePuzzmoFile.d.ts +11 -0
- package/lib/util/validatePuzzmoFile.js +28 -0
- package/lib/util/validatePuzzmoFile.js.map +1 -0
- package/package.json +2 -1
- package/src/commands/upload.ts +28 -2
- package/src/commands/validate.ts +46 -0
- package/src/index.ts +11 -6
- package/src/util/api.ts +13 -2
- package/src/util/validatePuzzmoFile.ts +32 -0
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 <
|
|
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
|
|
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
|
|
package/lib/commands/upload.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Uploads game build artifacts to Puzzmo */
|
|
2
|
-
export declare const upload: (
|
|
2
|
+
export declare const upload: (dir: string) => Promise<void>;
|
package/lib/commands/upload.js
CHANGED
|
@@ -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 (
|
|
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":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAC7C,OAAO,MAAM,MAAM,aAAa,CAAA;AAChC,OAAO,EAAE,MAAM,SAAS,CAAA;AACxB,OAAO,IAAI,MAAM,WAAW,CAAA;AAE5B,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC5C,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"upload.js","sourceRoot":"","sources":["../../src/commands/upload.ts"],"names":[],"mappings":"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"}
|
|
@@ -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 <
|
|
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
|
|
28
|
-
|
|
29
|
-
|
|
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(
|
|
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
|
|
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);
|
package/lib/util/api.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/util/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAA;AACxB,OAAO,IAAI,MAAM,WAAW,CAAA;AAE5B,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;
|
|
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.
|
|
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
|
}
|
package/src/commands/upload.ts
CHANGED
|
@@ -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 (
|
|
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 <
|
|
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
|
|
32
|
-
|
|
33
|
-
|
|
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(
|
|
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
|
+
}
|