Skip to content

Commit ee4e1db

Browse files
committed
迁移vm的插件加载逻辑,更新包管理
1 parent 272a6db commit ee4e1db

8 files changed

Lines changed: 449 additions & 258 deletions

File tree

.github/workflows/build.yml

Lines changed: 56 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,56 @@
1-
name: Build
2-
3-
on:
4-
push:
5-
branches:
6-
- main
7-
8-
jobs:
9-
build:
10-
runs-on: ubuntu-latest
11-
permissions:
12-
contents: write
13-
id-token: write
14-
15-
steps:
16-
- uses: actions/checkout@v4
17-
with:
18-
persist-credentials: false
19-
- name: Install Node.js
20-
uses: actions/setup-node@v4
21-
with:
22-
node-version: 22
23-
24-
- name: Install dependencies
25-
run: npm i
26-
27-
- name: Build
28-
run: npm run build
29-
- name: Test
30-
run: npm test
31-
- name: Deploy to cf-page branch
32-
uses: peaceiris/actions-gh-pages@v4
33-
with:
34-
github_token: ${{ secrets.GITHUB_TOKEN }}
35-
publish_dir: ./dist
36-
publish_branch: cf-page
37-
38-
- name: Build standalone
39-
run: npm run build-standalone-prod
40-
- name: Install tree
41-
run: sudo apt-get install tree
42-
- name: Print file tree
43-
run: tree
44-
- name: Copy standalone.html
45-
run: |
46-
mkdir -p standalone
47-
cp dist/standalone.html standalone/
48-
- name: Upload artifacts to tag
49-
uses: xresloader/upload-to-github-release@2bcae85344d41e21f7fc4c47fa2ed68223afdb49
50-
with:
51-
file: standalone/*
52-
draft: false
1+
name: Build
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
8+
jobs:
9+
build:
10+
runs-on: ubuntu-latest
11+
permissions:
12+
contents: write
13+
id-token: write
14+
15+
steps:
16+
- uses: actions/checkout@v4
17+
with:
18+
persist-credentials: false
19+
20+
- name: Install Node.js
21+
uses: actions/setup-node@v4
22+
with:
23+
node-version: 22
24+
25+
- name: Setup bun ⚙️
26+
uses: oven-sh/setup-bun@v2
27+
28+
- name: Install dependencies
29+
run: bun install
30+
31+
- name: Build
32+
run: bun run build
33+
- name: Test
34+
run: bun test
35+
- name: Deploy to cf-page branch
36+
uses: peaceiris/actions-gh-pages@v4
37+
with:
38+
github_token: ${{ secrets.GITHUB_TOKEN }}
39+
publish_dir: ./dist
40+
publish_branch: cf-page
41+
42+
- name: Build standalone
43+
run: bun run build-standalone-prod
44+
- name: Install tree
45+
run: sudo apt-get install tree
46+
- name: Print file tree
47+
run: tree
48+
- name: Copy standalone.html
49+
run: |
50+
mkdir -p standalone
51+
cp dist/standalone.html standalone/
52+
- name: Upload artifacts to tag
53+
uses: xresloader/upload-to-github-release@2bcae85344d41e21f7fc4c47fa2ed68223afdb49
54+
with:
55+
file: standalone/*
56+
draft: false

.github/workflows/main.yml

Lines changed: 0 additions & 39 deletions
This file was deleted.

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@
3838
"@turbowarp/sbdl": "^5.0.1",
3939
"all": "^0.0.0",
4040
"cross-fetch": "^4.1.0",
41+
"jsdom": "^22.0.0",
42+
"node-fetch": "^2.6.7",
43+
"scratch-render-fonts": "github:02engine/scratch-render-fonts#main",
4144
"sha.js": "^2.4.11"
4245
},
4346
"devDependencies": {

src/packager/extension-loader.js

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
// Extension Loader for 02Engine CLI
2+
// 支持Web、Node.js和P4三种环境
3+
// 迁移自scratch-vm的extension loader,完全独立不依赖scratch-vm
4+
5+
// 尝试导入Node.js环境的依赖
6+
let JSDOM, nodeFetch;
7+
try {
8+
// 动态导入,避免在浏览器环境中出错
9+
JSDOM = require('jsdom').JSDOM;
10+
nodeFetch = require('node-fetch');
11+
} catch (error) {
12+
// 在浏览器环境中这些模块可能不可用
13+
console.debug('Node.js modules not available, running in browser environment');
14+
}
15+
16+
/**
17+
* 检测当前运行环境
18+
* @returns {string} 'browser' | 'node' | 'standalone'
19+
*/
20+
const detectEnvironment = () => {
21+
if (typeof process !== 'undefined' && process.versions && process.versions.node) {
22+
// 检测是否为standalone环境
23+
if (process.env.STANDALONE) {
24+
return 'standalone';
25+
}
26+
return 'node';
27+
}
28+
return 'browser';
29+
};
30+
31+
/**
32+
* 浏览器环境的扩展加载器
33+
* @param {string} extensionURL - 扩展URL
34+
* @returns {Promise<string>} 扩展源代码
35+
*/
36+
const loadExtensionBrowser = (extensionURL) => new Promise((resolve, reject) => {
37+
const script = document.createElement('script');
38+
script.onload = () => {
39+
script.remove();
40+
resolve(extensionURL);
41+
};
42+
script.onerror = () => {
43+
script.remove();
44+
reject(new Error(`Failed to load extension: ${extensionURL}`));
45+
};
46+
script.src = extensionURL;
47+
document.head.appendChild(script);
48+
});
49+
50+
/**
51+
* Node.js环境的扩展加载器
52+
* @param {string} extensionURL - 扩展URL
53+
* @returns {Promise<string>} 扩展源代码
54+
*/
55+
const loadExtensionNode = async (extensionURL) => {
56+
// 检查是否已设置全局document对象
57+
if (!global.document && JSDOM) {
58+
// 模拟浏览器环境核心对象
59+
const dom = new JSDOM('<!DOCTYPE html><body></body>');
60+
global.document = dom.window.document;
61+
global.window = dom.window;
62+
global.location = dom.window.location;
63+
global.fetch = nodeFetch; // 使用node-fetch替换浏览器fetch
64+
} else if (!global.document) {
65+
throw new Error('Document object not available. Running in Node.js environment requires jsdom package.');
66+
}
67+
68+
try {
69+
// 用node-fetch下载扩展脚本
70+
const response = await fetch(extensionURL);
71+
if (!response.ok) {
72+
throw new Error(`HTTP error! status: ${response.status}`);
73+
}
74+
const scriptCode = await response.text();
75+
76+
// 在模拟的window环境中执行脚本
77+
// 使用eval或vm.runInContext取决于安全性要求
78+
// 这里使用window.eval确保在正确的上下文中执行
79+
if (global.window && global.window.eval) {
80+
global.window.eval(scriptCode);
81+
} else {
82+
// 降级方案
83+
eval(scriptCode); // eslint-disable-line no-eval
84+
}
85+
86+
return extensionURL;
87+
} catch (err) {
88+
throw new Error(`Error loading extension ${extensionURL}: ${err.message}`);
89+
}
90+
};
91+
92+
/**
93+
* Standalone环境(Electron等)的扩展加载器
94+
* 继承Node环境的逻辑但可能有特殊的处理
95+
* @param {string} extensionURL - 扩展URL
96+
* @returns {Promise<string>} 扩展源代码
97+
*/
98+
const loadExtensionStandalone = async (extensionURL) => {
99+
// Standalone环境中,如果可以访问浏览器的fetch,则使用浏览器逻辑
100+
if (typeof fetch !== 'undefined' && typeof document !== 'undefined') {
101+
return loadExtensionBrowser(extensionURL);
102+
}
103+
// 否则降级到Node.js逻辑
104+
return loadExtensionNode(extensionURL);
105+
};
106+
107+
/**
108+
* 自动检测环境并选择合适的加载方法
109+
* Load an extension from an arbitrary URL.
110+
* @param {string} extensionURL
111+
* @returns {Promise<string>} Resolves with extension URL if loaded successfully.
112+
*/
113+
const loadExtension = (extensionURL) => {
114+
const environment = detectEnvironment();
115+
116+
switch (environment) {
117+
case 'browser':
118+
return loadExtensionBrowser(extensionURL);
119+
case 'node':
120+
return loadExtensionNode(extensionURL);
121+
case 'standalone':
122+
return loadExtensionStandalone(extensionURL);
123+
default:
124+
throw new Error(`Unsupported environment: ${environment}`);
125+
}
126+
};
127+
128+
/**
129+
* 获取扩展源代码(用于打包到项目中)
130+
* @param {string} extensionURL
131+
* @returns {Promise<string>} 扩展源代码文本
132+
*/
133+
const fetchExtensionSource = async (extensionURL) => {
134+
const environment = detectEnvironment();
135+
136+
if (environment === 'browser' || environment === 'standalone') {
137+
// 浏览器和standalone环境优先使用原生fetch
138+
if (typeof fetch !== 'undefined') {
139+
const response = await fetch(extensionURL);
140+
if (!response.ok) {
141+
throw new Error(`HTTP error! status: ${response.status}`);
142+
}
143+
return response.text();
144+
}
145+
}
146+
147+
// Node.js环境或standalone环境的降级方案
148+
const fetchImpl = nodeFetch || (global && global.fetch);
149+
if (!fetchImpl) {
150+
throw new Error('Fetch not available in this environment');
151+
}
152+
const response = await fetchImpl(extensionURL);
153+
if (!response.ok) {
154+
throw new Error(`HTTP error! status: ${response.status}`);
155+
}
156+
return response.text();
157+
};
158+
159+
/**
160+
* 包装扩展源代码,使其在非沙盒环境中更安全
161+
* @param {string} source - 扩展源代码
162+
* @returns {string} 包装后的源代码
163+
*/
164+
const wrapExtensionSource = (source) => {
165+
// Wrap the extension in an IIFE so that extensions written for the sandbox are less
166+
// likely to cause issues in an unsandboxed environment due to global pollution or
167+
// overriding Scratch.*
168+
return `(function(Scratch) { ${source} })(Scratch);`;
169+
};
170+
171+
module.exports = {
172+
loadExtension,
173+
fetchExtensionSource,
174+
wrapExtensionSource,
175+
detectEnvironment,
176+
loadExtensionBrowser,
177+
loadExtensionNode,
178+
loadExtensionStandalone
179+
};

0 commit comments

Comments
 (0)