@puzzmo/cli 1.0.40 → 1.0.42
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/game/create.d.ts +11 -1
- package/lib/commands/game/create.js +3 -31
- package/lib/commands/game/create.js.map +1 -1
- package/lib/commands/login.d.ts +2 -2
- package/lib/commands/login.js +11 -7
- package/lib/commands/login.js.map +1 -1
- package/lib/commands/upload.js +26 -16
- package/lib/commands/upload.js.map +1 -1
- package/lib/index.js +76 -64
- package/lib/index.js.map +1 -1
- package/lib/util/api.d.ts +1 -1
- package/lib/util/api.js +1 -3
- package/lib/util/api.js.map +1 -1
- package/lib/util/config.d.ts +39 -9
- package/lib/util/config.js +106 -12
- package/lib/util/config.js.map +1 -1
- package/lib/util/createGame.d.ts +2 -0
- package/lib/util/createGame.js +1 -2
- package/lib/util/createGame.js.map +1 -1
- package/package.json +3 -2
- package/src/commands/game/create.ts +5 -30
- package/src/commands/login.ts +11 -7
- package/src/commands/upload.ts +36 -18
- package/src/index.ts +81 -64
- package/src/util/api.ts +1 -3
- package/src/util/config.ts +119 -15
- package/src/util/createGame.ts +3 -3
- package/templates/minesweeper/vite.config.ts +1 -0
package/lib/util/config.d.ts
CHANGED
|
@@ -1,13 +1,43 @@
|
|
|
1
|
+
export declare const defaultSource = "https://api.puzzmo.com";
|
|
2
|
+
export type TokenEntry = {
|
|
3
|
+
/** Server identifier (e.g. "api.puzzmo.com" or "localhost:8911") */
|
|
4
|
+
source: string;
|
|
5
|
+
/** The pzt- prefixed JWT issued by that server */
|
|
6
|
+
token: string;
|
|
7
|
+
};
|
|
1
8
|
type Config = {
|
|
2
|
-
|
|
3
|
-
apiURL?: string;
|
|
9
|
+
tokens: TokenEntry[];
|
|
4
10
|
};
|
|
5
|
-
/** Reads
|
|
11
|
+
/** Reads ~/.puzzmo/config.json. Returns an empty token list if the file doesn't exist. */
|
|
6
12
|
export declare const readConfig: () => Config;
|
|
7
|
-
/**
|
|
8
|
-
export declare const
|
|
9
|
-
/**
|
|
10
|
-
export declare const
|
|
11
|
-
/** Returns
|
|
12
|
-
export declare const
|
|
13
|
+
/** Saves a token for `source`, replacing any existing entry for that same source */
|
|
14
|
+
export declare const addToken: (source: string, token: string) => void;
|
|
15
|
+
/** Removes any saved token for `source`. Returns true if something was removed. */
|
|
16
|
+
export declare const removeToken: (source: string) => boolean;
|
|
17
|
+
/** Returns saved tokens. PUZZMO_TOKEN env var (with optional PUZZMO_API_URL) overrides everything. */
|
|
18
|
+
export declare const getTokens: () => TokenEntry[];
|
|
19
|
+
/** Best-guess token for clients that don't care which server it points at (e.g. .mcp.json scaffolding) */
|
|
20
|
+
export declare const getDefaultToken: () => string | undefined;
|
|
21
|
+
type TokenPayload = {
|
|
22
|
+
teamID?: string;
|
|
23
|
+
createdByID?: string;
|
|
24
|
+
iat?: number;
|
|
25
|
+
};
|
|
26
|
+
/** Decodes a `pzt-<jwt>` token (without verifying) and returns its payload, or null on failure */
|
|
27
|
+
export declare const decodeTokenPayload: (token: string) => TokenPayload | null;
|
|
28
|
+
/** Strips protocol and trailing slashes so two spellings of the same server compare equal */
|
|
29
|
+
export declare const normalizeSource: (source: string) => string;
|
|
30
|
+
/** Re-attaches a protocol to a normalized source. Localhost defaults to http, everything else to https. */
|
|
31
|
+
export declare const sourceToURL: (source: string) => string;
|
|
32
|
+
/** Sorts saved tokens by upload preference: localhost > *.puzzmo.com > anything else */
|
|
33
|
+
export declare const sortByServerPriority: (tokens: TokenEntry[]) => TokenEntry[];
|
|
34
|
+
/** Returns saved tokens whose JWT teamID matches the given teamID */
|
|
35
|
+
export declare const findTokensForTeam: (teamID: string) => TokenEntry[];
|
|
36
|
+
/** True if a quick fetch to `${source}/graphql` returns any HTTP response within `timeoutMs` */
|
|
37
|
+
export declare const isServerReachable: (source: string, timeoutMs?: number) => Promise<boolean>;
|
|
38
|
+
/**
|
|
39
|
+
* Picks the (source, token) pair to use for `teamID`. Prefers localhost when reachable,
|
|
40
|
+
* then *.puzzmo.com, then any other matching source. Returns null if no token matches.
|
|
41
|
+
*/
|
|
42
|
+
export declare const resolveServerForTeam: (teamID: string) => Promise<TokenEntry | null>;
|
|
13
43
|
export {};
|
package/lib/util/config.js
CHANGED
|
@@ -3,27 +3,121 @@ import os from "node:os";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
const configDir = path.join(os.homedir(), ".puzzmo");
|
|
5
5
|
const configPath = path.join(configDir, "config.json");
|
|
6
|
-
|
|
6
|
+
export const defaultSource = "https://api.puzzmo.com";
|
|
7
|
+
/** Reads ~/.puzzmo/config.json. Returns an empty token list if the file doesn't exist. */
|
|
7
8
|
export const readConfig = () => {
|
|
8
9
|
try {
|
|
9
|
-
const
|
|
10
|
-
return
|
|
10
|
+
const parsed = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
11
|
+
return { tokens: parsed.tokens ?? [] };
|
|
11
12
|
}
|
|
12
13
|
catch {
|
|
13
|
-
return {};
|
|
14
|
+
return { tokens: [] };
|
|
14
15
|
}
|
|
15
16
|
};
|
|
16
|
-
|
|
17
|
-
export const writeConfig = (config) => {
|
|
17
|
+
const writeConfig = (config) => {
|
|
18
18
|
fs.mkdirSync(configDir, { recursive: true });
|
|
19
19
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
20
20
|
};
|
|
21
|
-
/**
|
|
22
|
-
export const
|
|
23
|
-
|
|
21
|
+
/** Saves a token for `source`, replacing any existing entry for that same source */
|
|
22
|
+
export const addToken = (source, token) => {
|
|
23
|
+
const normalized = normalizeSource(source);
|
|
24
|
+
const { tokens } = readConfig();
|
|
25
|
+
const remaining = tokens.filter((t) => normalizeSource(t.source) !== normalized);
|
|
26
|
+
remaining.push({ source: normalized, token });
|
|
27
|
+
writeConfig({ tokens: remaining });
|
|
24
28
|
};
|
|
25
|
-
/** Returns
|
|
26
|
-
export const
|
|
27
|
-
|
|
29
|
+
/** Removes any saved token for `source`. Returns true if something was removed. */
|
|
30
|
+
export const removeToken = (source) => {
|
|
31
|
+
const normalized = normalizeSource(source);
|
|
32
|
+
const { tokens } = readConfig();
|
|
33
|
+
const remaining = tokens.filter((t) => normalizeSource(t.source) !== normalized);
|
|
34
|
+
if (remaining.length === tokens.length)
|
|
35
|
+
return false;
|
|
36
|
+
writeConfig({ tokens: remaining });
|
|
37
|
+
return true;
|
|
38
|
+
};
|
|
39
|
+
/** Returns saved tokens. PUZZMO_TOKEN env var (with optional PUZZMO_API_URL) overrides everything. */
|
|
40
|
+
export const getTokens = () => {
|
|
41
|
+
const envToken = process.env.PUZZMO_TOKEN;
|
|
42
|
+
if (envToken) {
|
|
43
|
+
const source = normalizeSource(process.env.PUZZMO_API_URL ?? defaultSource);
|
|
44
|
+
return [{ source, token: envToken }];
|
|
45
|
+
}
|
|
46
|
+
return readConfig().tokens;
|
|
47
|
+
};
|
|
48
|
+
/** Best-guess token for clients that don't care which server it points at (e.g. .mcp.json scaffolding) */
|
|
49
|
+
export const getDefaultToken = () => getTokens()[0]?.token;
|
|
50
|
+
/** Decodes a `pzt-<jwt>` token (without verifying) and returns its payload, or null on failure */
|
|
51
|
+
export const decodeTokenPayload = (token) => {
|
|
52
|
+
try {
|
|
53
|
+
const stripped = token.startsWith("pzt-") ? token.slice(4) : token;
|
|
54
|
+
const parts = stripped.split(".");
|
|
55
|
+
if (parts.length < 2)
|
|
56
|
+
return null;
|
|
57
|
+
const payload = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
58
|
+
const padded = payload + "=".repeat((4 - (payload.length % 4)) % 4);
|
|
59
|
+
return JSON.parse(Buffer.from(padded, "base64").toString("utf-8"));
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
/** Strips protocol and trailing slashes so two spellings of the same server compare equal */
|
|
66
|
+
export const normalizeSource = (source) => source
|
|
67
|
+
.trim()
|
|
68
|
+
.replace(/^https?:\/\//, "")
|
|
69
|
+
.replace(/\/+$/, "");
|
|
70
|
+
/** Re-attaches a protocol to a normalized source. Localhost defaults to http, everything else to https. */
|
|
71
|
+
export const sourceToURL = (source) => {
|
|
72
|
+
const trimmed = source.trim().replace(/\/+$/, "");
|
|
73
|
+
if (/^https?:\/\//.test(trimmed))
|
|
74
|
+
return trimmed;
|
|
75
|
+
const normalized = normalizeSource(trimmed);
|
|
76
|
+
if (isLocalhost(normalized))
|
|
77
|
+
return `http://${normalized}`;
|
|
78
|
+
return `https://${normalized}`;
|
|
79
|
+
};
|
|
80
|
+
const isLocalhost = (source) => {
|
|
81
|
+
const n = normalizeSource(source);
|
|
82
|
+
return n.startsWith("localhost") || n.startsWith("127.0.0.1") || n.startsWith("0.0.0.0");
|
|
83
|
+
};
|
|
84
|
+
const isPuzzmoCom = (source) => /(^|\.)puzzmo\.com(:|\/|$)/.test(normalizeSource(source));
|
|
85
|
+
/** Sorts saved tokens by upload preference: localhost > *.puzzmo.com > anything else */
|
|
86
|
+
export const sortByServerPriority = (tokens) => {
|
|
87
|
+
const priority = (t) => (isLocalhost(t.source) ? 0 : isPuzzmoCom(t.source) ? 1 : 2);
|
|
88
|
+
return [...tokens].sort((a, b) => priority(a) - priority(b));
|
|
89
|
+
};
|
|
90
|
+
/** Returns saved tokens whose JWT teamID matches the given teamID */
|
|
91
|
+
export const findTokensForTeam = (teamID) => getTokens().filter((t) => decodeTokenPayload(t.token)?.teamID === teamID);
|
|
92
|
+
/** True if a quick fetch to `${source}/graphql` returns any HTTP response within `timeoutMs` */
|
|
93
|
+
export const isServerReachable = async (source, timeoutMs = 1500) => {
|
|
94
|
+
const url = `${sourceToURL(source)}/graphql`;
|
|
95
|
+
const controller = new AbortController();
|
|
96
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
97
|
+
try {
|
|
98
|
+
await fetch(url, { method: "GET", signal: controller.signal });
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
clearTimeout(timer);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* Picks the (source, token) pair to use for `teamID`. Prefers localhost when reachable,
|
|
110
|
+
* then *.puzzmo.com, then any other matching source. Returns null if no token matches.
|
|
111
|
+
*/
|
|
112
|
+
export const resolveServerForTeam = async (teamID) => {
|
|
113
|
+
const matches = sortByServerPriority(findTokensForTeam(teamID));
|
|
114
|
+
if (matches.length === 0)
|
|
115
|
+
return null;
|
|
116
|
+
for (const candidate of matches) {
|
|
117
|
+
if (isLocalhost(candidate.source) && !(await isServerReachable(candidate.source)))
|
|
118
|
+
continue;
|
|
119
|
+
return candidate;
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
28
122
|
};
|
|
29
123
|
//# sourceMappingURL=config.js.map
|
package/lib/util/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/util/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAA;AACxB,OAAO,EAAE,MAAM,SAAS,CAAA;AACxB,OAAO,IAAI,MAAM,WAAW,CAAA;AAE5B,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAA;AACpD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/util/config.ts"],"names":[],"mappings":"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"}
|
package/lib/util/createGame.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export type CreateUserGameOptions = {
|
|
2
|
+
/** Base URL of the API server to call (e.g. "https://api.puzzmo.com") */
|
|
3
|
+
apiURL: string;
|
|
2
4
|
/** A teamAccessToken (the same token saved by `puzzmo login`). The mutation uses it to identify the team. */
|
|
3
5
|
teamAccessToken: string;
|
|
4
6
|
displayName: string;
|
package/lib/util/createGame.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { getAPIURL } from "./config.js";
|
|
2
1
|
const createUserGameMutation = `
|
|
3
2
|
mutation cliCreateUserGameMutation($teamAccessToken: String!, $input: CreateUserGameInput!) {
|
|
4
3
|
createUserGame(teamAccessToken: $teamAccessToken, input: $input) {
|
|
@@ -9,7 +8,7 @@ const createUserGameMutation = `
|
|
|
9
8
|
`;
|
|
10
9
|
/** Calls the createUserGame mutation using a teamAccessToken for auth. */
|
|
11
10
|
export const createUserGame = async (options) => {
|
|
12
|
-
const url = `${
|
|
11
|
+
const url = `${options.apiURL}/graphql`;
|
|
13
12
|
const response = await fetch(url, {
|
|
14
13
|
method: "POST",
|
|
15
14
|
headers: { "Content-Type": "application/json" },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createGame.js","sourceRoot":"","sources":["../../src/util/createGame.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"createGame.js","sourceRoot":"","sources":["../../src/util/createGame.ts"],"names":[],"mappings":"AAAA,MAAM,sBAAsB,GAAG;;;;;;;CAO9B,CAAA;AAwBD,0EAA0E;AAC1E,MAAM,CAAC,MAAM,cAAc,GAAG,KAAK,EAAE,OAA8B,EAAwB,EAAE,CAAC;IAC5F,MAAM,GAAG,GAAG,GAAG,OAAO,CAAC,MAAM,UAAU,CAAA;IAEvC,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;QAChC,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;QAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;YACnB,KAAK,EAAE,sBAAsB;YAC7B,SAAS,EAAE;gBACT,eAAe,EAAE,OAAO,CAAC,eAAe;gBACxC,KAAK,EAAE,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE;aAC5C;SACF,CAAC;KACH,CAAC,CAAA;IAEF,IAAI,CAAC,QAAQ,CAAC,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,kCAAkC,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAA;IAE7G,MAAM,MAAM,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAA2B,CAAA;IAEhE,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;QAC1B,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC9D,MAAM,IAAI,KAAK,CAAC,0BAA0B,OAAO,EAAE,CAAC,CAAA;IACtD,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,cAAc;QAAE,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAA;IAEpF,MAAM,EAAE,IAAI,EAAE,cAAc,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,cAAc,CAAA;IAC3D,OAAO,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,cAAc,EAAE,CAAA;AAAA,CACvF,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@puzzmo/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.42",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"puzzmo": "./lib/index.js"
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"@cfworker/json-schema": "^4.1.1",
|
|
25
25
|
"@clack/prompts": "^1.1.0",
|
|
26
26
|
"@opencode-ai/sdk": "1.14.20",
|
|
27
|
-
"@puzzmo/agent-cli-detect": "*"
|
|
27
|
+
"@puzzmo/agent-cli-detect": "*",
|
|
28
|
+
"citty": "^0.2.2"
|
|
28
29
|
}
|
|
29
30
|
}
|
|
@@ -10,12 +10,12 @@ import { downloadPage } from "../../download/page-downloader.js"
|
|
|
10
10
|
import { runCommand, gitCommit } from "../../util/exec.js"
|
|
11
11
|
import { runSkillsPipelineTUI, runAgentWithBuildLoop } from "../../skills/runner.js"
|
|
12
12
|
import { login } from "../login.js"
|
|
13
|
-
import {
|
|
13
|
+
import { getDefaultToken } from "../../util/config.js"
|
|
14
14
|
import { detectRepoContext, type RepoType } from "./detectRepo.js"
|
|
15
15
|
|
|
16
|
-
type Strategy = "import" | "blank" | "prompt"
|
|
16
|
+
export type Strategy = "import" | "blank" | "prompt"
|
|
17
17
|
|
|
18
|
-
type CreateOptions = {
|
|
18
|
+
export type CreateOptions = {
|
|
19
19
|
name?: string
|
|
20
20
|
url?: string
|
|
21
21
|
agent?: string
|
|
@@ -25,29 +25,6 @@ type CreateOptions = {
|
|
|
25
25
|
prompt?: string
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
/** Parses CLI args into CreateOptions */
|
|
29
|
-
const parseArgs = (args: string[]): CreateOptions => {
|
|
30
|
-
const opts: CreateOptions = {}
|
|
31
|
-
let i = 0
|
|
32
|
-
|
|
33
|
-
while (i < args.length) {
|
|
34
|
-
const arg = args[i]
|
|
35
|
-
if (arg === "--name" && args[i + 1]) opts.name = args[++i]
|
|
36
|
-
else if (arg === "--url" && args[i + 1]) opts.url = args[++i]
|
|
37
|
-
else if (arg === "--agent" && args[i + 1]) opts.agent = args[++i]
|
|
38
|
-
else if (arg === "--token" && args[i + 1]) opts.accessToken = args[++i]
|
|
39
|
-
else if (arg === "--pm" && args[i + 1]) opts.pm = args[++i]
|
|
40
|
-
else if (arg === "--prompt" && args[i + 1]) opts.prompt = args[++i]
|
|
41
|
-
else if (arg === "--strategy" && args[i + 1]) {
|
|
42
|
-
const v = args[++i]
|
|
43
|
-
if (v === "import" || v === "blank" || v === "prompt") opts.strategy = v
|
|
44
|
-
}
|
|
45
|
-
i++
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return opts
|
|
49
|
-
}
|
|
50
|
-
|
|
51
28
|
/** Converts a string to a URL-friendly slug (matches packages/shared/slugify.ts) */
|
|
52
29
|
const slugify = (text: string) =>
|
|
53
30
|
text
|
|
@@ -62,7 +39,7 @@ const slugify = (text: string) =>
|
|
|
62
39
|
|
|
63
40
|
/** Writes .mcp.json with dev server config */
|
|
64
41
|
const writeMcpConfig = (dir: string) => {
|
|
65
|
-
const token =
|
|
42
|
+
const token = getDefaultToken()
|
|
66
43
|
const mcpConfig = {
|
|
67
44
|
mcpServers: {
|
|
68
45
|
"dev.puzzmo.com": {
|
|
@@ -227,9 +204,7 @@ const buildPromptStrategyMessage = (userPrompt: string, displayName: string): st
|
|
|
227
204
|
].join("\n")
|
|
228
205
|
|
|
229
206
|
/** Main game create wizard */
|
|
230
|
-
export const gameCreate = async (
|
|
231
|
-
const opts = parseArgs(args)
|
|
232
|
-
|
|
207
|
+
export const gameCreate = async (opts: CreateOptions) => {
|
|
233
208
|
const require = createRequire(import.meta.url)
|
|
234
209
|
const { version } = require("../../../package.json")
|
|
235
210
|
p.intro(`Puzzmo Game Creator v${version}`)
|
package/src/commands/login.ts
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import * as p from "@clack/prompts"
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { defaultSource, addToken, decodeTokenPayload, normalizeSource } from "../util/config.js"
|
|
4
4
|
|
|
5
|
-
/** Saves a CLI token to ~/.puzzmo/config.json */
|
|
6
|
-
export const login = (token: string) => {
|
|
5
|
+
/** Saves a CLI token to ~/.puzzmo/config.json under the given source server */
|
|
6
|
+
export const login = (token: string, source: string = defaultSource) => {
|
|
7
7
|
if (!token.startsWith("pzt-")) {
|
|
8
8
|
p.log.error("Invalid CLI token. Generate one from dev.puzzmo.com.")
|
|
9
9
|
process.exit(1)
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
const payload = decodeTokenPayload(token)
|
|
13
|
+
if (!payload?.teamID) {
|
|
14
|
+
p.log.error("Could not decode the team from this token. Make sure you're using a token from dev.puzzmo.com.")
|
|
15
|
+
process.exit(1)
|
|
16
|
+
}
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
const normalized = normalizeSource(source)
|
|
19
|
+
addToken(normalized, token)
|
|
20
|
+
p.log.success(`Logged in to ${normalized} for team ${payload.teamID}. Token saved to ~/.puzzmo/config.json`)
|
|
17
21
|
}
|
package/src/commands/upload.ts
CHANGED
|
@@ -5,7 +5,7 @@ import path from "node:path"
|
|
|
5
5
|
import * as p from "@clack/prompts"
|
|
6
6
|
|
|
7
7
|
import { GameNotFoundError, uploadFiles } from "../util/api.js"
|
|
8
|
-
import {
|
|
8
|
+
import { type TokenEntry, findTokensForTeam, getTokens, resolveServerForTeam, sourceToURL } from "../util/config.js"
|
|
9
9
|
import { createUserGame } from "../util/createGame.js"
|
|
10
10
|
import { discoverGames, type DiscoveredGame } from "../util/discoverGames.js"
|
|
11
11
|
|
|
@@ -16,6 +16,7 @@ type UploadOptions = {
|
|
|
16
16
|
type GameSuccess = {
|
|
17
17
|
ok: true
|
|
18
18
|
slug: string
|
|
19
|
+
source: string
|
|
19
20
|
fileCount: number
|
|
20
21
|
totalBytes: number
|
|
21
22
|
versionID: string
|
|
@@ -33,8 +34,8 @@ type GameResult = GameSuccess | GameFailure
|
|
|
33
34
|
/** Uploads game build artifacts to Puzzmo by discovering puzzmo.json files in `dir` */
|
|
34
35
|
export const upload = async (dir: string, options: UploadOptions = {}) => {
|
|
35
36
|
const { verbose = false } = options
|
|
36
|
-
|
|
37
|
-
if (
|
|
37
|
+
|
|
38
|
+
if (getTokens().length === 0) {
|
|
38
39
|
console.error("Not logged in. Run `puzzmo login <token>` or set PUZZMO_TOKEN.")
|
|
39
40
|
process.exit(1)
|
|
40
41
|
}
|
|
@@ -70,10 +71,6 @@ export const upload = async (dir: string, options: UploadOptions = {}) => {
|
|
|
70
71
|
if (description) console.log(`\nMessage: ${description}`)
|
|
71
72
|
if (repoURL) console.log(`Repo: ${repoURL}`)
|
|
72
73
|
|
|
73
|
-
const apiURL = getAPIURL()
|
|
74
|
-
const defaultURL = "https://api.puzzmo.com"
|
|
75
|
-
if (apiURL !== defaultURL) console.log(`API: ${apiURL}`)
|
|
76
|
-
|
|
77
74
|
const results: GameResult[] = []
|
|
78
75
|
for (const err of discoveryErrors) {
|
|
79
76
|
results.push({ ok: false, slug: err.slug ?? path.relative(rootDir, err.puzzmoJsonPath), error: err.errors.join("; ") })
|
|
@@ -81,22 +78,40 @@ export const upload = async (dir: string, options: UploadOptions = {}) => {
|
|
|
81
78
|
|
|
82
79
|
for (let i = 0; i < games.length; i++) {
|
|
83
80
|
const game = games[i]
|
|
84
|
-
|
|
81
|
+
const slug = game.puzzmoFile.game.slug
|
|
82
|
+
console.log(`\n[${i + 1}/${games.length}] Uploading ${slug}`)
|
|
83
|
+
|
|
84
|
+
const teamID = game.puzzmoFile.game.teamID
|
|
85
|
+
const credential = await resolveServerForTeam(teamID)
|
|
86
|
+
if (!credential) {
|
|
87
|
+
const matches = findTokensForTeam(teamID)
|
|
88
|
+
const message =
|
|
89
|
+
matches.length === 0
|
|
90
|
+
? `No saved token for team ${teamID}. Run \`puzzmo login <token>\` (use \`--source\` if the token is for a non-default server).`
|
|
91
|
+
: `Token for team ${teamID} is registered against ${matches.map((m) => m.source).join(", ")} but none of those servers are reachable.`
|
|
92
|
+
console.error(` ${message}`)
|
|
93
|
+
results.push({ ok: false, slug, error: message })
|
|
94
|
+
continue
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const apiURL = sourceToURL(credential.source)
|
|
98
|
+
console.log(` Server: ${credential.source}`)
|
|
99
|
+
|
|
85
100
|
try {
|
|
86
101
|
let result: GameSuccess
|
|
87
102
|
try {
|
|
88
|
-
result = await uploadOneGame(game, {
|
|
103
|
+
result = await uploadOneGame(game, { credential, apiURL, sha, description, repoURL, verbose, rootDir })
|
|
89
104
|
} catch (e) {
|
|
90
105
|
if (!(e instanceof GameNotFoundError)) throw e
|
|
91
|
-
const created = await maybeCreateMissingGame(e, game, { teamAccessToken: token })
|
|
106
|
+
const created = await maybeCreateMissingGame(e, game, { apiURL, teamAccessToken: credential.token })
|
|
92
107
|
if (!created) throw e
|
|
93
|
-
result = await uploadOneGame(game, {
|
|
108
|
+
result = await uploadOneGame(game, { credential, apiURL, sha, description, repoURL, verbose, rootDir })
|
|
94
109
|
}
|
|
95
110
|
results.push(result)
|
|
96
111
|
} catch (e) {
|
|
97
112
|
const message = e instanceof Error ? e.message : String(e)
|
|
98
113
|
console.error(` Failed: ${message}`)
|
|
99
|
-
results.push({ ok: false, slug
|
|
114
|
+
results.push({ ok: false, slug, error: message })
|
|
100
115
|
}
|
|
101
116
|
}
|
|
102
117
|
|
|
@@ -107,7 +122,8 @@ export const upload = async (dir: string, options: UploadOptions = {}) => {
|
|
|
107
122
|
}
|
|
108
123
|
|
|
109
124
|
type UploadOneOptions = {
|
|
110
|
-
|
|
125
|
+
credential: TokenEntry
|
|
126
|
+
apiURL: string
|
|
111
127
|
sha: string
|
|
112
128
|
description: string | null
|
|
113
129
|
repoURL: string | null
|
|
@@ -117,7 +133,7 @@ type UploadOneOptions = {
|
|
|
117
133
|
|
|
118
134
|
/** Uploads a single discovered game; throws on failure */
|
|
119
135
|
const uploadOneGame = async (game: DiscoveredGame, opts: UploadOneOptions): Promise<GameSuccess> => {
|
|
120
|
-
const {
|
|
136
|
+
const { credential, apiURL, sha, description, repoURL, verbose, rootDir } = opts
|
|
121
137
|
const { puzzmoFile, distDir } = game
|
|
122
138
|
const gameSlug = puzzmoFile.game.slug
|
|
123
139
|
|
|
@@ -130,7 +146,8 @@ const uploadOneGame = async (game: DiscoveredGame, opts: UploadOneOptions): Prom
|
|
|
130
146
|
console.log(` ${files.length} file(s), ${formatBytes(totalBytes)} total (sha ${sha.slice(0, 8)})`)
|
|
131
147
|
|
|
132
148
|
const result = await uploadFiles(
|
|
133
|
-
|
|
149
|
+
apiURL,
|
|
150
|
+
credential.token,
|
|
134
151
|
gameSlug,
|
|
135
152
|
sha,
|
|
136
153
|
files,
|
|
@@ -146,6 +163,7 @@ const uploadOneGame = async (game: DiscoveredGame, opts: UploadOneOptions): Prom
|
|
|
146
163
|
return {
|
|
147
164
|
ok: true,
|
|
148
165
|
slug: gameSlug,
|
|
166
|
+
source: credential.source,
|
|
149
167
|
fileCount: files.length,
|
|
150
168
|
totalBytes,
|
|
151
169
|
versionID: result.versionID,
|
|
@@ -159,7 +177,7 @@ const printReport = (results: GameResult[]) => {
|
|
|
159
177
|
const successes = results.filter((r): r is GameSuccess => r.ok)
|
|
160
178
|
const failures = results.filter((r): r is GameFailure => !r.ok)
|
|
161
179
|
for (const r of successes) {
|
|
162
|
-
console.log(` OK ${r.slug.padEnd(24)} ${formatBytes(r.totalBytes).padStart(8)} ${r.versionID}`)
|
|
180
|
+
console.log(` OK ${r.slug.padEnd(24)} ${formatBytes(r.totalBytes).padStart(8)} ${r.source.padEnd(20)} ${r.versionID}`)
|
|
163
181
|
}
|
|
164
182
|
for (const r of failures) {
|
|
165
183
|
console.log(` FAIL ${r.slug.padEnd(24)} ${r.error}`)
|
|
@@ -241,7 +259,7 @@ const hashGames = (games: DiscoveredGame[]): string => {
|
|
|
241
259
|
const maybeCreateMissingGame = async (
|
|
242
260
|
err: GameNotFoundError,
|
|
243
261
|
game: DiscoveredGame,
|
|
244
|
-
opts: { teamAccessToken: string },
|
|
262
|
+
opts: { apiURL: string; teamAccessToken: string },
|
|
245
263
|
): Promise<boolean> => {
|
|
246
264
|
const slug = err.slug
|
|
247
265
|
const displayName = game.puzzmoFile.game.displayName
|
|
@@ -258,7 +276,7 @@ const maybeCreateMissingGame = async (
|
|
|
258
276
|
if (p.isCancel(consent) || !consent) return false
|
|
259
277
|
|
|
260
278
|
try {
|
|
261
|
-
const created = await createUserGame({ displayName, teamAccessToken: opts.teamAccessToken })
|
|
279
|
+
const created = await createUserGame({ apiURL: opts.apiURL, displayName, teamAccessToken: opts.teamAccessToken })
|
|
262
280
|
console.log(` Created game ${created.slug} (${created.id})`)
|
|
263
281
|
if (created.slug !== slug) {
|
|
264
282
|
console.warn(` Server picked slug "${created.slug}" but your puzzmo.json uses "${slug}". Update puzzmo.json before re-uploading.`)
|