gamealgo-cli 0.1.2 → 0.1.3
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 +1 -1
- package/cli/README.md +3 -1
- package/dist/gamealgo.js +54 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -40,7 +40,7 @@ gamealgo key create --name <runtime-name> --json
|
|
|
40
40
|
gamealgo key reveal --name <runtime-name> --json
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
安装和更新 CLI 都使用 `npm install -g gamealgo-cli@latest`,不需要拉取 GameAlgo Client
|
|
43
|
+
安装和更新 CLI 都使用 `npm install -g gamealgo-cli@latest`,不需要拉取 GameAlgo Client 仓库。请在每个游戏项目根目录执行 `gamealgo login`,凭据保存在项目的 `.gamealgo/cli.json`,不同游戏互不覆盖。`gamealgo docs` 从当前 Admin Host 获取最新 AI 文档;登录前显式传 `--host`,登录后 CLI 会使用已保存的 Host。需要网页阅读时使用 `gamealgo docs --open`。
|
|
44
44
|
|
|
45
45
|
如果已有可用 Client Game Key,就复用或 reveal;如果没有,就创建。Client Game Key 格式为 `ga_live_*`,用于游戏运行时拉配置、拉配置文件、上报事件。
|
|
46
46
|
|
package/cli/README.md
CHANGED
|
@@ -56,7 +56,9 @@ gamealgo login \
|
|
|
56
56
|
--admin-key ga_admin_xxx
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
请在游戏项目根目录执行登录。登录信息会写入当前目录的 `.gamealgo/cli.json`,CLI 会把文件权限设置为 `600`,并自动忽略整个 `.gamealgo/` 目录,避免密钥被提交到 Git。后续命令会从当前目录逐级向上查找该文件,因此可以在项目子目录执行;不同游戏仓库的登录信息互不覆盖。
|
|
60
|
+
|
|
61
|
+
旧版的 `~/.gamealgo/cli.json` 仍可作为未找到项目级配置时的兜底。也可以通过环境变量临时传入:
|
|
60
62
|
|
|
61
63
|
```bash
|
|
62
64
|
GAMEALGO_ADMIN_HOST=<admin-host>
|
package/dist/gamealgo.js
CHANGED
|
@@ -7,7 +7,9 @@ import { homedir } from "node:os";
|
|
|
7
7
|
import { createHash } from "node:crypto";
|
|
8
8
|
import { spawn } from "node:child_process";
|
|
9
9
|
import YAML from "yaml";
|
|
10
|
-
const
|
|
10
|
+
const GLOBAL_CONFIG_PATH = join(homedir(), ".gamealgo", "cli.json");
|
|
11
|
+
const PROJECT_CONFIG_DIR = ".gamealgo";
|
|
12
|
+
const PROJECT_CONFIG_FILE = "cli.json";
|
|
11
13
|
const DOCS_CACHE_ROOT = join(homedir(), ".gamealgo", "docs");
|
|
12
14
|
const SCRIPT_EXTENSIONS = new Set([".js", ".lua"]);
|
|
13
15
|
const FILE_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_.-]*$/;
|
|
@@ -135,12 +137,13 @@ async function login(args, global) {
|
|
|
135
137
|
if (session.principal.type !== "game_admin" || !session.principal.gameId) {
|
|
136
138
|
throw new Error("admin key is not scoped to a game");
|
|
137
139
|
}
|
|
138
|
-
await saveConfig({ host, adminKey });
|
|
140
|
+
const configPath = await saveConfig({ host, adminKey });
|
|
139
141
|
await printResult({
|
|
140
142
|
ok: true,
|
|
141
143
|
host,
|
|
142
144
|
gameId: session.principal.gameId,
|
|
143
145
|
keyName: session.principal.keyName,
|
|
146
|
+
configPath,
|
|
144
147
|
}, global);
|
|
145
148
|
}
|
|
146
149
|
async function handleDocs(args, global) {
|
|
@@ -1398,18 +1401,59 @@ function requireFlagValue(args, index, flag) {
|
|
|
1398
1401
|
return value;
|
|
1399
1402
|
}
|
|
1400
1403
|
async function loadConfig() {
|
|
1401
|
-
|
|
1402
|
-
|
|
1404
|
+
const localPath = await findProjectConfigPath(process.cwd());
|
|
1405
|
+
const paths = localPath && localPath !== GLOBAL_CONFIG_PATH
|
|
1406
|
+
? [localPath, GLOBAL_CONFIG_PATH]
|
|
1407
|
+
: [GLOBAL_CONFIG_PATH];
|
|
1408
|
+
for (const configPath of paths) {
|
|
1409
|
+
try {
|
|
1410
|
+
return JSON.parse(await readFile(configPath, "utf8"));
|
|
1411
|
+
}
|
|
1412
|
+
catch {
|
|
1413
|
+
// Try the next supported config location.
|
|
1414
|
+
}
|
|
1403
1415
|
}
|
|
1404
|
-
|
|
1405
|
-
|
|
1416
|
+
return null;
|
|
1417
|
+
}
|
|
1418
|
+
async function findProjectConfigPath(startDir) {
|
|
1419
|
+
let currentDir = startDir;
|
|
1420
|
+
while (true) {
|
|
1421
|
+
const candidate = join(currentDir, PROJECT_CONFIG_DIR, PROJECT_CONFIG_FILE);
|
|
1422
|
+
try {
|
|
1423
|
+
await readFile(candidate, "utf8");
|
|
1424
|
+
return candidate;
|
|
1425
|
+
}
|
|
1426
|
+
catch {
|
|
1427
|
+
const parentDir = dirname(currentDir);
|
|
1428
|
+
if (parentDir === currentDir)
|
|
1429
|
+
return null;
|
|
1430
|
+
currentDir = parentDir;
|
|
1431
|
+
}
|
|
1406
1432
|
}
|
|
1407
1433
|
}
|
|
1408
1434
|
async function saveConfig(config) {
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
await
|
|
1412
|
-
await chmod(
|
|
1435
|
+
const configDir = join(process.cwd(), PROJECT_CONFIG_DIR);
|
|
1436
|
+
const configPath = join(configDir, PROJECT_CONFIG_FILE);
|
|
1437
|
+
await mkdir(configDir, { recursive: true, mode: 0o700 });
|
|
1438
|
+
await chmod(configDir, 0o700).catch(() => undefined);
|
|
1439
|
+
await ensureProjectConfigIgnored(configDir);
|
|
1440
|
+
await writeFile(configPath, JSON.stringify(config, null, 2) + "\n", { encoding: "utf8", mode: 0o600 });
|
|
1441
|
+
await chmod(configPath, 0o600).catch(() => undefined);
|
|
1442
|
+
return configPath;
|
|
1443
|
+
}
|
|
1444
|
+
async function ensureProjectConfigIgnored(configDir) {
|
|
1445
|
+
const ignorePath = join(configDir, ".gitignore");
|
|
1446
|
+
try {
|
|
1447
|
+
const existing = await readFile(ignorePath, "utf8");
|
|
1448
|
+
const rules = existing.split(/\r?\n/).map((line) => line.trim());
|
|
1449
|
+
if (rules.includes("*") || rules.includes(PROJECT_CONFIG_FILE) || rules.includes(`/${PROJECT_CONFIG_FILE}`))
|
|
1450
|
+
return;
|
|
1451
|
+
const separator = existing && !existing.endsWith("\n") ? "\n" : "";
|
|
1452
|
+
await writeFile(ignorePath, `${existing}${separator}${PROJECT_CONFIG_FILE}\n`, { encoding: "utf8", mode: 0o600 });
|
|
1453
|
+
}
|
|
1454
|
+
catch {
|
|
1455
|
+
await writeFile(ignorePath, "*\n", { encoding: "utf8", mode: 0o600 });
|
|
1456
|
+
}
|
|
1413
1457
|
}
|
|
1414
1458
|
async function readObjectFile(filePath, label) {
|
|
1415
1459
|
const text = await readFile(filePath, "utf8");
|