@puzzmo/cli 1.0.28 → 1.0.30
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/lib/commands/upload.js +28 -4
- 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 +6 -0
- package/lib/index.js.map +1 -1
- package/lib/util/api.d.ts +11 -2
- 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 +29 -4
- package/src/commands/validate.ts +46 -0
- package/src/index.ts +6 -0
- package/src/util/api.ts +14 -3
- package/src/util/validatePuzzmoFile.ts +32 -0
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 });
|
|
@@ -17,10 +18,10 @@ const collectFiles = (dir) => {
|
|
|
17
18
|
}
|
|
18
19
|
return files;
|
|
19
20
|
};
|
|
20
|
-
/** Tries to get the
|
|
21
|
+
/** Tries to get the shortest unique git SHA, returns null if not in a git repo */
|
|
21
22
|
const getGitSHA = () => {
|
|
22
23
|
try {
|
|
23
|
-
return execSync("git rev-parse HEAD", { encoding: "utf-8" }).trim();
|
|
24
|
+
return execSync("git rev-parse --short HEAD", { encoding: "utf-8" }).trim();
|
|
24
25
|
}
|
|
25
26
|
catch {
|
|
26
27
|
return null;
|
|
@@ -59,6 +60,29 @@ 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;
|
|
62
86
|
// Determine SHA
|
|
63
87
|
const sha = getGitSHA() || hashFiles(files);
|
|
64
88
|
console.log(`\nUploading ${gameSlug} (${sha.slice(0, 8)})`);
|
|
@@ -78,10 +102,10 @@ export const upload = async (gameSlug, dir) => {
|
|
|
78
102
|
console.log(`Uploading to ${apiURL}...`);
|
|
79
103
|
else
|
|
80
104
|
console.log("Uploading...");
|
|
81
|
-
const result = await uploadFiles(token, gameSlug, sha, files, distDir, (batch, totalBatches, uploaded) => {
|
|
105
|
+
const result = await uploadFiles(token, gameSlug, sha, files, distDir, puzzmoFile, (batch, totalBatches, uploaded) => {
|
|
82
106
|
console.log(` Batch ${batch}/${totalBatches} done (${uploaded} file(s) uploaded)`);
|
|
83
107
|
});
|
|
84
108
|
console.log(`\nDone - ${result.versionID}`);
|
|
85
|
-
console.log(`
|
|
109
|
+
console.log(`Assets: ${result.assetsBase}`);
|
|
86
110
|
};
|
|
87
111
|
//# sourceMappingURL=upload.js.map
|
|
@@ -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);
|
|
@@ -10,6 +11,7 @@ const printUsage = () => {
|
|
|
10
11
|
puzzmo game create [token] Create a new Puzzmo game project
|
|
11
12
|
|
|
12
13
|
puzzmo upload <slug> <dir> Upload game build from <dir>
|
|
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 () => {
|
|
@@ -44,6 +46,10 @@ const run = async () => {
|
|
|
44
46
|
}
|
|
45
47
|
break;
|
|
46
48
|
}
|
|
49
|
+
case "validate": {
|
|
50
|
+
validate(args[0] || ".");
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
47
53
|
case "migrate": {
|
|
48
54
|
await migrate();
|
|
49
55
|
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,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,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,10 +1,19 @@
|
|
|
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;
|
|
4
13
|
error?: string;
|
|
5
14
|
};
|
|
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.30",
|
|
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[] => {
|
|
@@ -18,10 +19,10 @@ const collectFiles = (dir: string): string[] => {
|
|
|
18
19
|
return files
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
/** Tries to get the
|
|
22
|
+
/** Tries to get the shortest unique git SHA, returns null if not in a git repo */
|
|
22
23
|
const getGitSHA = (): string | null => {
|
|
23
24
|
try {
|
|
24
|
-
return execSync("git rev-parse HEAD", { encoding: "utf-8" }).trim()
|
|
25
|
+
return execSync("git rev-parse --short HEAD", { encoding: "utf-8" }).trim()
|
|
25
26
|
} catch {
|
|
26
27
|
return null
|
|
27
28
|
}
|
|
@@ -63,6 +64,30 @@ 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
|
+
|
|
66
91
|
// Determine SHA
|
|
67
92
|
const sha = getGitSHA() || hashFiles(files)
|
|
68
93
|
|
|
@@ -85,10 +110,10 @@ export const upload = async (gameSlug: string, dir: string) => {
|
|
|
85
110
|
if (apiURL !== defaultURL) console.log(`Uploading to ${apiURL}...`)
|
|
86
111
|
else console.log("Uploading...")
|
|
87
112
|
|
|
88
|
-
const result = await uploadFiles(token, gameSlug, sha, files, distDir, (batch, totalBatches, uploaded) => {
|
|
113
|
+
const result = await uploadFiles(token, gameSlug, sha, files, distDir, puzzmoFile, (batch, totalBatches, uploaded) => {
|
|
89
114
|
console.log(` Batch ${batch}/${totalBatches} done (${uploaded} file(s) uploaded)`)
|
|
90
115
|
})
|
|
91
116
|
|
|
92
117
|
console.log(`\nDone - ${result.versionID}`)
|
|
93
|
-
console.log(`
|
|
118
|
+
console.log(`Assets: ${result.assetsBase}`)
|
|
94
119
|
}
|
|
@@ -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
|
|
|
@@ -13,6 +14,7 @@ const printUsage = () => {
|
|
|
13
14
|
puzzmo game create [token] Create a new Puzzmo game project
|
|
14
15
|
|
|
15
16
|
puzzmo upload <slug> <dir> Upload game build from <dir>
|
|
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
|
|
|
@@ -47,6 +49,10 @@ const run = async () => {
|
|
|
47
49
|
}
|
|
48
50
|
break
|
|
49
51
|
}
|
|
52
|
+
case "validate": {
|
|
53
|
+
validate(args[0] || ".")
|
|
54
|
+
break
|
|
55
|
+
}
|
|
50
56
|
case "migrate": {
|
|
51
57
|
await migrate()
|
|
52
58
|
break
|
package/src/util/api.ts
CHANGED
|
@@ -3,11 +3,21 @@ 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 }
|
|
9
19
|
type FileResponse = { path: string; error?: string }
|
|
10
|
-
type CompleteResponse = {
|
|
20
|
+
type CompleteResponse = { assetsBase: string; versionID: string; error?: string }
|
|
11
21
|
|
|
12
22
|
/** Callback for reporting batch upload progress */
|
|
13
23
|
export type UploadProgress = (batch: number, totalBatches: number, uploaded: number) => void
|
|
@@ -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
|
+
}
|