sapdon 3.3.3 → 3.4.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/README.md +17 -7
- package/doc/dev/architecture.md +418 -415
- package/doc/dev/cli.md +467 -467
- package/doc/dev/core.md +751 -717
- package/doc/dev/lr-paradigm.md +85 -0
- package/doc/dev/workflow.md +257 -0
- package/doc/hello_sapdon/hello_sapdon.md +3 -3
- package/doc/user/api/block.md +599 -14
- package/doc/user/api/item.md +284 -55
- package/doc/user/api/neo-guidebook.md +409 -0
- package/doc/user/api/sapdon-ui.md +185 -0
- package/doc/user/config/build-config.md +4 -5
- package/doc/user/quick-start.md +20 -6
- package/doc/user/tutorials/block.md +12 -1
- package/doc/user/tutorials/item.md +3 -3
- package/doc/user/tutorials/neo-guidebook-experience.md +381 -0
- package/doc/user/tutorials/neo-guidebook.md +640 -0
- package/doc/user/tutorials/sapdon-ui.md +207 -0
- package/package.json +5 -1
- package/prod/cli/index.js +1 -1
- package/prod/cli/start.js +1 -1
- package/prod/core/index.d.ts +1557 -299
- package/prod/core/index.js +1 -1
- package/prod/oc/index.d.ts +3 -1
- package/prod/oc/index.js +1 -1
- package/prod/utils/index.d.ts +0 -7
- package/prod/utils/index.js +1 -1
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# Sapdon UI 页面壳系统教程
|
|
2
|
+
|
|
3
|
+
本教程将带你创建一个使用 `sapdon_ui:` 前缀路由的自定义 Server Form UI:一个页面含「内容面板 + 按键面板」(按键盖在内容上层),左下/右下摆表单按钮,右上放退出键。完整可运行示例见 `examples/test_ui`。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 目录
|
|
8
|
+
|
|
9
|
+
1. [准备工作](#1-准备工作)
|
|
10
|
+
2. [创建构建入口](#2-创建构建入口)
|
|
11
|
+
3. [定义内容面板](#3-定义内容面板)
|
|
12
|
+
4. [定义按键面板](#4-定义按键面板)
|
|
13
|
+
5. [组装页面并注册](#5-组装页面并注册)
|
|
14
|
+
6. [编写运行时脚本](#6-编写运行时脚本)
|
|
15
|
+
7. [构建与部署](#7-构建与部署)
|
|
16
|
+
8. [完整示例](#8-完整示例)
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 1. 准备工作
|
|
21
|
+
|
|
22
|
+
初始化一个 TS 项目并安装依赖:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm init -y
|
|
26
|
+
npm install @minecraft/server@2.8.0 @minecraft/server-ui@2.1.0
|
|
27
|
+
# @sapdon/core 由 postinstall 的 `sapdon lib` 自动同步
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
项目结构:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
test_ui/
|
|
34
|
+
├── main.ts # 构建入口(声明式定义 UI)
|
|
35
|
+
├── scripts/
|
|
36
|
+
│ └── index.ts # 运行时入口(触发表单)
|
|
37
|
+
├── build.config # 构建配置
|
|
38
|
+
├── mod.info # 模块信息(含 min_engine_version)
|
|
39
|
+
├── tsconfig.json
|
|
40
|
+
├── package.json
|
|
41
|
+
├── pack_icon.png
|
|
42
|
+
└── res/ # 资源目录
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`build.config` 关键项(参考 `examples/test_ui/build.config`):
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"formatVersion": 2,
|
|
50
|
+
"buildOptions": {
|
|
51
|
+
"buildMode": "dev",
|
|
52
|
+
"buildEntry": "main.ts",
|
|
53
|
+
"scriptEntry": "scripts/index.ts",
|
|
54
|
+
"scriptOutput": "scripts/index.js",
|
|
55
|
+
"useJs": false,
|
|
56
|
+
"buildDir": "dev/",
|
|
57
|
+
"dependencies": [
|
|
58
|
+
{ "module_name": "@minecraft/server-ui", "version": "2.1.0" },
|
|
59
|
+
{ "module_name": "@minecraft/server", "version": "2.8.0" }
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 2. 创建构建入口
|
|
68
|
+
|
|
69
|
+
`main.ts` 是构建时脚本:声明 UI → 注册页面 → `registry.submit()` 生成所有 UI JSON。
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
import {
|
|
73
|
+
Label, Layout, Panel, SapdonButton, SapdonButtonPanel, SapdonPanel,
|
|
74
|
+
SapdonServerUI, StackPanel, Text, registry
|
|
75
|
+
} from '@sapdon/core'
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## 3. 定义内容面板
|
|
81
|
+
|
|
82
|
+
内容面板是页面的主体(背景 + 标题/正文),最后会被画在按键面板**下面**。
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
const apple_content_panel = new Panel("apple_content_panel")
|
|
86
|
+
.setLayout(new Layout().setSize(["40%", "40%"]))
|
|
87
|
+
.enableDebug();
|
|
88
|
+
apple_content_panel.addControl(
|
|
89
|
+
new StackPanel("main", undefined)
|
|
90
|
+
.addStack(["100%", "30%"],
|
|
91
|
+
new Label("title", undefined).enableDebug().setText(new Text().setText("苹果界面")))
|
|
92
|
+
.addStack(["100%", "70%"],
|
|
93
|
+
new Label("body", undefined).enableDebug().setText(new Text().setText("内容面板")))
|
|
94
|
+
);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
> 面板 id(`apple_content_panel`)稍后会作为注册引用 `sapdon_ui_apple.apple_content_panel`。
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## 4. 定义按键面板
|
|
102
|
+
|
|
103
|
+
按键面板画在内容面板**上层**。两个表单按钮用 `SapdonButtonPanel`(grid 2×1) 摆到左右下角;右上角放一个普通退出键(不占表单集合)。
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
import { Button } from '@sapdon/core'
|
|
107
|
+
|
|
108
|
+
const apple_buttons_panel = new Panel("apple_buttons_panel")
|
|
109
|
+
.setLayout(new Layout().setSize(["40%", "40%"]))
|
|
110
|
+
.addControl(
|
|
111
|
+
new SapdonButtonPanel("apple_buttons_grid")
|
|
112
|
+
.setDimensions([2, 1]) // 左右两份
|
|
113
|
+
.setCollection("form_buttons")
|
|
114
|
+
.setSize(["100%", "100%"])
|
|
115
|
+
.place([0, 0], new SapdonButton("bt0").setAnchor("bottom_left"))
|
|
116
|
+
.place([1, 0], new SapdonButton("bt1").setAnchor("bottom_right"))
|
|
117
|
+
.build()
|
|
118
|
+
)
|
|
119
|
+
.addControl(
|
|
120
|
+
new Button("exit", "common.button") // 右上:普通退出键
|
|
121
|
+
.addVariable("pressed_button_name", "button.menu_exit")
|
|
122
|
+
.setLayout(new Layout().setSize([48, 24]).setAnchorFrom("top_right").setAnchorTo("top_right"))
|
|
123
|
+
.addControl(new Label("exit_text", undefined).setText(new Text().setText("退出")))
|
|
124
|
+
);
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
要点:
|
|
128
|
+
- `grid_dimensions [2,1]` 把按键面板切成左右两半;格子内默认左上对齐,`setAnchor("bottom_left")` / `("bottom_right")` 把按钮落到两个下角。
|
|
129
|
+
- 表单按钮走 `SapdonButton`(自动引用框架模板 `server_form.form_button`,吃 `form_buttons` 集合数据)。
|
|
130
|
+
- 退出键用 `common.button` + `button.menu_exit`,点击关闭表单。
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## 5. 组装页面并注册
|
|
135
|
+
|
|
136
|
+
`SapdonPanel` 把内容/按键两块组装进该页面自己的 UI 文件;`SapdonServerUI.registerPage` 注册到路由。
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
new SapdonPanel("sapdon_ui_apple") // 生成 ui/sapdon_ui_apple.json
|
|
140
|
+
.setContent(apple_content_panel)
|
|
141
|
+
.setButtons(apple_buttons_panel)
|
|
142
|
+
.build();
|
|
143
|
+
|
|
144
|
+
SapdonServerUI.registerPage({
|
|
145
|
+
panelId: "sapdon_ui:apple", // title 精确匹配
|
|
146
|
+
name: "apple",
|
|
147
|
+
contentPanel: "sapdon_ui_apple.apple_content_panel",
|
|
148
|
+
buttonsPanel: "sapdon_ui_apple.apple_buttons_panel",
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
registry.submit() // 输出 server_form.json + 各页面 ui 文件 + _ui_defs.json
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
> 纯内容页(无按键):也需提供一个空的按键面板并注册,否则壳的 `$user_buttons_panel` 引用会报缺失。
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## 6. 编写运行时脚本
|
|
159
|
+
|
|
160
|
+
`scripts/index.ts`:使用物品触发带 `sapdon_ui:` 前缀 title 的 ActionForm。
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
import { world } from "@minecraft/server";
|
|
164
|
+
import { ActionFormData } from "@minecraft/server-ui";
|
|
165
|
+
|
|
166
|
+
world.afterEvents.itemUse.subscribe((event) => {
|
|
167
|
+
if (event.itemStack.typeId != "minecraft:apple") return;
|
|
168
|
+
new ActionFormData()
|
|
169
|
+
.title("sapdon_ui:apple") // 前缀路由 → 自定义页
|
|
170
|
+
.body("触发苹果页")
|
|
171
|
+
.button("test1") // 喂给集合 form_buttons
|
|
172
|
+
.button("test2")
|
|
173
|
+
.show(event.source)
|
|
174
|
+
.then((r) => world.sendMessage("selection: " + r.selection));
|
|
175
|
+
});
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
- title 含 `sapdon_ui:` → 显示自定义全屏 UI;否则走原版原生表单。
|
|
179
|
+
- 按钮点击返回 `response.selection`(对应集合下标)。
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## 7. 构建与部署
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
npm i # postinstall 自动执行 sapdon lib,同步 @sapdon/core
|
|
187
|
+
npm run build # 构建并复制到开发包目录
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
构建产物(`dev/test_ui_RP/ui/`):
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
server_form.json # 路由壳(custom_full_screen / sapdon_screen_content / custom_panel_content / form_button)
|
|
194
|
+
sapdon_ui_apple.json # 页面:内容面板 + 按键面板
|
|
195
|
+
_ui_defs.json # 自动登记
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
进入游戏用苹果触发,验证:
|
|
199
|
+
1. 内容面板在底层、按键面板覆盖在上层。
|
|
200
|
+
2. 左下/右下各一个表单按钮(数据对应 test1/test2)。
|
|
201
|
+
3. 右上「退出」点击关闭表单。
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## 8. 完整示例
|
|
206
|
+
|
|
207
|
+
完整可运行代码见仓库 `examples/test_ui`(含页面 A 内容+按键、页面 B 纯内容两个页面)。API 细节见《Sapdon UI 页面壳系统 API 参考》(`doc/user/api/sapdon-ui.md`)。
|
package/package.json
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sapdon",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.0",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"build": "node scripts/build.cjs",
|
|
6
|
+
"test": "tsc && tsc-alias && node --test \"tests/*.test.mjs\"",
|
|
7
|
+
"build:demo": "cd examples/block_demo && npm run build",
|
|
6
8
|
"pub": "npm run build && npm publish"
|
|
7
9
|
},
|
|
8
10
|
"keywords": [
|
|
@@ -20,6 +22,7 @@
|
|
|
20
22
|
"devDependencies": {
|
|
21
23
|
"@minecraft/server": "^2.0.0-beta.1.21.80-preview.20",
|
|
22
24
|
"@minecraft/server-ui": "^1.3.0",
|
|
25
|
+
"@types/archiver": "^6.0.0",
|
|
23
26
|
"@types/d3-array": "^3.2.1",
|
|
24
27
|
"@types/lodash": "^4.17.16",
|
|
25
28
|
"@types/node": "^22.14.0",
|
|
@@ -34,6 +37,7 @@
|
|
|
34
37
|
"chalk": "^5.4.1",
|
|
35
38
|
"commander": "^13.0.0",
|
|
36
39
|
"d3-array": "^3.2.4",
|
|
40
|
+
"archiver": "^7.0.0",
|
|
37
41
|
"download-git-repo": "^3.0.2",
|
|
38
42
|
"inquirer": "^12.3.1",
|
|
39
43
|
"lodash": "^4.17.21",
|
package/prod/cli/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import e from"http";Symbol.metadata||(Symbol.metadata=Symbol("[[metadata]]"));const
|
|
1
|
+
import e from"http";Symbol.metadata||(Symbol.metadata=Symbol("[[metadata]]"));const r=Symbol("isRawJSON");const t=["boolean","number"],n=["string","undefined"];function o(e,o){const s=typeof o;if(null===s)return null;if(n.includes(s))return o;if(t.includes(s))return JSON.rawJSON(o);if("object"===s)return JSON.isRawJSON(o)?o:function(e){return!0===e?.[r]}(o)?JSON.rawJSON(o.rawJSON):o;if("bigint"===s)return JSON.rawJSON(o.toString());throw new Error("Unexpected value")}const s={encode:e=>JSON.stringify(e,o),decode:JSON.parse};function i(e,r=s){return r.encode(e)}const a={port:49037},{port:c}=a;const{port:l}=a;const d=new class{cliServerHandlers=new Map;listening=!1;isListening(){return this.listening}bootstrap(){this.listening=!0;const r=e.createServer(async(e,r)=>{const t=this.cliServerHandlers.get((e.url??"/").slice(1));if(t){try{const{promise:r,resolve:n,reject:o}=Promise.withResolvers();let i=Buffer.alloc(0);e.on("data",e=>i=Buffer.concat([i,e])),e.on("end",()=>{try{n(function(e,r=s){return r.decode(e)}(i))}catch(e){o(e)}}),await t(...await r)}catch(e){return console.error(e),r.writeHead(500),void r.end()}r.writeHead(200),r.end()}else r.writeHead(404),r.end()}).listen(l,()=>console.log(`Dev Server listening on port ${l}`));return r.on("error",e=>{throw this.listening=!1,function(e){return"object"==typeof e&&null!==e&&"EADDRINUSE"===e.code}(e)&&(console.error(`[sapdon] Dev Server 端口 ${l} 已被其他 sapdon 进程占用。`),console.error("[sapdon] 请先结束残留的 sapdon 进程,再重新构建,否则本次构建的数据可能被写入错误的包目录。"),process.exit(1)),e}),r}handle(e,r){this.cliServerHandlers.set(e,r)}getHandler(e){return this.cliServerHandlers.get(e)}interceptHandler(e,r){const t=r(this.getHandler(e)??Function.prototype);return this.cliServerHandlers.set(e,t),t}},u={call:async(e,...r)=>await async function(e,...r){try{await fetch(`http://localhost:${c}/${e}`,{method:"POST",headers:{"Content-Type":"application/json"},body:i(r)})}catch(e){console.error(e),console.error("尝试在构建脚本中使用 server.startDevServer() 启动开发服务器")}}(e,...r)};export{u as client,d as devServer};
|
package/prod/cli/start.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{program as e}from"commander";import n from"inquirer";import t from"path";import{fileURLToPath as o}from"url";import i from"fs";import{randomUUID as r}from"crypto";import{fileURLToPath as s}from"node:url";import c from"child_process";import a from"fs/promises";import l from"http";import{rollup as u}from"rollup";import p from"@rollup/plugin-commonjs";import{nodeResolve as d}from"@rollup/plugin-node-resolve";import m from"@rollup/plugin-typescript";import{typescriptPaths as f}from"rollup-plugin-typescript-paths";import g from"@rollup/plugin-json";import"rollup-plugin-visualizer";import y from"@rollup/plugin-terser";import h from"os";import j from"lodash";const b=()=>r();function S(e,n){try{i.copyFileSync(e,n)}catch(e){console.error("文件复制失败:",e)}}const w=e=>!i.existsSync(e),_=e=>{try{return i.readFileSync(e,"utf8")}catch(e){console.log(e)}return null},v=(e,n)=>{i.mkdirSync(t.dirname(e),{recursive:!0}),i.writeFileSync(e,n)},O=(e,n)=>{if(i.mkdirSync(t.dirname(n),{recursive:!0}),!i.existsSync(e))return void console.log(`Source path ${e} does not exist.`);i.existsSync(n)||i.mkdirSync(n);i.readdirSync(e).forEach(o=>{const r=t.join(e,o),s=t.join(n,o);i.lstatSync(r).isDirectory()?O(r,s):i.copyFileSync(r,s)})};function k(e){const n=s(e.url);return t.dirname(n)}const x=new Map;function N(e,n){let t=x.get(e);return t||(t=n(e),x.set(e,t),t)}function J(){const e=M(),n=t.join(e,"build.config");if(!i.existsSync(n))throw new Error("未找到项目配置文件,请先初始化项目");return N(n,e=>function(e){const n=(t=i.readFileSync(e),JSON.parse(String(t).replace(/\/\/.*|\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/g,"$1")));var t;if(2===n.formatVersion)return n;const o=function({defaultConfig:e,resources:n,scripts:t}){const o={formatVersion:2,buildOptions:{useHMR:!0,buildMode:"development",buildEntry:e.buildEntry,scriptEntry:e.scriptEntry,scriptOutput:e.scriptEntry.replace(".ts",".js"),useJs:"ts"!==t[0].type,buildDir:e.buildDir,dependencies:e.dependencies,resource:{path:n[0].path,resourceHints:!0}},versionType:"release"};return o}(n);return i.writeFileSync(e,JSON.stringify(o,null,2)),o}(e))}const F=o(import.meta.url),$=t.dirname(F),R={js:"js_sapdon",ts:"ts_sapdon"},P=e=>{const n=t.join(e,"package.json");if(!i.existsSync(n))return null;try{const o=JSON.parse(i.readFileSync(n,"utf-8"));return{name:t.basename(e),description:o.description||"A new sapdon project",author:o.author||"Sapdon",version:o.version||"1.0.0"}}catch(e){return console.error("读取package.json文件时出错:",e),null}},D={};function M(){const e=D.projectPath??process.cwd();if(!i.existsSync(t.join(e,"build.config")))throw new Error("无效的项目路径");return e}function E(){const{buildDir:e}=J().buildOptions,n=N("projectName",()=>t.basename(M()));return t.join(M(),e,n+"_bp")}const B=async(e,n,o="")=>{const i={},r=await a.readdir(e);for(const s of r){const r=t.join(e,s);if((await a.stat(r)).isDirectory()){const e=await B(r,n,o);Object.assign(i,e)}else if(s.endsWith(".png")){const e=t.basename(s,".png"),c=`${o}${t.relative(n,r).replace(/\.png$/,"").replace(/\\/g,"/")}`;i[e]={textures:c}}}return i},L=async(e,n,t)=>{try{const o=JSON.stringify(n,null,2);await a.writeFile(e,o),console.log(t)}catch(e){console.error("Error writing JSON file:",e)}};Symbol.metadata||(Symbol.metadata=Symbol("[[metadata]]"));const H=Symbol("isRawJSON");const A=["boolean","number"],I=["string","undefined"];function T(e,n){const t=typeof n;if(null===t)return null;if(I.includes(t))return n;if(A.includes(t))return JSON.rawJSON(n);if("object"===t)return JSON.isRawJSON(n)?n:function(e){return!0===e?.[H]}(n)?JSON.rawJSON(n.rawJSON):n;if("bigint"===t)return JSON.rawJSON(n.toString());throw new Error("Unexpected value")}const W={encode:e=>JSON.stringify(e,T),decode:JSON.parse};const{port:C}={port:49037};const U=new class{cliServerHandlers=new Map;listening=!1;isListening(){return this.listening}bootstrap(){this.listening=!0;const e=l.createServer(async(e,n)=>{const t=this.cliServerHandlers.get((e.url??"/").slice(1));if(t){try{const{promise:n,resolve:o,reject:i}=Promise.withResolvers();let r=Buffer.alloc(0);e.on("data",e=>r=Buffer.concat([r,e])),e.on("end",()=>{try{o(function(e,n=W){return n.decode(e)}(r))}catch(e){i(e)}}),await t(...await n)}catch(e){return console.error(e),n.writeHead(500),void n.end()}n.writeHead(200),n.end()}else n.writeHead(404),n.end()}).listen(C,()=>console.log(`Dev Server listening on port ${C}`));return e.on("error",()=>this.listening=!1),e}handle(e,n){this.cliServerHandlers.set(e,n)}getHandler(e){return this.cliServerHandlers.get(e)}interceptHandler(e,n){const t=n(this.getHandler(e)??Function.prototype);return this.cliServerHandlers.set(e,t),t}};async function V({level:e,message:n,timeStamp:t,stack:o}){console[e](n,o,`\nat ${new Date(t).toLocaleString()}`)}class G{static dataList=[];static getDataList(){return[...this.dataList]}static startServer(){U.handle("submitGregistry",e=>{this.dataList=e}),U.handle("remote-logger",V)}}const q=async(e,n,o)=>{try{t.join(n,`${o}_BP`);const i=t.join(n,`${o}_RP`),r=(...e)=>t.join(i,...e),s=G.getDataList(),c={item:null,block:null,flipbook:[]};for(const{name:e,root:i,path:r,data:a}of s){switch(console.log("处理数据:",e,i,r),e){case"item_texture":c.item=a,console.log("用户物品贴图数据:",c.item);continue;case"terrain_texture":c.block=a,console.log("用户方块贴图数据:",c.block);continue;case"flipbook_textures":c.flipbook=a,console.log("用户翻书贴图数据:",c.flipbook);continue}const s="behavior"===i?`${o}_BP`:`${o}_RP`,l=t.join(n,s),u=t.join(l,r,`${e}.json`);v(u,JSON.stringify(a,null,2))}await async function(e,n,t){const o=e("textures/items"),i=e("textures/item_texture.json");await async function(e,n,t,o){try{const i=await B(e,e,"textures/items/");o&&Object.assign(i,o);const r={resource_pack_name:t,texture_name:"atlas.items",texture_data:i};await L(n,r,"Item texture JSON file generated successfully.")}catch(e){console.error("Error generating item texture JSON:",e)}}(o,i,n,t.item);const r=e("textures/blocks"),s=e("textures/terrain_texture.json");await async function(e,n,t,o){try{const i=await B(e,e,"textures/blocks/");o&&Object.assign(i,o);const r={texture_name:"atlas.terrain",resource_pack_name:t,padding:8,num_mip_levels:4,texture_data:i};await L(n,r,"Block texture JSON file generated successfully.")}catch(e){console.error("Error generating block texture JSON:",e)}}(r,s,n,t.block);const c=e("textures/flipbook_textures.json");v(c,JSON.stringify(t.flipbook,null,2))}(r,o,c),console.log(`已加载并执行 ${e} 文件!`)}catch(n){console.error(`加载或执行 ${e} 失败:${n.message}`),console.error(n.stack)}};class z{name;description;version;uuid;allow_random_seed;lock_template_options;pack_scope;base_game_version;min_engine_version;constructor(e,n,t,o,i={}){this.name=e,this.description=n,this.version=t,this.uuid=o,this.allow_random_seed=i.allow_random_seed,this.lock_template_options=i.lock_template_options,this.pack_scope=i.pack_scope,this.base_game_version=i.base_game_version,this.min_engine_version=i.min_engine_version}}class K{description;type;uuid;version;constructor(e,n,t,o){this.description=e,this.type=n,this.uuid=t,this.version=o}}class Q{authors;license;generated_with;product_type;url;constructor(e,n,t,o,i){this.authors=e,this.license=n,this.generated_with=t,this.product_type=o,this.url=i}}class X{format_version;header;modules;dependencies;capabilities;metadata;constructor(e,n,t,o,i=null,r=null){this.format_version=e,this.header=n,this.modules=t,this.dependencies=o,null!=i&&(this.capabilities=i),null!=r&&(this.metadata=r)}}const Y=process.env.MC_PATH,Z=process.env.MC_BETA_PATH,ee="AppData/Roaming/Minecraft Bedrock/Users/Shared/games/com.mojang",ne="AppData/Local/Packages/Microsoft.MinecraftWindowsBeta_8wekyb3d8bbwe/LocalState/games/com.mojang/";function te(){return"beta"===J().versionType?N("McInstallPath.Beta",()=>Z||t.join(h.homedir(),ne)):N("McInstallPath.Main",()=>Y||t.join(h.homedir(),ee))}var oe={name:"sapdon",version:"3.3.3",scripts:{build:"node scripts/build.cjs",pub:"npm run build && npm publish"},keywords:["bedrock","tools","addon"],author:"Meteage",license:"ISC",type:"module",bin:{sapdon:"prod/cli/start.js"},description:"Sapdon is a Node.js toolkit designed for building Bedrock Edition Minecraft addon packs.",devDependencies:{"@minecraft/server":"^2.0.0-beta.1.21.80-preview.20","@minecraft/server-ui":"^1.3.0","@types/d3-array":"^3.2.1","@types/lodash":"^4.17.16","@types/node":"^22.14.0","tsc-alias":"^1.8.13"},dependencies:{"@rollup/plugin-commonjs":"^28.0.2","@rollup/plugin-json":"^6.1.0","@rollup/plugin-node-resolve":"^16.0.0","@rollup/plugin-terser":"^0.4.4","@rollup/plugin-typescript":"^12.1.2",chalk:"^5.4.1",commander:"^13.0.0","d3-array":"^3.2.4","download-git-repo":"^3.0.2",inquirer:"^12.3.1",lodash:"^4.17.21",ora:"^8.1.1",rollup:"^4.39.0","rollup-plugin-dts":"^6.2.1","rollup-plugin-typescript-paths":"^1.5.0","rollup-plugin-visualizer":"^5.14.0","tsconfig-paths":"^4.2.0",tslib:"^2.8.1",typescript:"^5.8.2"},files:["prod/**/*","doc/**/*"]};function ie(){return N(t.join(k(import.meta),"../../../package.json"),()=>oe)}async function re(e,n){const o=J(),r=t.join(e,o.buildOptions.buildDir),s=t.join(r,`${n}_BP`),c=t.join(r,`${n}_RP`);i.cpSync(s,t.join(te(),"development_behavior_packs/",`${n}_BP/`),{recursive:!0,force:!0}),i.cpSync(c,t.join(te(),"development_resource_packs/",`${n}_RP/`),{recursive:!0,force:!0})}async function se(e){const n=t.join(e,"node_modules"),o=t.join(k(import.meta),"../"),r=t.join(o,"core"),s=t.join(o,"cli"),c=t.join(o,"oc"),a=t.join(n,"@sapdon/core"),l=t.join(n,"@sapdon/cli"),u=t.join(n,"@sapdon/runtime"),p=ie();i.cpSync(r,a,{recursive:!0,force:!0}),i.cpSync(s,l,{recursive:!0,force:!0}),i.cpSync(c,u,{recursive:!0,force:!0}),i.writeFileSync(t.join(a,"package.json"),JSON.stringify({name:"@sapdon/core",type:"module",main:"index.js",version:p.version})),i.writeFileSync(t.join(l,"package.json"),JSON.stringify({name:"@sapdon/cli",type:"module",main:"index.js",version:p.version})),i.writeFileSync(t.join(u,"package.json"),JSON.stringify({name:"@sapdon/runtime",type:"module",main:"index.js",version:p.version}))}const ce=o(import.meta.url);t.dirname(ce);const ae=["rollup","typescript","@sapdon/core","@sapdon/cli","@minecraft"],le={js:async(e,n,t)=>{const o=J();try{const i=await u({input:e,plugins:[d({preferBuiltins:!0}),p(),g(),..."development"===o.buildOptions.buildMode?[]:[y()]],external(e){for(const n of ae)if(e.includes(n))return!0;return!1}});await i.write({file:n,format:"esm",sourcemap:t}),i.close()}catch(e){console.error(e)}},ts:async(e,n,o)=>{const i=M(),r=J(),s={file:n,format:"esm",sourcemap:o};try{const n=await u({input:e,plugins:[f(),d({preferBuiltins:!0}),m({tsconfig:t.join(i,"tsconfig.json"),compilerOptions:{outDir:t.dirname(s.file)}}),p(),g(),..."development"===r.buildOptions.buildMode?[]:[y()]],external(e){for(const n of ae)if(e.includes(n))return!0;return!1}});await n.write(s),n.close()}catch(e){console.error(e)}},any:async(e,n)=>{J().buildOptions.useJs?await le.js(e,n):await le.ts(e,n)}};async function ue(e){const n="."+crypto.randomUUID()+".js",o=t.join(t.dirname(e),".tmp",n);await le.any(e,o);try{await async function(e){const{promise:n,resolve:t}=Promise.withResolvers();return c.fork(e,{stdio:"inherit"}).on("exit",t),n}(o)}catch(e){console.error(e)}finally{i.rmSync(o,{force:!0})}}function pe(e){return console.log("开始构建项目"),console.log("项目路径:"+e),w(e)?(console.log("项目不存在"),!1):!w(t.join(e,"build.config"))||(console.log("项目没有build.config文件"),!1)}const de=async(e,n)=>{const o=J(),i=t.join(e,"mod.info"),r=JSON.parse(_(i)),s=me(r.min_engine_version),c=t.join(e,o.buildOptions.buildDir),a=t.join(c,`${n}_BP/`),l=t.join(c,`${n}_RP/`),u=t.join(e,o.buildOptions.buildEntry);if(!U.isListening()){const i=t.join(a,"manifest.json");if(w(i)){const e=fe(r.name,r.description,r.version,{min_engine_version:s},o.buildOptions.dependencies,o.buildOptions.scriptOutput),n=ge(r.name,r.description,r.version,{min_engine_version:s},[]);v(t.join(a,"manifest.json"),e),v(t.join(l,"manifest.json"),n)}const p=t.join(e,"pack_icon.png");S(p,t.join(a,"pack_icon.png")),S(p,t.join(l,"pack_icon.png"));const d=o.buildOptions.resource,m=t.join(e,d.path);O(m,l),U.isListening()||U.bootstrap(),G.startServer(),U.handle("submit",async e=>{"development"===o.buildOptions.buildMode&&(G.dataList=e,await q(u,c,n))})}await ue(u),await async function(e=!1){const n=e?"js":"ts",o=M(),i=J(),{scriptEntry:r,scriptOutput:s,buildMode:c}=i.buildOptions;le[n](t.join(o,r),t.join(E(),s),"development"===c)}(o.buildOptions.useJs),await re(e,n)};function me(e){return e.split(".").map(e=>Number(e))}const fe=(e,n,t,o={},i=[],r)=>{console.log("开始生成behavior_packs/manifest.json"),console.log("Entry:",r);const s=new z(e+"_BP",n,me(t),b(),o),c=new K("行为模块","data",b(),me(t)),a=new K("脚本模块","script",b(),me(t));r&&(a.entry=r);const l=new Q(["@sapdon"],"MIT",{sapdon:["1.0.0"]},"addon","https://github.com/junjun260/sapdon"),u=new X(2,s,[c,a],i,null,l);return JSON.stringify(u,null,2)},ge=(e,n,t,o={},i=[])=>{const r=new z(e+"_RP",n,me(t),b(),o),s=new K("资源模块","resources",b(),me(t)),c=new Q(["@sapdon"],"MIT",{sapdon:["1.0.0"]},"addon","https://github.com/junjun260/sapdon"),a=new X(2,r,[s],[],null,c);return JSON.stringify(a,null,2)},ye="import fs from 'fs'\nexport class FileResource {\n static cache: Record<string, FileResource> = {}\n static fileSystemLoader = (uri: string) => fs.readFileSync(uri)\n\n private _res: any\n\n /**\n * 不要调用构造器!\n * 使用 FileResource.get(uri) 方法获取实例\n * @param origin \n */\n constructor(public origin: string) {\n this.origin = origin\n FileResource.cache[origin] = this\n }\n\n /**\n * @param uri \n * @returns {FileResource}\n */\n static get(uri: string) {\n if (uri in FileResource.cache) {\n return FileResource.cache[uri]\n }\n return new FileResource(uri)\n }\n\n load(loader=FileResource.fileSystemLoader) {\n if (this._res) {\n return this._res\n }\n const content = loader(this.origin)\n this._res = content\n return content\n }\n\n clear() {\n delete FileResource.cache[this.origin]\n }\n\n loadWithoutCache(loader=FileResource.fileSystemLoader) {\n return loader(this.origin)\n }\n\n ptr() {\n const func = () => this.load()\n return Object.assign(func, this)\n }\n}",he=j.debounce;function je(e,n,o){i.readdirSync(e).forEach(r=>{const s=t.join(e,r);i.statSync(s).isDirectory()?(o(r,e),je(s,n,o)):n(r,e)})}var be;function Se(e){return e.replaceAll(".","_").replaceAll("-","_").replaceAll("@","$")}function we(e){const n={type:be.Dir,name:e,children:{}},o={[e]:n};let i=n;return je(e,(e,n)=>{const o=t.basename(e),r=Se(o.slice(0,o.lastIndexOf("."))),s={type:be.File,name:r,origin:t.join(n,e)};i.children[r]=s},(e,n)=>{const r=Se(e),s=o[n],c={type:be.Dir,name:r,children:{}};s.children[r]=c,o[t.join(n,e)]=c,i=c}),n}function _e(e){return!!i.existsSync(t.join(e,"build.config"))||(console.log("无法生成资源目录,请 cd 到项目根目录下执行 sapdon res"),!1)}function ve(){const{buildOptions:e}=J();if(e.buildEntry.endsWith("js"))return;const n=process.cwd(),o=t.join(n,"res");if(!_e(n))return;i.existsSync(o)||i.mkdirSync(o);!function(e,n){const t={};!function e(n,t){for(const o in n.children){const i=n.children[o];if(i.type===be.File)t[i.name]=`<fn>${i.origin}</fn>`;else{const n={};t[i.name]=n,e(i,n)}}}(e,t);const o=[ye].join(";\n"),r=`\n;export default ${JSON.stringify(t,null,2)}`.replace(/"\<fn\>(.*)\<\/fn\>"/g,"FileResource.get('$1').ptr()");i.writeFileSync(n,o+r)}(we(o),t.join(n,"res.hint.ts"))}!function(e){e[e.File=0]="File",e[e.Dir=1]="Dir"}(be||(be={}));const Oe=j.debounce;function ke(e,n){const{buildDir:o,useHMR:r}=J().buildOptions;!function(){const e=process.cwd();_e(e)&&i.watch(t.join(e,"res"),{recursive:!0},he((e,n)=>{c.execSync("sapdon res")},3e3))}(),r&&i.watch(e,{recursive:!0},Oe(async(i,r)=>{i&&r&&(r.startsWith(".")||r.startsWith(t.join(o,"./"))||r.includes(".tmp")||(r.endsWith(".js")||r.endsWith(".ts")||"build.config"===r||"mod.info"===r)&&(process.stdout.write(`File ${r} changed, reloading...\r`),await de(e,n),await re(e,n),console.log(`Reloaded ${r}`)))}),1e3)}const xe=o(import.meta.url),Ne=t.dirname(xe);process.removeAllListeners("warning"),e.command("init").description("初始化一个基于NodeJS的项目").action(()=>{const e=process.cwd(),o=t.join(e,"package.json");let r;if(i.existsSync(o)){try{r=JSON.parse(i.readFileSync(o,"utf-8"))}catch(e){return console.error("读取package.json文件时出错:",e),void console.log("请使改用create命令进行创建。")}r.scripts={...r.scripts,init:"sapdon init",pack:"sapdon pack",config:"sapdon config"};try{i.writeFileSync(o,JSON.stringify(r,null,2),"utf-8")}catch(e){return void console.error("写入package.json文件时出错:",e)}n.prompt([{type:"input",name:"min_engine_version",message:"最低引擎版本:",default:"1.19.50"}]).then(n=>{((e,n)=>{if(!(w(t.join(e,"mod.info"))&&w(t.join(e,"main.mjs"))&&w(t.join(e,"scripts"))&&w(t.join(e,"build.config"))))return void console.log("项目已存在...");const o=t.join($,"../templates/js_sapdon");O(o,e),v(t.join(e,"mod.info"),JSON.stringify(n,null,2))})(e,{...P(e),...n}),console.log("请使用命令sapdon config配置框架的build.config文件。")})}else console.error("没有找到package.json文件,请使改用create命令进行创建。")}),e.command("create <project-name>").description("Create a new project").action(e=>{n.prompt([{type:"input",name:"name",message:"Project Name:",default:t.basename(e)},{type:"input",name:"description",message:"Project Description:",default:"A new sapdon project"},{type:"input",name:"author",message:"Author Name:",default:"Sapdon"},{type:"input",name:"version",message:"Project Version:",default:"1.0.0"},{type:"input",name:"min_engine_version",message:"Minimum Engine Version:",default:"1.19.50"},{type:"input",name:"language",message:"Language:(js/ts)",default:"ts"}]).then(n=>{const o=t.join(process.cwd(),e);console.log("项目路径:",o),((e,n)=>{if(console.log(1,n),!w(e))return void console.log("项目名称已存在,创建项目目录失败");const o=t.join($,`../../src/templates/${R[n.language||"js"]}`);O(o,e),v(t.join(e,"mod.info"),JSON.stringify(n,null,2)),c.execSync("npm i",{cwd:e,stdio:"inherit"})})(o,n),se(o)})}),e.command("build <project-name>").description("Build the project").action(e=>{console.log("Building the project...");const n=t.join(process.cwd(),e),o=t.basename(n);D.projectPath=n,ve(),pe(n)&&(de(n,o),ke(n,o))}),e.command("pack").description("Pack the current project").action(()=>{console.log("Packing the current project...");const e=process.cwd();pe(e)&&de(e,t.basename(e))}),e.command("lib").description("Generate lib files for development server.").action(()=>{se(process.cwd())}),e.command("res").description("Generate resource hints.").action(()=>{ve()}),e.command("config").description("Configure build.config file").action(()=>{const e=t.join(Ne,"./build.config");let n;try{n=JSON.parse(_(e))}catch(e){return void console.error("读取build.config文件时出错:",e)}}),e.parse(e.argv);
|
|
2
|
+
import{program as e}from"commander";import n from"inquirer";import o from"path";import{fileURLToPath as t}from"url";import i from"fs";import{randomUUID as r}from"crypto";import{fileURLToPath as s}from"node:url";import c from"child_process";import a from"fs/promises";import l from"http";import{rollup as u}from"rollup";import p from"@rollup/plugin-commonjs";import{nodeResolve as d}from"@rollup/plugin-node-resolve";import m from"@rollup/plugin-typescript";import{typescriptPaths as f}from"rollup-plugin-typescript-paths";import g from"@rollup/plugin-json";import"rollup-plugin-visualizer";import y from"@rollup/plugin-terser";import h from"os";import j from"lodash";import b from"archiver";const S=()=>r();function v(e,n){try{i.copyFileSync(e,n)}catch(e){console.error("文件复制失败:",e)}}const w=e=>!i.existsSync(e),_=e=>{try{return i.readFileSync(e,"utf8")}catch(e){console.log(e)}return null},O=(e,n)=>{i.mkdirSync(o.dirname(e),{recursive:!0}),i.writeFileSync(e,n)},k=(e,n)=>{if(i.mkdirSync(o.dirname(n),{recursive:!0}),!i.existsSync(e))return void console.log(`Source path ${e} does not exist.`);i.existsSync(n)||i.mkdirSync(n);i.readdirSync(e).forEach(t=>{const r=o.join(e,t),s=o.join(n,t);i.lstatSync(r).isDirectory()?k(r,s):i.copyFileSync(r,s)})};function x(e){const n=s(e.url);return o.dirname(n)}const N=new Map;function $(e,n){let o=N.get(e);return o||(o=n(e),N.set(e,o),o)}function J(){const e=E(),n=o.join(e,"build.config");if(!i.existsSync(n))throw new Error("未找到项目配置文件,请先初始化项目");return $(n,e=>function(e){const n=(o=i.readFileSync(e),JSON.parse(String(o).replace(/\/\/.*|\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/g,"$1")));var o;if(2===n.formatVersion)return n;const t=function({defaultConfig:e,resources:n,scripts:o}){const t={formatVersion:2,buildOptions:{useHMR:!0,buildMode:"dev",buildEntry:e.buildEntry,scriptEntry:e.scriptEntry,scriptOutput:e.scriptEntry.replace(".ts",".js"),useJs:"ts"!==o[0].type,buildDir:e.buildDir,dependencies:e.dependencies,resource:{path:n[0].path,resourceHints:!0}},versionType:"release"};return t}(n);return i.writeFileSync(e,JSON.stringify(t,null,2)),t}(e))}const F=t(import.meta.url),R=o.dirname(F),P={js:"js_sapdon",ts:"ts_sapdon"},D=e=>{const n=o.join(e,"package.json");if(!i.existsSync(n))return null;try{const t=JSON.parse(i.readFileSync(n,"utf-8"));return{name:o.basename(e),description:t.description||"A new sapdon project",author:t.author||"Sapdon",version:t.version||"1.0.0"}}catch(e){return console.error("读取package.json文件时出错:",e),null}},M={};function E(){const e=M.projectPath??process.cwd();if(!i.existsSync(o.join(e,"build.config")))throw new Error("无效的项目路径");return e}function B(){const{buildDir:e}=J().buildOptions,n=$("projectName",()=>o.basename(E()));return o.join(E(),e,n+"_bp")}const H=async(e,n,t="")=>{const i={};try{await a.access(e)}catch{return i}const r=await a.readdir(e);for(const s of r){const r=o.join(e,s);if((await a.stat(r)).isDirectory()){const e=await H(r,n,t);Object.assign(i,e)}else if(s.endsWith(".png")){const e=o.basename(s,".png"),c=`${t}${o.relative(n,r).replace(/\.png$/,"").replace(/\\/g,"/")}`;i[e]={textures:c}}}return i},L=async(e,n,o)=>{try{const t=JSON.stringify(n,null,2);await a.writeFile(e,t),console.log(o)}catch(e){console.error("Error writing JSON file:",e)}};Symbol.metadata||(Symbol.metadata=Symbol("[[metadata]]"));const A=Symbol("isRawJSON");const I=["boolean","number"],C=["string","undefined"];function W(e,n){const o=typeof n;if(null===o)return null;if(C.includes(o))return n;if(I.includes(o))return JSON.rawJSON(n);if("object"===o)return JSON.isRawJSON(n)?n:function(e){return!0===e?.[A]}(n)?JSON.rawJSON(n.rawJSON):n;if("bigint"===o)return JSON.rawJSON(n.toString());throw new Error("Unexpected value")}const T={encode:e=>JSON.stringify(e,W),decode:JSON.parse};const{port:U}={port:49037};const z=new class{cliServerHandlers=new Map;listening=!1;isListening(){return this.listening}bootstrap(){this.listening=!0;const e=l.createServer(async(e,n)=>{const o=this.cliServerHandlers.get((e.url??"/").slice(1));if(o){try{const{promise:n,resolve:t,reject:i}=Promise.withResolvers();let r=Buffer.alloc(0);e.on("data",e=>r=Buffer.concat([r,e])),e.on("end",()=>{try{t(function(e,n=T){return n.decode(e)}(r))}catch(e){i(e)}}),await o(...await n)}catch(e){return console.error(e),n.writeHead(500),void n.end()}n.writeHead(200),n.end()}else n.writeHead(404),n.end()}).listen(U,()=>console.log(`Dev Server listening on port ${U}`));return e.on("error",e=>{throw this.listening=!1,function(e){return"object"==typeof e&&null!==e&&"EADDRINUSE"===e.code}(e)&&(console.error(`[sapdon] Dev Server 端口 ${U} 已被其他 sapdon 进程占用。`),console.error("[sapdon] 请先结束残留的 sapdon 进程,再重新构建,否则本次构建的数据可能被写入错误的包目录。"),process.exit(1)),e}),e}handle(e,n){this.cliServerHandlers.set(e,n)}getHandler(e){return this.cliServerHandlers.get(e)}interceptHandler(e,n){const o=n(this.getHandler(e)??Function.prototype);return this.cliServerHandlers.set(e,o),o}};async function V({level:e,message:n,timeStamp:o,stack:t}){console[e](n,t,`\nat ${new Date(o).toLocaleString()}`)}class G{static dataList=[];static getDataList(){return[...this.dataList]}static startServer(){z.handle("submitGregistry",e=>{this.dataList=e}),z.handle("remote-logger",V)}}const q=async(e,n,t)=>{try{o.join(n,`${t}_BP`);const r=o.join(n,`${t}_RP`),s=(...e)=>o.join(r,...e),c=G.getDataList(),a={item:null,block:null,flipbook:[]},l=[];for(const{name:r,root:s,path:u,data:p}of c){switch(console.log("处理数据:",r,s,u),r){case"item_texture":a.item=p,console.log("用户物品贴图数据:",a.item);continue;case"terrain_texture":a.block=p,console.log("用户方块贴图数据:",a.block);continue;case"flipbook_textures":a.flipbook=p,console.log("用户翻书贴图数据:",a.flipbook);continue}if(p._scriptSource){const n=o.dirname(e),t=o.join(n,u,`${r}.js`);i.existsSync(t)?console.log(`自定义组件脚本已存在,跳过: ${t}`):(O(t,p.source),console.log(`已生成自定义组件脚本: ${t}`)),l.push({safeName:r,componentId:p.componentId});continue}const c="behavior"===s?`${t}_BP`:`${t}_RP`,d=o.join(n,c),m=o.join(d,u,`${r}.json`);O(m,JSON.stringify(p,null,2))}if(l.length>0){const n=o.dirname(e),t=o.join(n,"scripts","custom_components"),r=o.join(t,"index.js");if(i.existsSync(r))console.log(`自定义组件注册索引已存在,跳过: ${r}`);else{const e=l.map(({safeName:e})=>`import { ${e} } from './${e}.js';`),n=l.map(({safeName:e,componentId:n})=>` init.blockComponentRegistry.registerCustomComponent('${n}', ${e});`),o=["// Auto-generated by sapdon.","import { system } from '@minecraft/server';","",...e,"","system.beforeEvents.startup.subscribe((init) => {",...n,"});",""].join("\n");O(r,o),console.log(`已生成自定义组件注册索引: ${r}`)}}await async function(e,n,o){const t=e("textures/items"),i=e("textures/item_texture.json");await async function(e,n,o,t){try{const i=await H(e,e,"textures/items/");t&&Object.assign(i,t);const r={resource_pack_name:o,texture_name:"atlas.items",texture_data:i};await L(n,r,"Item texture JSON file generated successfully.")}catch(e){console.error("Error generating item texture JSON:",e)}}(t,i,n,o.item);const r=e("textures/blocks"),s=e("textures/terrain_texture.json");await async function(e,n,o,t){try{const i=await H(e,e,"textures/blocks/");t&&Object.assign(i,t);const r={texture_name:"atlas.terrain",resource_pack_name:o,padding:8,num_mip_levels:4,texture_data:i};await L(n,r,"Block texture JSON file generated successfully.")}catch(e){console.error("Error generating block texture JSON:",e)}}(r,s,n,o.block);const c=e("textures/flipbook_textures.json");O(c,JSON.stringify(o.flipbook,null,2))}(s,t,a),console.log(`已加载并执行 ${e} 文件!`)}catch(n){console.error(`加载或执行 ${e} 失败:${n.message}`),console.error(n.stack)}};class K{name;description;version;uuid;allow_random_seed;lock_template_options;pack_scope;base_game_version;min_engine_version;constructor(e,n,o,t,i={}){this.name=e,this.description=n,this.version=o,this.uuid=t,this.allow_random_seed=i.allow_random_seed,this.lock_template_options=i.lock_template_options,this.pack_scope=i.pack_scope,this.base_game_version=i.base_game_version,this.min_engine_version=i.min_engine_version}}class Q{description;type;uuid;version;constructor(e,n,o,t){this.description=e,this.type=n,this.uuid=o,this.version=t}}class X{authors;license;generated_with;product_type;url;constructor(e,n,o,t,i){this.authors=e,this.license=n,this.generated_with=o,this.product_type=t,this.url=i}}class Y{format_version;header;modules;dependencies;capabilities;metadata;constructor(e,n,o,t,i=null,r=null){this.format_version=e,this.header=n,this.modules=o,this.dependencies=t,null!=i&&(this.capabilities=i),null!=r&&(this.metadata=r)}}const Z=process.env.MC_PATH,ee=process.env.MC_BETA_PATH,ne="AppData/Roaming/Minecraft Bedrock/Users/Shared/games/com.mojang",oe="AppData/Local/Packages/Microsoft.MinecraftWindowsBeta_8wekyb3d8bbwe/LocalState/games/com.mojang/";function te(){return"beta"===J().versionType?$("McInstallPath.Beta",()=>ee||o.join(h.homedir(),oe)):$("McInstallPath.Main",()=>Z||o.join(h.homedir(),ne))}var ie={name:"sapdon",version:"3.4.0",scripts:{build:"node scripts/build.cjs",test:'tsc && tsc-alias && node --test "tests/*.test.mjs"',"build:demo":"cd examples/block_demo && npm run build",pub:"npm run build && npm publish"},keywords:["bedrock","tools","addon"],author:"Meteage",license:"ISC",type:"module",bin:{sapdon:"prod/cli/start.js"},description:"Sapdon is a Node.js toolkit designed for building Bedrock Edition Minecraft addon packs.",devDependencies:{"@minecraft/server":"^2.0.0-beta.1.21.80-preview.20","@minecraft/server-ui":"^1.3.0","@types/archiver":"^6.0.0","@types/d3-array":"^3.2.1","@types/lodash":"^4.17.16","@types/node":"^22.14.0","tsc-alias":"^1.8.13"},dependencies:{"@rollup/plugin-commonjs":"^28.0.2","@rollup/plugin-json":"^6.1.0","@rollup/plugin-node-resolve":"^16.0.0","@rollup/plugin-terser":"^0.4.4","@rollup/plugin-typescript":"^12.1.2",chalk:"^5.4.1",commander:"^13.0.0","d3-array":"^3.2.4",archiver:"^7.0.0","download-git-repo":"^3.0.2",inquirer:"^12.3.1",lodash:"^4.17.21",ora:"^8.1.1",rollup:"^4.39.0","rollup-plugin-dts":"^6.2.1","rollup-plugin-typescript-paths":"^1.5.0","rollup-plugin-visualizer":"^5.14.0","tsconfig-paths":"^4.2.0",tslib:"^2.8.1",typescript:"^5.8.2"},files:["prod/**/*","doc/**/*"]};function re(){return $(o.join(x(import.meta),"../../../package.json"),()=>ie)}async function se(e,n){const t=J(),r=o.join(e,t.buildOptions.buildDir),s=o.join(r,`${n}_BP`),c=o.join(r,`${n}_RP`);i.cpSync(s,o.join(te(),"development_behavior_packs/",`${n}_BP/`),{recursive:!0,force:!0}),i.cpSync(c,o.join(te(),"development_resource_packs/",`${n}_RP/`),{recursive:!0,force:!0})}async function ce(e){const n=o.join(e,"node_modules"),t=o.join(x(import.meta),"../"),r=o.join(t,"core"),s=o.join(t,"cli"),c=o.join(t,"oc"),a=o.join(n,"@sapdon/core"),l=o.join(n,"@sapdon/cli"),u=o.join(n,"@sapdon/runtime"),p=re();i.cpSync(r,a,{recursive:!0,force:!0}),i.cpSync(s,l,{recursive:!0,force:!0}),i.cpSync(c,u,{recursive:!0,force:!0}),i.writeFileSync(o.join(a,"package.json"),JSON.stringify({name:"@sapdon/core",type:"module",main:"index.js",types:"index.d.ts",version:p.version})),i.writeFileSync(o.join(l,"package.json"),JSON.stringify({name:"@sapdon/cli",type:"module",main:"index.js",types:"index.d.ts",version:p.version})),i.writeFileSync(o.join(u,"package.json"),JSON.stringify({name:"@sapdon/runtime",type:"module",main:"index.js",types:"index.d.ts",version:p.version}))}const ae=t(import.meta.url);o.dirname(ae);const le=["rollup","typescript","@sapdon/core","@sapdon/cli","@minecraft"];function ue(e){const n=(e=>o.join(e,".sapdon_uuid.json"))(e);if(i.existsSync(n))return JSON.parse(i.readFileSync(n,"utf-8"));const t={bp:S(),rp:S()};return i.mkdirSync(o.dirname(n),{recursive:!0}),i.writeFileSync(n,JSON.stringify(t,null,2)),console.log("已生成持久化 UUID 用于 BP/RP 交叉绑定"),t}const pe={js:async(e,n,o)=>{const t=J();try{const i=await u({input:e,plugins:[d({preferBuiltins:!0}),p(),g(),..."prod"===t.buildOptions.buildMode?[y()]:[]],external(e){for(const n of le)if(e.includes(n))return!0;return!1}});await i.write({file:n,format:"esm",sourcemap:o}),i.close()}catch(e){console.error(e)}},ts:async(e,n,t)=>{const i=E(),r=J(),s={file:n,format:"esm",sourcemap:t};try{const n=await u({input:e,plugins:[f(),d({preferBuiltins:!0}),m({tsconfig:o.join(i,"tsconfig.json"),compilerOptions:{outDir:o.dirname(s.file)}}),p(),g(),..."prod"===r.buildOptions.buildMode?[y()]:[]],external(e){for(const n of le)if(e.includes(n))return!0;return!1}});await n.write(s),n.close()}catch(e){console.error(e)}},any:async(e,n)=>{J().buildOptions.useJs?await pe.js(e,n):await pe.ts(e,n)}};async function de(e){const n="."+crypto.randomUUID()+".js",t=o.join(o.dirname(e),".tmp",n);await pe.any(e,t);try{await async function(e){const{promise:n,resolve:o}=Promise.withResolvers();return c.fork(e,{stdio:"inherit"}).on("exit",o),n}(t)}catch(e){console.error(e)}finally{i.rmSync(t,{force:!0})}}function me(e){return console.log("开始构建项目"),console.log("项目路径:"+e),w(e)?(console.log("项目不存在"),!1):!w(o.join(e,"build.config"))||(console.log("项目没有build.config文件"),!1)}const fe=async(e,n)=>{const t=J(),r=o.join(e,"mod.info"),s=JSON.parse(_(r)),c=ge(s.min_engine_version),a=o.join(e,t.buildOptions.buildDir),l=o.join(a,`${n}_BP/`),u=o.join(a,`${n}_RP/`),p=o.join(e,t.buildOptions.buildEntry);if(!z.isListening()){const i=o.join(l,"manifest.json");if(w(i)){const e=ue(a),n=ge(s.version),i=ye(s.name,s.description,s.version,{min_engine_version:c},t.buildOptions.dependencies,t.buildOptions.scriptOutput,e.bp,e.rp,n),r=he(s.name,s.description,s.version,{min_engine_version:c},[],e.bp,e.rp,n);O(o.join(l,"manifest.json"),i),O(o.join(u,"manifest.json"),r)}const r=o.join(e,"pack_icon.png");v(r,o.join(l,"pack_icon.png")),v(r,o.join(u,"pack_icon.png"));const d=t.buildOptions.resource,m=o.join(e,d.path);k(m,u),z.isListening()||z.bootstrap(),G.startServer(),z.handle("submit",async e=>{"debug"!==t.buildOptions.buildMode&&(G.dataList=e,await q(p,a,n))})}await de(p),await async function(e=!1){const n=e?"js":"ts",t=E(),r=J(),{scriptEntry:s,scriptOutput:c,buildMode:a}=r.buildOptions,l=o.join(B(),c);i.mkdirSync(o.dirname(l),{recursive:!0}),await pe[n](o.join(t,s),l,"dev"===a)}(t.buildOptions.useJs),await se(e,n),J().buildOptions.keepServer||(console.log("[sapdon] 构建完成,开发服务器已自动退出。"),console.log('[sapdon] 如需保持服务器常开(HMR / 热更新),请在 build.config 中设置 "buildOptions.keepServer": true'),process.exit(0))};function ge(e){return e.split(".").map(e=>Number(e))}const ye=(e,n,o,t={},i=[],r,s,c,a)=>{console.log("开始生成behavior_packs/manifest.json"),console.log("Entry:",r);const l=new K(e+"_BP",n,a,s,t),u=new Q("行为模块","data",S(),a),p=new Q("脚本模块","script",S(),a);r&&(p.entry=r);const d=new X(["@sapdon"],"MIT",{sapdon:["1.0.0"]},"addon","https://github.com/junjun260/sapdon"),m=new Y(2,l,[u,p],[...i,{uuid:c,version:a}],null,d);return JSON.stringify(m,null,2)},he=(e,n,o,t={},i=[],r,s,c)=>{const a=new K(e+"_RP",n,c,s,t),l=new Q("资源模块","resources",S(),c),u=new X(["@sapdon"],"MIT",{sapdon:["1.0.0"]},"addon","https://github.com/junjun260/sapdon"),p=new Y(2,a,[l],[...i,{uuid:r,version:c}],null,u);return JSON.stringify(p,null,2)},je="import fs from 'fs'\nexport class FileResource {\n static cache: Record<string, FileResource> = {}\n static fileSystemLoader = (uri: string) => fs.readFileSync(uri)\n\n private _res: any\n\n /**\n * 不要调用构造器!\n * 使用 FileResource.get(uri) 方法获取实例\n * @param origin \n */\n constructor(public origin: string) {\n this.origin = origin\n FileResource.cache[origin] = this\n }\n\n /**\n * @param uri \n * @returns {FileResource}\n */\n static get(uri: string) {\n if (uri in FileResource.cache) {\n return FileResource.cache[uri]\n }\n return new FileResource(uri)\n }\n\n load(loader=FileResource.fileSystemLoader) {\n if (this._res) {\n return this._res\n }\n const content = loader(this.origin)\n this._res = content\n return content\n }\n\n clear() {\n delete FileResource.cache[this.origin]\n }\n\n loadWithoutCache(loader=FileResource.fileSystemLoader) {\n return loader(this.origin)\n }\n\n ptr() {\n const func = () => this.load()\n return Object.assign(func, this)\n }\n}",be=j.debounce;function Se(e,n,t){i.readdirSync(e).forEach(r=>{const s=o.join(e,r);i.statSync(s).isDirectory()?(t(r,e),Se(s,n,t)):n(r,e)})}var ve;function we(e){return e.replaceAll(".","_").replaceAll("-","_").replaceAll("@","$")}function _e(e){const n={type:ve.Dir,name:e,children:{}},t={[e]:n};let i=n;return Se(e,(e,n)=>{const t=o.basename(e),r=we(t.slice(0,t.lastIndexOf("."))),s={type:ve.File,name:r,origin:o.join(n,e)};i.children[r]=s},(e,n)=>{const r=we(e),s=t[n],c={type:ve.Dir,name:r,children:{}};s.children[r]=c,t[o.join(n,e)]=c,i=c}),n}function Oe(e){return!!i.existsSync(o.join(e,"build.config"))||(console.log("无法生成资源目录,请 cd 到项目根目录下执行 sapdon res"),!1)}function ke(){const{buildOptions:e}=J();if(e.buildEntry.endsWith("js"))return;const n=process.cwd(),t=o.join(n,"res");if(!Oe(n))return;i.existsSync(t)||i.mkdirSync(t);!function(e,n){const o={};!function e(n,o){for(const t in n.children){const i=n.children[t];if(i.type===ve.File)o[i.name]=`<fn>${i.origin}</fn>`;else{const n={};o[i.name]=n,e(i,n)}}}(e,o);const t=[je].join(";\n"),r=`\n;export default ${JSON.stringify(o,null,2)}`.replace(/"\<fn\>(.*)\<\/fn\>"/g,"FileResource.get('$1').ptr()");i.writeFileSync(n,t+r)}(_e(t),o.join(n,"res.hint.ts"))}!function(e){e[e.File=0]="File",e[e.Dir=1]="Dir"}(ve||(ve={}));const xe=j.debounce;function Ne(e,n){const{buildDir:t,useHMR:r}=J().buildOptions;!function(){const e=process.cwd();Oe(e)&&i.watch(o.join(e,"res"),{recursive:!0},be((e,n)=>{c.execSync("sapdon res")},3e3))}(),r&&i.watch(e,{recursive:!0},xe(async(i,r)=>{i&&r&&(r.startsWith(".")||r.startsWith("node_modules")||r.startsWith(o.join(t,"./"))||r.includes(".tmp")||(r.endsWith(".js")||r.endsWith(".ts")||"build.config"===r||"mod.info"===r)&&(process.stdout.write(`File ${r} changed, reloading...\r`),await fe(e,n),await se(e,n),console.log(`Reloaded ${r}`)))}),1e3)}const $e=t(import.meta.url),Je=o.dirname($e);process.removeAllListeners("warning"),e.command("init").description("初始化一个基于NodeJS的项目").action(()=>{const e=process.cwd(),t=o.join(e,"package.json");let r;if(i.existsSync(t)){try{r=JSON.parse(i.readFileSync(t,"utf-8"))}catch(e){return console.error("读取package.json文件时出错:",e),void console.log("请使改用create命令进行创建。")}r.scripts={...r.scripts,init:"sapdon init",compile:"sapdon compile",config:"sapdon config"};try{i.writeFileSync(t,JSON.stringify(r,null,2),"utf-8")}catch(e){return void console.error("写入package.json文件时出错:",e)}n.prompt([{type:"input",name:"min_engine_version",message:"最低引擎版本:",default:"1.19.50"}]).then(n=>{((e,n)=>{if(!(w(o.join(e,"mod.info"))&&w(o.join(e,"main.mjs"))&&w(o.join(e,"scripts"))&&w(o.join(e,"build.config"))))return void console.log("项目已存在...");const t=o.join(R,"../templates/js_sapdon");k(t,e),O(o.join(e,"mod.info"),JSON.stringify(n,null,2))})(e,{...D(e),...n}),console.log("请使用命令sapdon config配置框架的build.config文件。")})}else console.error("没有找到package.json文件,请使改用create命令进行创建。")}),e.command("create <project-name>").description("Create a new project").action(e=>{n.prompt([{type:"input",name:"name",message:"Project Name:",default:o.basename(e)},{type:"input",name:"description",message:"Project Description:",default:"A new sapdon project"},{type:"input",name:"author",message:"Author Name:",default:"Sapdon"},{type:"input",name:"version",message:"Project Version:",default:"1.0.0"},{type:"input",name:"min_engine_version",message:"Minimum Engine Version:",default:"1.19.50"},{type:"input",name:"language",message:"Language:(js/ts)",default:"ts"}]).then(n=>{const t=o.join(process.cwd(),e);console.log("项目路径:",t),((e,n)=>{if(console.log(1,n),!w(e))return void console.log("项目名称已存在,创建项目目录失败");const t=o.join(R,`../../src/templates/${P[n.language||"js"]}`);k(t,e),O(o.join(e,"mod.info"),JSON.stringify(n,null,2)),c.execSync("npm i",{cwd:e,stdio:"inherit"})})(t,n),ce(t)})}),e.command("build <project-name>").description("Build the project").action(e=>{console.log("Building the project...");const n=o.join(process.cwd(),e),t=o.basename(n);M.projectPath=n,ke(),me(n)&&(fe(n,t),Ne(n,t))}),e.command("compile").description("Compile the current project (build without HMR)").action(()=>{console.log("Compiling the current project...");const e=process.cwd();me(e)&&fe(e,o.basename(e))}),e.command("pack").description("Package build output into .mcaddon file").action(async()=>{const e=process.cwd(),n=o.basename(e);M.projectPath=e,await(async e=>{const n=E(),t=J(),r=o.join(n,t.buildOptions.buildDir),s=o.join(r,`${e}_BP`),c=o.join(r,`${e}_RP`);if(!i.existsSync(s)||!i.existsSync(c))return void console.error("构建输出目录不存在,请先运行 sapdon compile");const a=o.join(r,`${e}.mcaddon`),l=i.createWriteStream(a),u=b("zip",{zlib:{level:9}});l.on("close",()=>{console.log(`打包完成: ${a} (${u.pointer()} bytes)`)}),u.on("error",e=>{throw e}),u.pipe(l),u.directory(s,`${e}_BP`),u.directory(c,`${e}_RP`),await u.finalize()})(n)}),e.command("lib").description("Generate lib files for development server.").action(()=>{ce(process.cwd())}),e.command("res").description("Generate resource hints.").action(()=>{ke()}),e.command("config").description("Configure build.config file").action(()=>{const e=o.join(Je,"./build.config");let n;try{n=JSON.parse(_(e))}catch(e){return void console.error("读取build.config文件时出错:",e)}}),e.parse(e.argv);
|