@lizardon386/sdk-tools 0.0.0-stage → 0.2.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 3178-battle-platform contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,15 @@
1
- # Temporary Holding Version
1
+ # @lizardon386/sdk-tools
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ 3178 SDK 的构建与打包工具:
4
+
5
+ - `bpmap` CLI:`pack` / `inspect` / `unpack` / `doctor`
6
+ - `templates/scripts/setup-sdk.mjs`:把 SDK tarball 安装到游戏工程
7
+ - `templates/scripts/build.mjs`:构建服务端/客户端、生成清单、打包并校验
8
+
9
+ ```bash
10
+ npx bpmap --help
11
+ ```
12
+
13
+ 接入文档:[INTEGRATION_GUIDE.md](https://github.com/Lizardon386/3178-battle-platform/blob/master/INTEGRATION_GUIDE.md)
14
+
15
+ License: MIT
@@ -0,0 +1,34 @@
1
+ export interface HostInfo {
2
+ platform?: string;
3
+ packFormats?: unknown;
4
+ protocolVersions?: unknown;
5
+ apiVersions?: unknown;
6
+ modes?: Record<string, unknown>;
7
+ clientCaps?: unknown;
8
+ }
9
+ export interface DoctorTarget {
10
+ kind: 'source' | 'pack';
11
+ path: string;
12
+ id: string;
13
+ version: string;
14
+ server: string;
15
+ client?: string;
16
+ format: number;
17
+ mode: string;
18
+ apiVersion: number;
19
+ }
20
+ export interface DoctorCheck {
21
+ ok: boolean;
22
+ label: string;
23
+ detail?: string;
24
+ }
25
+ export interface DoctorReport {
26
+ target: DoctorTarget;
27
+ checks: DoctorCheck[];
28
+ }
29
+ export declare function readDoctorTarget(target: string): DoctorTarget;
30
+ export declare function checkHost(target: DoctorTarget, host: HostInfo): DoctorCheck[];
31
+ export declare function fetchHostInfo(host: string): Promise<HostInfo>;
32
+ export declare function localChecks(target: DoctorTarget): DoctorCheck[];
33
+ export declare function doctor(target: string, hostUrl?: string): Promise<DoctorReport>;
34
+ //# sourceMappingURL=doctor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/cli/doctor.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,WAAW,EAAE,CAAC;CACvB;AA2CD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,CAI7D;AAUD,wBAAgB,SAAS,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,QAAQ,GAAG,WAAW,EAAE,CAoB7E;AAED,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAiBnE;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,YAAY,GAAG,WAAW,EAAE,CAa/D;AAED,wBAAsB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAQpF"}
@@ -0,0 +1,125 @@
1
+ import { existsSync, readFileSync, statSync } from 'node:fs';
2
+ import { join, resolve } from 'node:path';
3
+ import { API_VERSION, DEFAULT_MODE } from '@lizardon386/core-abi';
4
+ import { PACK_FORMAT, readPack, readPackSource } from '@lizardon386/pack-format';
5
+ function readSourceTarget(dir) {
6
+ const rawPath = join(dir, 'bpmap.json');
7
+ let raw;
8
+ try {
9
+ const parsed = JSON.parse(readFileSync(rawPath, 'utf8'));
10
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
11
+ throw new Error('不是对象');
12
+ raw = parsed;
13
+ }
14
+ catch (error) {
15
+ throw new Error(`读不到或解析不了源清单:${rawPath}(${error instanceof Error ? error.message : String(error)})`);
16
+ }
17
+ const source = readPackSource(dir);
18
+ return {
19
+ kind: 'source',
20
+ path: dir,
21
+ id: source.id,
22
+ version: source.version,
23
+ server: source.server,
24
+ ...(source.client ? { client: source.client } : {}),
25
+ format: PACK_FORMAT,
26
+ mode: typeof raw.mode === 'string' && raw.mode ? raw.mode : DEFAULT_MODE,
27
+ apiVersion: typeof raw.apiVersion === 'number' && Number.isSafeInteger(raw.apiVersion) && raw.apiVersion > 0
28
+ ? raw.apiVersion
29
+ : API_VERSION,
30
+ };
31
+ }
32
+ function readPackTarget(file) {
33
+ const pack = readPack(file);
34
+ return {
35
+ kind: 'pack',
36
+ path: file,
37
+ id: pack.manifest.id,
38
+ version: pack.manifest.version,
39
+ server: pack.manifest.server,
40
+ ...(pack.manifest.client ? { client: pack.manifest.client } : {}),
41
+ format: pack.manifest.format,
42
+ mode: pack.manifest.mode,
43
+ apiVersion: pack.manifest.apiVersion,
44
+ };
45
+ }
46
+ export function readDoctorTarget(target) {
47
+ const absolute = resolve(target);
48
+ if (!existsSync(absolute))
49
+ throw new Error(`目标不存在:${absolute}`);
50
+ return statSync(absolute).isDirectory() ? readSourceTarget(absolute) : readPackTarget(absolute);
51
+ }
52
+ function includesVersion(value, wanted) {
53
+ return Array.isArray(value) && value.some((item) => item === wanted);
54
+ }
55
+ function modeSupported(value, wanted) {
56
+ return Array.isArray(value) && value.some((item) => item === wanted);
57
+ }
58
+ export function checkHost(target, host) {
59
+ const checks = [];
60
+ checks.push({
61
+ ok: includesVersion(host.packFormats, target.format),
62
+ label: `包格式 ${target.format}`,
63
+ detail: Array.isArray(host.packFormats) ? `宿主支持 ${host.packFormats.join('、')}` : '宿主要求没有返回 packFormats',
64
+ });
65
+ checks.push({
66
+ ok: includesVersion(host.apiVersions, target.apiVersion),
67
+ label: `apiVersion ${target.apiVersion}`,
68
+ detail: Array.isArray(host.apiVersions) ? `宿主支持 ${host.apiVersions.join('、')}` : '宿主要求没有返回 apiVersions',
69
+ });
70
+ const modes = host.modes && typeof host.modes === 'object' ? host.modes : {};
71
+ const profile = modes[target.mode];
72
+ checks.push({
73
+ ok: modeSupported(profile, 1),
74
+ label: `模式 ${target.mode}`,
75
+ detail: profile === undefined ? `宿主没有声明 ${target.mode}` : `宿主支持 ${target.mode} 的 profile ${Array.isArray(profile) ? profile.join('、') : '(格式不对)'}`,
76
+ });
77
+ return checks;
78
+ }
79
+ export async function fetchHostInfo(host) {
80
+ const url = `${host.replace(/\/+$/, '')}/api/version`;
81
+ let response;
82
+ try {
83
+ response = await fetch(url, { signal: AbortSignal.timeout(5_000) });
84
+ }
85
+ catch (error) {
86
+ throw new Error(`连不上宿主 ${url}:${error instanceof Error ? error.message : String(error)}`);
87
+ }
88
+ if (!response.ok)
89
+ throw new Error(`宿主 ${url} 返回 ${response.status}`);
90
+ let parsed;
91
+ try {
92
+ parsed = await response.json();
93
+ }
94
+ catch {
95
+ throw new Error(`宿主 ${url} 返回的不是 JSON`);
96
+ }
97
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
98
+ throw new Error(`宿主 ${url} 返回格式不对`);
99
+ return parsed;
100
+ }
101
+ export function localChecks(target) {
102
+ if (target.kind === 'source') {
103
+ const dir = target.path;
104
+ const checks = [
105
+ { ok: existsSync(join(dir, target.server)), label: `服务端入口 ${target.server}` },
106
+ ];
107
+ if (target.client)
108
+ checks.push({ ok: existsSync(join(dir, target.client)), label: `客户端入口 ${target.client}` });
109
+ return checks;
110
+ }
111
+ return [
112
+ { ok: target.format === PACK_FORMAT, label: `包格式 ${target.format}` },
113
+ { ok: Boolean(target.server), label: `服务端入口 ${target.server}` },
114
+ ];
115
+ }
116
+ export async function doctor(target, hostUrl) {
117
+ const info = readDoctorTarget(target);
118
+ const checks = localChecks(info);
119
+ if (hostUrl) {
120
+ const host = await fetchHostInfo(hostUrl);
121
+ checks.push(...checkHost(info, host));
122
+ }
123
+ return { target: info, checks };
124
+ }
125
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/cli/doctor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":""}
@@ -0,0 +1,142 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
3
+ import { dirname, join, resolve, sep } from 'node:path';
4
+ import { packDirectory, readPack, readPackSource } from '@lizardon386/pack-format';
5
+ import { doctor } from './doctor.js';
6
+ function usage() {
7
+ console.log(`用法:
8
+ bpmap pack <源目录> [--out <文件>]
9
+ bpmap inspect <文件.bpmap>
10
+ bpmap unpack <文件.bpmap> <目录>
11
+ bpmap doctor <源目录|文件.bpmap> [--host <地址>]
12
+
13
+ 源目录必须有 bpmap.json:{ "id", "version", "server", "client?" }。
14
+ 源目录里除 bpmap.json 外的普通文件都会进包。`);
15
+ }
16
+ function fail(message) {
17
+ console.error(`[x] ${message}`);
18
+ process.exit(1);
19
+ }
20
+ function parse(argv) {
21
+ const command = argv[0];
22
+ const args = [];
23
+ let out = null;
24
+ let host = null;
25
+ for (let i = 1; i < argv.length; i += 1) {
26
+ const token = argv[i];
27
+ if (!token)
28
+ continue;
29
+ if (token === '--out') {
30
+ const value = argv[i + 1];
31
+ if (!value)
32
+ fail('--out 后要跟文件路径');
33
+ out = value;
34
+ i += 1;
35
+ }
36
+ else if (token === '--host') {
37
+ const value = argv[i + 1];
38
+ if (!value)
39
+ fail('--host 后要跟宿主地址');
40
+ host = value;
41
+ i += 1;
42
+ }
43
+ else if (token === '--help' || token === '-h') {
44
+ usage();
45
+ process.exit(0);
46
+ }
47
+ else {
48
+ args.push(token);
49
+ }
50
+ }
51
+ return { command, args, out, host };
52
+ }
53
+ const { command, args, out, host } = parse(process.argv.slice(2));
54
+ if (!command) {
55
+ usage();
56
+ process.exit(1);
57
+ }
58
+ try {
59
+ if (command === 'pack') {
60
+ const source = args[0];
61
+ if (!source)
62
+ fail('用法:bpmap pack <源目录> [--out <文件>]');
63
+ const sourceAbs = resolve(source);
64
+ if (!existsSync(join(sourceAbs, 'bpmap.json')))
65
+ fail(`源目录缺 bpmap.json:${sourceAbs}`);
66
+ const manifest = readPackSource(sourceAbs);
67
+ const outAbs = resolve(out ?? join(process.cwd(), `${manifest.id}.bpmap`));
68
+ if (outAbs === sourceAbs || outAbs.startsWith(`${sourceAbs}${sep}`)) {
69
+ fail('输出不能放在源目录里,否则下次打包会把旧包也装进去');
70
+ }
71
+ const packed = packDirectory(sourceAbs, outAbs, manifest);
72
+ const pack = readPack(outAbs);
73
+ const bytes = packed.files.reduce((sum, file) => sum + file.size, 0);
74
+ console.log(`已生成 ${outAbs}`);
75
+ console.log(` id/version:${packed.id} ${packed.version}`);
76
+ console.log(` 模式/apiVersion:${packed.mode} / ${packed.apiVersion}`);
77
+ console.log(` 文件:${packed.files.length} 个,${bytes} 字节`);
78
+ console.log(` 包哈希:${pack.packageHash}`);
79
+ console.log('把文件丢进 games/,下次启动宿主就会加载。');
80
+ }
81
+ else if (command === 'doctor') {
82
+ const target = args[0];
83
+ if (!target)
84
+ fail('用法:bpmap doctor <源目录|文件.bpmap> [--host <地址>]');
85
+ const report = await doctor(target, host ?? undefined);
86
+ console.log(`目标:${report.target.path}`);
87
+ console.log(` id/version:${report.target.id} ${report.target.version}`);
88
+ console.log(` 类型:${report.target.kind === 'source' ? '源目录' : '单文件包'}`);
89
+ console.log(` 模式/apiVersion:${report.target.mode} / ${report.target.apiVersion}`);
90
+ let failed = 0;
91
+ for (const check of report.checks) {
92
+ if (!check.ok)
93
+ failed += 1;
94
+ console.log(` ${check.ok ? '[ok]' : '[x]'} ${check.label}${check.detail ? `(${check.detail})` : ''}`);
95
+ }
96
+ if (host)
97
+ console.log(` 宿主:${host}`);
98
+ if (failed > 0)
99
+ fail(`doctor 发现 ${failed} 项不兼容`);
100
+ }
101
+ else if (command === 'inspect') {
102
+ const file = args[0];
103
+ if (!file)
104
+ fail('用法:bpmap inspect <文件.bpmap>');
105
+ const pack = readPack(resolve(file));
106
+ const bytes = pack.manifest.files.reduce((sum, entry) => sum + entry.size, 0);
107
+ console.log(`包:${resolve(file)}`);
108
+ console.log(` id/version:${pack.manifest.id} ${pack.manifest.version}`);
109
+ console.log(` 模式/apiVersion:${pack.manifest.mode} / ${pack.manifest.apiVersion}`);
110
+ console.log(` 服务端入口:${pack.manifest.server}`);
111
+ console.log(` 客户端入口:${pack.manifest.client ?? '(没有)'}`);
112
+ console.log(` 文件:${pack.manifest.files.length} 个,${bytes} 字节`);
113
+ console.log(` 包哈希:${pack.packageHash}`);
114
+ for (const entry of pack.manifest.files) {
115
+ console.log(` ${entry.path} ${entry.size} ${entry.sha256}`);
116
+ }
117
+ }
118
+ else if (command === 'unpack') {
119
+ const file = args[0];
120
+ const target = args[1];
121
+ if (!file || !target)
122
+ fail('用法:bpmap unpack <文件.bpmap> <目录>');
123
+ const pack = readPack(resolve(file));
124
+ const outDir = resolve(target);
125
+ for (const entry of pack.files) {
126
+ const absolute = resolve(outDir, ...entry.path.split('/'));
127
+ if (absolute !== outDir && !absolute.startsWith(`${outDir}${sep}`))
128
+ fail(`路径越界:${entry.path}`);
129
+ mkdirSync(dirname(absolute), { recursive: true });
130
+ writeFileSync(absolute, entry.bytes);
131
+ }
132
+ console.log(`已解包到 ${outDir}`);
133
+ }
134
+ else {
135
+ usage();
136
+ process.exit(1);
137
+ }
138
+ }
139
+ catch (error) {
140
+ fail(error instanceof Error ? error.message : String(error));
141
+ }
142
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,41 @@
1
1
  {
2
2
  "name": "@lizardon386/sdk-tools",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "description": "3178 SDK 工具:bpmap CLI、doctor 和游戏工程脚本模板",
5
+ "license": "MIT",
6
+ "author": "3178-battle-platform contributors",
7
+ "homepage": "https://github.com/Lizardon386/3178-battle-platform#readme",
8
+ "bugs": {
9
+ "url": "https://github.com/Lizardon386/3178-battle-platform/issues"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/Lizardon386/3178-battle-platform.git",
14
+ "directory": "packages/sdk-tools"
15
+ },
16
+ "engines": {
17
+ "node": ">=20.19.0"
18
+ },
19
+ "keywords": [
20
+ "3178",
21
+ "game-platform",
22
+ "bpmap",
23
+ "sdk-tools"
24
+ ],
25
+ "type": "module",
26
+ "bin": {
27
+ "bpmap": "dist/cli/index.js"
28
+ },
29
+ "files": [
30
+ "dist",
31
+ "templates",
32
+ "src"
33
+ ],
34
+ "dependencies": {
35
+ "@lizardon386/core-abi": "^0.2.0",
36
+ "@lizardon386/pack-format": "^0.2.0"
37
+ },
38
+ "publishConfig": {
39
+ "access": "public"
40
+ }
41
+ }
@@ -0,0 +1,157 @@
1
+ import { existsSync, readFileSync, statSync } from 'node:fs';
2
+ import { join, resolve } from 'node:path';
3
+ import { API_VERSION, DEFAULT_MODE } from '@lizardon386/core-abi';
4
+ import { PACK_FORMAT, readPack, readPackSource } from '@lizardon386/pack-format';
5
+
6
+ export interface HostInfo {
7
+ platform?: string;
8
+ packFormats?: unknown;
9
+ protocolVersions?: unknown;
10
+ apiVersions?: unknown;
11
+ modes?: Record<string, unknown>;
12
+ clientCaps?: unknown;
13
+ }
14
+
15
+ export interface DoctorTarget {
16
+ kind: 'source' | 'pack';
17
+ path: string;
18
+ id: string;
19
+ version: string;
20
+ server: string;
21
+ client?: string;
22
+ format: number;
23
+ mode: string;
24
+ apiVersion: number;
25
+ }
26
+
27
+ export interface DoctorCheck {
28
+ ok: boolean;
29
+ label: string;
30
+ detail?: string;
31
+ }
32
+
33
+ export interface DoctorReport {
34
+ target: DoctorTarget;
35
+ checks: DoctorCheck[];
36
+ }
37
+
38
+ function readSourceTarget(dir: string): DoctorTarget {
39
+ const rawPath = join(dir, 'bpmap.json');
40
+ let raw: Record<string, unknown>;
41
+ try {
42
+ const parsed = JSON.parse(readFileSync(rawPath, 'utf8')) as unknown;
43
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new Error('不是对象');
44
+ raw = parsed as Record<string, unknown>;
45
+ } catch (error) {
46
+ throw new Error(`读不到或解析不了源清单:${rawPath}(${error instanceof Error ? error.message : String(error)})`);
47
+ }
48
+ const source = readPackSource(dir);
49
+ return {
50
+ kind: 'source',
51
+ path: dir,
52
+ id: source.id,
53
+ version: source.version,
54
+ server: source.server,
55
+ ...(source.client ? { client: source.client } : {}),
56
+ format: PACK_FORMAT,
57
+ mode: typeof raw.mode === 'string' && raw.mode ? raw.mode : DEFAULT_MODE,
58
+ apiVersion: typeof raw.apiVersion === 'number' && Number.isSafeInteger(raw.apiVersion) && raw.apiVersion > 0
59
+ ? raw.apiVersion
60
+ : API_VERSION,
61
+ };
62
+ }
63
+
64
+ function readPackTarget(file: string): DoctorTarget {
65
+ const pack = readPack(file);
66
+ return {
67
+ kind: 'pack',
68
+ path: file,
69
+ id: pack.manifest.id,
70
+ version: pack.manifest.version,
71
+ server: pack.manifest.server,
72
+ ...(pack.manifest.client ? { client: pack.manifest.client } : {}),
73
+ format: pack.manifest.format,
74
+ mode: pack.manifest.mode,
75
+ apiVersion: pack.manifest.apiVersion,
76
+ };
77
+ }
78
+
79
+ export function readDoctorTarget(target: string): DoctorTarget {
80
+ const absolute = resolve(target);
81
+ if (!existsSync(absolute)) throw new Error(`目标不存在:${absolute}`);
82
+ return statSync(absolute).isDirectory() ? readSourceTarget(absolute) : readPackTarget(absolute);
83
+ }
84
+
85
+ function includesVersion(value: unknown, wanted: number): boolean {
86
+ return Array.isArray(value) && value.some((item) => item === wanted);
87
+ }
88
+
89
+ function modeSupported(value: unknown, wanted: number): boolean {
90
+ return Array.isArray(value) && value.some((item) => item === wanted);
91
+ }
92
+
93
+ export function checkHost(target: DoctorTarget, host: HostInfo): DoctorCheck[] {
94
+ const checks: DoctorCheck[] = [];
95
+ checks.push({
96
+ ok: includesVersion(host.packFormats, target.format),
97
+ label: `包格式 ${target.format}`,
98
+ detail: Array.isArray(host.packFormats) ? `宿主支持 ${host.packFormats.join('、')}` : '宿主要求没有返回 packFormats',
99
+ });
100
+ checks.push({
101
+ ok: includesVersion(host.apiVersions, target.apiVersion),
102
+ label: `apiVersion ${target.apiVersion}`,
103
+ detail: Array.isArray(host.apiVersions) ? `宿主支持 ${host.apiVersions.join('、')}` : '宿主要求没有返回 apiVersions',
104
+ });
105
+ const modes = host.modes && typeof host.modes === 'object' ? host.modes : {};
106
+ const profile = (modes as Record<string, unknown>)[target.mode];
107
+ checks.push({
108
+ ok: modeSupported(profile, 1),
109
+ label: `模式 ${target.mode}`,
110
+ detail: profile === undefined ? `宿主没有声明 ${target.mode}` : `宿主支持 ${target.mode} 的 profile ${Array.isArray(profile) ? profile.join('、') : '(格式不对)'}`,
111
+ });
112
+ return checks;
113
+ }
114
+
115
+ export async function fetchHostInfo(host: string): Promise<HostInfo> {
116
+ const url = `${host.replace(/\/+$/, '')}/api/version`;
117
+ let response: Response;
118
+ try {
119
+ response = await fetch(url, { signal: AbortSignal.timeout(5_000) });
120
+ } catch (error) {
121
+ throw new Error(`连不上宿主 ${url}:${error instanceof Error ? error.message : String(error)}`);
122
+ }
123
+ if (!response.ok) throw new Error(`宿主 ${url} 返回 ${response.status}`);
124
+ let parsed: unknown;
125
+ try {
126
+ parsed = await response.json();
127
+ } catch {
128
+ throw new Error(`宿主 ${url} 返回的不是 JSON`);
129
+ }
130
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new Error(`宿主 ${url} 返回格式不对`);
131
+ return parsed as HostInfo;
132
+ }
133
+
134
+ export function localChecks(target: DoctorTarget): DoctorCheck[] {
135
+ if (target.kind === 'source') {
136
+ const dir = target.path;
137
+ const checks: DoctorCheck[] = [
138
+ { ok: existsSync(join(dir, target.server)), label: `服务端入口 ${target.server}` },
139
+ ];
140
+ if (target.client) checks.push({ ok: existsSync(join(dir, target.client)), label: `客户端入口 ${target.client}` });
141
+ return checks;
142
+ }
143
+ return [
144
+ { ok: target.format === PACK_FORMAT, label: `包格式 ${target.format}` },
145
+ { ok: Boolean(target.server), label: `服务端入口 ${target.server}` },
146
+ ];
147
+ }
148
+
149
+ export async function doctor(target: string, hostUrl?: string): Promise<DoctorReport> {
150
+ const info = readDoctorTarget(target);
151
+ const checks = localChecks(info);
152
+ if (hostUrl) {
153
+ const host = await fetchHostInfo(hostUrl);
154
+ checks.push(...checkHost(info, host));
155
+ }
156
+ return { target: info, checks };
157
+ }
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
3
+ import { dirname, join, resolve, sep } from 'node:path';
4
+ import { packDirectory, readPack, readPackSource } from '@lizardon386/pack-format';
5
+ import { doctor } from './doctor.js';
6
+
7
+ function usage(): void {
8
+ console.log(`用法:
9
+ bpmap pack <源目录> [--out <文件>]
10
+ bpmap inspect <文件.bpmap>
11
+ bpmap unpack <文件.bpmap> <目录>
12
+ bpmap doctor <源目录|文件.bpmap> [--host <地址>]
13
+
14
+ 源目录必须有 bpmap.json:{ "id", "version", "server", "client?" }。
15
+ 源目录里除 bpmap.json 外的普通文件都会进包。`);
16
+ }
17
+
18
+ function fail(message: string): never {
19
+ console.error(`[x] ${message}`);
20
+ process.exit(1);
21
+ }
22
+
23
+ interface Parsed {
24
+ command: string | undefined;
25
+ args: string[];
26
+ out: string | null;
27
+ host: string | null;
28
+ }
29
+
30
+ function parse(argv: string[]): Parsed {
31
+ const command = argv[0];
32
+ const args: string[] = [];
33
+ let out: string | null = null;
34
+ let host: string | null = null;
35
+ for (let i = 1; i < argv.length; i += 1) {
36
+ const token = argv[i];
37
+ if (!token) continue;
38
+ if (token === '--out') {
39
+ const value = argv[i + 1];
40
+ if (!value) fail('--out 后要跟文件路径');
41
+ out = value;
42
+ i += 1;
43
+ } else if (token === '--host') {
44
+ const value = argv[i + 1];
45
+ if (!value) fail('--host 后要跟宿主地址');
46
+ host = value;
47
+ i += 1;
48
+ } else if (token === '--help' || token === '-h') {
49
+ usage();
50
+ process.exit(0);
51
+ } else {
52
+ args.push(token);
53
+ }
54
+ }
55
+ return { command, args, out, host };
56
+ }
57
+
58
+ const { command, args, out, host } = parse(process.argv.slice(2));
59
+ if (!command) {
60
+ usage();
61
+ process.exit(1);
62
+ }
63
+
64
+ try {
65
+ if (command === 'pack') {
66
+ const source = args[0];
67
+ if (!source) fail('用法:bpmap pack <源目录> [--out <文件>]');
68
+ const sourceAbs = resolve(source);
69
+ if (!existsSync(join(sourceAbs, 'bpmap.json'))) fail(`源目录缺 bpmap.json:${sourceAbs}`);
70
+ const manifest = readPackSource(sourceAbs);
71
+ const outAbs = resolve(out ?? join(process.cwd(), `${manifest.id}.bpmap`));
72
+ if (outAbs === sourceAbs || outAbs.startsWith(`${sourceAbs}${sep}`)) {
73
+ fail('输出不能放在源目录里,否则下次打包会把旧包也装进去');
74
+ }
75
+ const packed = packDirectory(sourceAbs, outAbs, manifest);
76
+ const pack = readPack(outAbs);
77
+ const bytes = packed.files.reduce((sum, file) => sum + file.size, 0);
78
+ console.log(`已生成 ${outAbs}`);
79
+ console.log(` id/version:${packed.id} ${packed.version}`);
80
+ console.log(` 模式/apiVersion:${packed.mode} / ${packed.apiVersion}`);
81
+ console.log(` 文件:${packed.files.length} 个,${bytes} 字节`);
82
+ console.log(` 包哈希:${pack.packageHash}`);
83
+ console.log('把文件丢进 games/,下次启动宿主就会加载。');
84
+ } else if (command === 'doctor') {
85
+ const target = args[0];
86
+ if (!target) fail('用法:bpmap doctor <源目录|文件.bpmap> [--host <地址>]');
87
+ const report = await doctor(target, host ?? undefined);
88
+ console.log(`目标:${report.target.path}`);
89
+ console.log(` id/version:${report.target.id} ${report.target.version}`);
90
+ console.log(` 类型:${report.target.kind === 'source' ? '源目录' : '单文件包'}`);
91
+ console.log(` 模式/apiVersion:${report.target.mode} / ${report.target.apiVersion}`);
92
+ let failed = 0;
93
+ for (const check of report.checks) {
94
+ if (!check.ok) failed += 1;
95
+ console.log(` ${check.ok ? '[ok]' : '[x]'} ${check.label}${check.detail ? `(${check.detail})` : ''}`);
96
+ }
97
+ if (host) console.log(` 宿主:${host}`);
98
+ if (failed > 0) fail(`doctor 发现 ${failed} 项不兼容`);
99
+ } else if (command === 'inspect') {
100
+ const file = args[0];
101
+ if (!file) fail('用法:bpmap inspect <文件.bpmap>');
102
+ const pack = readPack(resolve(file));
103
+ const bytes = pack.manifest.files.reduce((sum, entry) => sum + entry.size, 0);
104
+ console.log(`包:${resolve(file)}`);
105
+ console.log(` id/version:${pack.manifest.id} ${pack.manifest.version}`);
106
+ console.log(` 模式/apiVersion:${pack.manifest.mode} / ${pack.manifest.apiVersion}`);
107
+ console.log(` 服务端入口:${pack.manifest.server}`);
108
+ console.log(` 客户端入口:${pack.manifest.client ?? '(没有)'}`);
109
+ console.log(` 文件:${pack.manifest.files.length} 个,${bytes} 字节`);
110
+ console.log(` 包哈希:${pack.packageHash}`);
111
+ for (const entry of pack.manifest.files) {
112
+ console.log(` ${entry.path} ${entry.size} ${entry.sha256}`);
113
+ }
114
+ } else if (command === 'unpack') {
115
+ const file = args[0];
116
+ const target = args[1];
117
+ if (!file || !target) fail('用法:bpmap unpack <文件.bpmap> <目录>');
118
+ const pack = readPack(resolve(file));
119
+ const outDir = resolve(target);
120
+ for (const entry of pack.files) {
121
+ const absolute = resolve(outDir, ...entry.path.split('/'));
122
+ if (absolute !== outDir && !absolute.startsWith(`${outDir}${sep}`)) fail(`路径越界:${entry.path}`);
123
+ mkdirSync(dirname(absolute), { recursive: true });
124
+ writeFileSync(absolute, entry.bytes);
125
+ }
126
+ console.log(`已解包到 ${outDir}`);
127
+ } else {
128
+ usage();
129
+ process.exit(1);
130
+ }
131
+ } catch (error) {
132
+ fail(error instanceof Error ? error.message : String(error));
133
+ }
@@ -0,0 +1,148 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * 用法:node scripts/build.mjs [--platform <3178 仓库>] [--start] [--host <地址>]
4
+ * [--port 3178] [--lan-port 3000] [--out dist/<id>.bpmap]
5
+ * 构建 server 和客户端、生成 dist-pack/bpmap.json、打包并校验;
6
+ * --platform 复制到平台的 games/,--start 启动宿主,--host 调 bpmap doctor。
7
+ */
8
+ import { createRequire } from 'node:module';
9
+ import { copyFileSync, cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
10
+ import { dirname, isAbsolute, join, resolve, sep } from 'node:path';
11
+ import { spawnSync } from 'node:child_process';
12
+ import { build } from 'esbuild';
13
+ import { API_VERSION, DEFAULT_MODE } from '@lizardon386/game-sdk';
14
+ import { packDirectory, readPack, readPackSource } from '@lizardon386/game-sdk/node';
15
+
16
+ const args = process.argv.slice(2);
17
+ const argValue = (name, fallback) => {
18
+ const index = args.indexOf(name);
19
+ if (index < 0) return fallback;
20
+ const value = args[index + 1];
21
+ if (!value || value.startsWith('--')) fail(`${name} 后面要跟一个值`);
22
+ return value;
23
+ };
24
+ const hasFlag = (name) => args.includes(name);
25
+
26
+ const gameRoot = process.cwd();
27
+ const packagePath = join(gameRoot, 'package.json');
28
+ if (!existsSync(packagePath)) fail('没有 package.json,先跑 node scripts/setup-sdk.mjs');
29
+
30
+ const pkg = JSON.parse(readFileSync(packagePath, 'utf8'));
31
+ const id = pkg.bp?.id ?? pkg.name;
32
+ const version = pkg.version;
33
+ const entry = pkg.bp?.entry ?? 'client/client.js';
34
+ const outDir = join(gameRoot, 'dist-pack');
35
+ const packFile = resolve(gameRoot, argValue('--out', `dist/${id}.bpmap`));
36
+ const platformRoot = argValue('--platform', null);
37
+ const hostUrl = argValue('--host', null);
38
+ const shouldStart = hasFlag('--start');
39
+
40
+ if (!/^[a-z0-9][a-z0-9-]{0,31}$/.test(String(id))) {
41
+ fail(`package.json 的 bp.id 不合法:${id}(只允许小写字母、数字、连字符)`);
42
+ }
43
+ if (typeof version !== 'string' || !/^\S{1,64}$/.test(version)) {
44
+ fail(`package.json 的 version 不合法:${version}`);
45
+ }
46
+ if (typeof entry !== 'string' || entry.includes('..') || isAbsolute(entry) || entry.includes('\\')) {
47
+ fail(`package.json 的 bp.entry 必须是包内相对路径:${entry}`);
48
+ }
49
+ if (packFile === outDir || packFile.startsWith(`${outDir}${sep}`)) {
50
+ fail('--out 不能放在 dist-pack 里,否则下次打包会把旧包也装进去');
51
+ }
52
+ if (shouldStart && !platformRoot) fail('--start 需要同时给 --platform <3178 仓库>');
53
+
54
+ const hasClient = existsSync(join(gameRoot, 'src', 'client.ts'));
55
+ if (!hasClient && entry !== 'client/client.js') {
56
+ log(`没有 src/client.ts,忽略 bp.entry=${entry},按无客户端游戏打包`);
57
+ }
58
+
59
+ rmSync(outDir, { recursive: true, force: true });
60
+ mkdirSync(outDir, { recursive: true });
61
+
62
+ await build({
63
+ entryPoints: [join(gameRoot, 'src', 'server.ts')],
64
+ bundle: true,
65
+ platform: 'node',
66
+ format: 'esm',
67
+ external: ['@lizardon386/game-sdk', '@lizardon386/game-sdk/node', '@lizardon386/game-sdk/kit/server', '@lizardon386/core-abi'],
68
+ outfile: join(outDir, 'server.js'),
69
+ logLevel: 'info',
70
+ });
71
+
72
+ if (hasClient) {
73
+ const clientOut = join(outDir, entry);
74
+ await build({
75
+ entryPoints: [join(gameRoot, 'src', 'client.ts')],
76
+ bundle: true,
77
+ platform: 'browser',
78
+ format: 'esm',
79
+ outfile: clientOut,
80
+ logLevel: 'info',
81
+ });
82
+ if (existsSync(join(gameRoot, 'src', 'assets'))) {
83
+ cpSync(join(gameRoot, 'src', 'assets'), join(dirname(clientOut), 'assets'), { recursive: true });
84
+ }
85
+ }
86
+
87
+ const manifest = {
88
+ id,
89
+ version,
90
+ mode: pkg.bp?.mode ?? DEFAULT_MODE,
91
+ apiVersion: pkg.bp?.apiVersion ?? API_VERSION,
92
+ server: 'server.js',
93
+ ...(hasClient ? { client: entry } : {}),
94
+ };
95
+ writeFileSync(join(outDir, 'bpmap.json'), JSON.stringify(manifest, null, 2) + '\n');
96
+
97
+ const source = readPackSource(outDir);
98
+ const packed = packDirectory(outDir, packFile, source);
99
+ const pack = readPack(packFile);
100
+ const bytes = packed.files.reduce((sum, file) => sum + file.size, 0);
101
+
102
+ console.log('');
103
+ console.log(`已生成 ${packFile}`);
104
+ console.log(` id/version:${packed.id} ${packed.version}`);
105
+ console.log(` 文件:${packed.files.length} 个,${bytes} 字节`);
106
+ console.log(` 包哈希:${pack.packageHash}`);
107
+
108
+ if (platformRoot) {
109
+ const platform = resolve(gameRoot, platformRoot);
110
+ const gamesDir = join(platform, 'games');
111
+ if (!existsSync(gamesDir)) fail(`平台里没有 games 目录:${gamesDir}`);
112
+ const target = join(gamesDir, `${id}.bpmap`);
113
+ copyFileSync(packFile, target);
114
+ console.log(`已安装到 ${target}`);
115
+ }
116
+
117
+ if (hostUrl) {
118
+ const require = createRequire(import.meta.url);
119
+ const cli = join(dirname(require.resolve('@lizardon386/sdk-tools/package.json')), 'dist', 'cli', 'index.js');
120
+ console.log(`检查宿主兼容性:${hostUrl}`);
121
+ const result = spawnSync(process.execPath, [cli, 'doctor', packFile, '--host', hostUrl], { stdio: 'inherit' });
122
+ if (result.status !== 0) process.exit(result.status ?? 1);
123
+ }
124
+
125
+ if (shouldStart) {
126
+ const platform = resolve(gameRoot, platformRoot);
127
+ const hostCli = join(platform, 'packages', 'hostd', 'dist', 'cli.js');
128
+ if (!existsSync(hostCli)) fail('平台还没构建:先在平台仓库跑 npm run build');
129
+ const port = argValue('--port', '3178');
130
+ const lanPort = argValue('--lan-port', '3000');
131
+ console.log(`启动宿主:http://127.0.0.1:${port}`);
132
+ const result = spawnSync(process.execPath, [hostCli, 'host', '--port', port, '--lan-port', lanPort], {
133
+ cwd: platform,
134
+ stdio: 'inherit',
135
+ });
136
+ process.exit(result.status ?? 0);
137
+ }
138
+
139
+ console.log('下一步:重启宿主,建房时就能在列表里看到它。');
140
+
141
+ function fail(message) {
142
+ console.error(`[x] ${message}`);
143
+ process.exit(1);
144
+ }
145
+
146
+ function log(message) {
147
+ console.log(`[i] ${message}`);
148
+ }
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * 用法:node setup-sdk.mjs [--release sdk-v0.1.0] [--tarballs <Release 目录>] [--platform <3178 仓库>] [--sdk-dir sdk]
4
+ * 三种来源按顺序选一个:
5
+ * --release 从 GitHub Release 下载四个 tarball、验 SHA256,再安装(外部接入一条命令)
6
+ * --tarballs 直接安装 Release 下载目录里的四个 tarball
7
+ * --platform 从平台仓库打四个 tarball 再安装(开发时用)
8
+ */
9
+ import { createHash } from 'node:crypto';
10
+ import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
11
+ import { basename, dirname, join, resolve } from 'node:path';
12
+ import { spawnSync } from 'node:child_process';
13
+
14
+ const args = process.argv.slice(2);
15
+ const argValue = (name, fallback) => {
16
+ const index = args.indexOf(name);
17
+ if (index < 0) return fallback;
18
+ const value = args[index + 1];
19
+ if (!value || value.startsWith('--')) fail(`${name} 后面要跟一个值`);
20
+ return value;
21
+ };
22
+
23
+ const gameRoot = process.cwd();
24
+ const releaseDir = argValue('--tarballs', null);
25
+ const releaseTag = argValue('--release', null);
26
+ const releaseBase = argValue('--release-base', 'https://github.com/Lizardon386/3178-battle-platform/releases/download');
27
+ const platformRoot = resolve(argValue('--platform', '../3178-battle-platform'));
28
+ const sdkDir = resolve(argValue('--sdk-dir', 'sdk'));
29
+ const npmCli = join(dirname(process.execPath), 'node_modules', 'npm', 'bin', 'npm-cli.js');
30
+ const PACKAGE_PREFIXES = ['lizardon386-core-abi-', 'lizardon386-pack-format-', 'lizardon386-game-sdk-', 'lizardon386-sdk-tools-'];
31
+
32
+ function fail(message) {
33
+ console.error(`[x] ${message}`);
34
+ process.exit(1);
35
+ }
36
+
37
+ function run(command, commandArgs, cwd) {
38
+ const result = spawnSync(command, commandArgs, { cwd, stdio: 'inherit' });
39
+ if (result.error) fail(result.error.message);
40
+ if (result.status !== 0) process.exit(result.status ?? 1);
41
+ }
42
+
43
+ function npm(commandArgs, cwd) {
44
+ if (existsSync(npmCli)) {
45
+ run(process.execPath, [npmCli, ...commandArgs], cwd);
46
+ return;
47
+ }
48
+ const result = spawnSync('npm', commandArgs, { cwd, stdio: 'inherit', shell: true });
49
+ if (result.error) fail(result.error.message);
50
+ if (result.status !== 0) process.exit(result.status ?? 1);
51
+ }
52
+
53
+ if (!releaseDir && !releaseTag && !existsSync(join(platformRoot, 'packages', 'game-sdk', 'package.json'))) {
54
+ fail(`找不到平台仓库:${platformRoot},用 --platform 指向 3178-battle-platform;或用 --release sdk-v0.1.0 / --tarballs`);
55
+ }
56
+
57
+ const packagePath = join(gameRoot, 'package.json');
58
+ if (!existsSync(packagePath)) {
59
+ writeFileSync(packagePath, JSON.stringify({
60
+ name: basename(gameRoot).toLowerCase().replace(/[^a-z0-9-]+/g, '-').replace(/^-+|-+$/g, '') || 'mygame',
61
+ version: '0.1.0',
62
+ private: true,
63
+ type: 'module',
64
+ }, null, 2) + '\n');
65
+ console.log('没有 package.json,已生成一个最小版本');
66
+ } else {
67
+ const pkg = JSON.parse(readFileSync(packagePath, 'utf8'));
68
+ if (pkg.type !== 'module') {
69
+ pkg.type = 'module';
70
+ writeFileSync(packagePath, JSON.stringify(pkg, null, 2) + '\n');
71
+ console.log('package.json 已补上 "type": "module"');
72
+ }
73
+ }
74
+
75
+ if (!releaseDir && !releaseTag &&
76
+ (!existsSync(join(platformRoot, 'packages', 'game-sdk', 'dist', 'index.js')) ||
77
+ !existsSync(join(platformRoot, 'packages', 'core-abi', 'dist', 'index.js')) ||
78
+ !existsSync(join(platformRoot, 'packages', 'pack-format', 'dist', 'index.js')) ||
79
+ !existsSync(join(platformRoot, 'packages', 'sdk-tools', 'dist', 'cli', 'index.js')))) {
80
+ console.log('平台还没构建,先跑 npm run build');
81
+ npm(['run', 'build'], platformRoot);
82
+ }
83
+
84
+ let tarballs;
85
+ if (releaseTag) {
86
+ mkdirSync(sdkDir, { recursive: true });
87
+ for (const name of readdirSync(sdkDir)) {
88
+ if (name.endsWith('.tgz')) rmSync(join(sdkDir, name));
89
+ }
90
+ const base = `${releaseBase.replace(/\/+$/, '')}/${releaseTag}`;
91
+ const sumsResponse = await fetch(`${base}/SHA256SUMS`, { signal: AbortSignal.timeout(15_000) });
92
+ if (!sumsResponse.ok) fail(`下载 SHA256SUMS 失败:${sumsResponse.status}`);
93
+ const sums = (await sumsResponse.text())
94
+ .split('\n')
95
+ .map((line) => line.trim())
96
+ .filter((line) => line && !line.startsWith('#'))
97
+ .map((line) => {
98
+ const [hash, name] = line.split(/\s+/);
99
+ return { hash, name };
100
+ })
101
+ .filter(({ name }) => name && PACKAGE_PREFIXES.some((prefix) => name.startsWith(prefix)) && name.endsWith('.tgz'));
102
+ for (const prefix of PACKAGE_PREFIXES) {
103
+ if (!sums.some(({ name }) => name.startsWith(prefix))) fail(`Release ${releaseTag} 缺少 ${prefix}*.tgz`);
104
+ }
105
+ for (const { hash, name } of sums) {
106
+ const response = await fetch(`${base}/${name}`, { signal: AbortSignal.timeout(60_000) });
107
+ if (!response.ok) fail(`下载 ${name} 失败:${response.status}`);
108
+ const bytes = Buffer.from(await response.arrayBuffer());
109
+ const actual = createHash('sha256').update(bytes).digest('hex');
110
+ if (actual !== hash) fail(`SHA256 不匹配:${name}(${actual} != ${hash})`);
111
+ writeFileSync(join(sdkDir, name), bytes);
112
+ console.log(`已下载 ${name}`);
113
+ }
114
+ tarballs = sums.map(({ name }) => join(sdkDir, name));
115
+ } else if (releaseDir) {
116
+ const dir = resolve(releaseDir);
117
+ if (!existsSync(dir)) fail(`Release 目录不存在:${dir}`);
118
+ const found = readdirSync(dir).filter((name) => name.endsWith('.tgz'));
119
+ for (const prefix of PACKAGE_PREFIXES) {
120
+ if (!found.some((name) => name.startsWith(prefix))) fail(`Release 目录缺少 ${prefix}*.tgz:${dir}`);
121
+ }
122
+ tarballs = found.map((name) => join(dir, name));
123
+ } else {
124
+ mkdirSync(sdkDir, { recursive: true });
125
+ for (const name of readdirSync(sdkDir)) {
126
+ if (name.endsWith('.tgz')) rmSync(join(sdkDir, name));
127
+ }
128
+
129
+ npm(['pack', './packages/core-abi', '--pack-destination', sdkDir], platformRoot);
130
+ npm(['pack', './packages/pack-format', '--pack-destination', sdkDir], platformRoot);
131
+ npm(['pack', './packages/game-sdk', '--pack-destination', sdkDir], platformRoot);
132
+ npm(['pack', './packages/sdk-tools', '--pack-destination', sdkDir], platformRoot);
133
+
134
+ tarballs = readdirSync(sdkDir)
135
+ .filter((name) => name.endsWith('.tgz'))
136
+ .map((name) => join(sdkDir, name));
137
+ }
138
+
139
+ npm(['install', '--save-dev', ...tarballs, 'esbuild'], gameRoot);
140
+
141
+ console.log('');
142
+ console.log(`SDK 已安装到 ${gameRoot}`);
143
+ console.log(` ${tarballs.map((file) => basename(file)).join('、')}`);
144
+ console.log('下一步:node scripts/build.mjs');