Merge origin/master and adapt specialized player integration
Integrate Packager 3.14.0, updated audio/render sources, preview isolation and escaping fixes. Adapt continuation temporaries and deserialized layer ordering, preserve frozen extension support, and align CI with master.
This commit is contained in:
19 files changed
+116
-58
No files matched your search
@@ -32,6 +32,8 @@ runs:
|
||||
- name: Install locked sources
|
||||
shell: bash
|
||||
working-directory: tw-packager
|
||||
env:
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1"
|
||||
run: |
|
||||
npm ci --prefix ../scratch-vm
|
||||
npm ci
|
||||
@@ -4,3 +4,6 @@ updates:
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
allow:
|
||||
- dependency-name: "@turbowarp/*"
|
||||
- dependency-name: "scratch-*"
|
||||
@@ -2,9 +2,9 @@ name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
@@ -3,7 +3,7 @@ name: Deploy to GitHub Pages
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [main]
|
||||
branches: [master]
|
||||
|
||||
concurrency:
|
||||
group: "deploy"
|
||||
|
||||
@@ -59,14 +59,14 @@ Note that npm is a very buggy piece of software and our dependency tree is very
|
||||
|
||||
The packager is deployed as a simple static website. You can host it anywhere by just copying the `dist` folder after a build.
|
||||
|
||||
This fork includes `.github/workflows/deploy.yml` for GitHub Pages. In the repository's **Settings → Pages**, set **Source** to **GitHub Actions**. A push to `main`, or a manual run of “Deploy to GitHub Pages”, will test the project, build the production website and standalone version, upload the Pages artifact, and deploy it to `https://sccnv5.github.io/packager/`.
|
||||
This fork includes `.github/workflows/deploy.yml` for GitHub Pages. In the repository's **Settings → Pages**, set **Source** to **GitHub Actions**. A push to `master`, or a manual run of “Deploy to GitHub Pages”, will test the project, build the production website and standalone version, upload the Pages artifact, and deploy it to `https://sccnv5.github.io/packager/`.
|
||||
|
||||
We use GitHub Actions and GitHub Pages to manage our deployment. If you want to do this too:
|
||||
|
||||
- Fork the repository on GitHub and push your changes.
|
||||
- Go to your fork's settings on GitHub and enable GitHub Pages with the source set to GitHub Actions.
|
||||
- Go to the "Actions" tab and enable GitHub Actions if it isn't already enabled.
|
||||
- Push commits to the "main" branch.
|
||||
- Push commits to the "master" branch.
|
||||
- In a few minutes, your site will automatically be built and deployed to GitHub Pages.
|
||||
|
||||
### Branding
|
||||
|
||||
@@ -77,7 +77,7 @@ To exercise aggressive mode through the public Node API, HTML/ZIP packaging, rea
|
||||
|
||||
CI checks out `ScCNv5/scratch-vm` and `ScCNv5/packager` as siblings. Set the `SPECIALIZED_VM_REF` repository variable to the full commit SHA containing the matching VM changes; workflows reject an absent or floating revision. Before enabling CI after a migration or history rewrite, push the matching VM revision to `ScCNv5/scratch-vm`, then set the variable in `ScCNv5/packager` to the SHA printed by `git -C ../scratch-vm rev-parse HEAD`. Local builds do not push commits. `node test/specialized-differential.mjs --linked` runs the same fixture comparison against the complete linked/pruned/renamed production VM, in addition to the faster continuation-only harness.
|
||||
|
||||
The execution harness covers all 112 existing VM fixtures and compares variables, lists and SAY event order with JS AOT. The linked variant also exercises serialization, pruning and joint transformation; it caught headless target-layer loss and inline legacy primitive registration, both corrected in the implementation. Separate lifecycle tests cover stale Promise completions after stop/restart and completion during pause. Artifact tests reject direct/aliased/computed dynamic evaluation, runtime imports and generators, and inspect source removal, capability removal, seed determinism and dispatch structure. Browser tests prohibit JavaScript dynamic evaluation and exercise playback, API isolation, async extensions, actual WebGL pen pixels and Node/Worker byte equality.
|
||||
The execution harness covers all 150 current VM fixtures and compares variables, lists and SAY event order with JS AOT. The linked variant also exercises serialization, pruning and joint transformation; it caught headless target-layer loss and inline legacy primitive registration, both corrected in the implementation. Separate lifecycle tests cover stale Promise completions after stop/restart and completion during pause. Artifact tests reject direct/aliased/computed dynamic evaluation, runtime imports and generators, and inspect source removal, capability removal, seed determinism and dispatch structure. Browser tests prohibit JavaScript dynamic evaluation and exercise playback, API isolation, async extensions, actual WebGL pen pixels and Node/Worker byte equality.
|
||||
|
||||
The performance harness measures the actual jointly linked VM in the same JS realm as JS AOT, after three warmups and over five measured rounds. It records medians, nearest-rank p95, linked-player startup, build time and process heap. Its 10% median CPU regression gate fails the test on regression. These CPU fixtures cover arithmetic, warp procedure calls among cold scripts, lists, broadcasts, clones and pen state updates. **Passing these small fixtures does not establish real-project performance parity. GPU drawing, per-player peak memory and native desktop startup on every supported OS remain separate release gates.** An additional renderer trace test compares movement, effects and pen operations; an actual dispatcher substitution test rejects a dispatcher from another build and verifies that both original builds preserve behavior. Broader renderer scenarios remain part of project-level regression testing.
|
||||
|
||||
|
||||
Generated
+6
-6
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@sccn/tw-packager",
|
||||
"version": "3.13.0",
|
||||
"version": "3.14.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@sccn/tw-packager",
|
||||
"version": "3.13.0",
|
||||
"version": "3.14.0",
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
"@fiahfy/icns": "^0.0.7",
|
||||
@@ -114,7 +114,6 @@
|
||||
"scratch-storage": "2.3.1",
|
||||
"script-loader": "0.7.2",
|
||||
"stats.js": "0.17.0",
|
||||
"tap": "16.2.0",
|
||||
"tiny-worker": "2.3.0",
|
||||
"uglifyjs-webpack-plugin": "1.2.7",
|
||||
"webpack": "4.47.0",
|
||||
@@ -13159,8 +13158,8 @@
|
||||
},
|
||||
"node_modules/scratch-audio": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "git+ssh://git@github.com/TurboWarp/scratch-audio.git#aba00cd02e36d95407effafa03f3678e4c669b30",
|
||||
"integrity": "sha512-SW920/XWeSC9u9DSnYjQT3LVE/yRVxGOk1Oml4ShTKysq3f0KuNmqWn02nurzk+LP9gEEx/unGYjUvVZyBo9ww==",
|
||||
"resolved": "git+ssh://git@github.com/TurboWarp/scratch-audio.git#dc47bf3bc177289531ed8a4fb616fed895dc303a",
|
||||
"integrity": "sha512-JWfcMClxHzQLHGjL/LCx6IP0D0eF3HNT+Zp5vwHUmh0Lg8OWH3hscnvCWJFj8EkRf+gQ07uOMIt9VT3MwD6CWQ==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
@@ -13170,7 +13169,8 @@
|
||||
},
|
||||
"node_modules/scratch-render": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "git+ssh://git@github.com/TurboWarp/scratch-render.git#a67f7c9c07d459582c227d4fd3fae8f59d8fc9ce",
|
||||
"resolved": "git+ssh://git@github.com/TurboWarp/scratch-render.git#430b44fbff2040198ce526b05bae4f59f4e530ce",
|
||||
"integrity": "sha512-nXbON0rvQ31172Wv32IyWgaSJkV2J17oEOwu7ZXtdZh2LXeUn1uRHfhg3jkUnY2hw5FFkPVtOpcVZLJA44X2Ew==",
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@sccn/tw-packager",
|
||||
"version": "3.13.0",
|
||||
"version": "3.14.0",
|
||||
"description": "Compiles, protects, and packages Scratch projects as HTML, archives, or desktop applications.",
|
||||
"main": "dist/packager.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -114,15 +114,15 @@ function prepareVM(code, id) {
|
||||
input instanceof ArrayBuffer || ArrayBuffer.isView(input) ? JSON.parse(new TextDecoder().decode(input)) : input;
|
||||
this.clear();
|
||||
for (const id of project.extensions || []) await this.extensionManager.loadExtensionURL((project.extensionURLs || {})[id] || id);
|
||||
const {targets} = await require('./serialization/sb3').deserialize(project, this.runtime);
|
||||
const {targets, layerOrder} = await require('./serialization/sb3').deserialize(project, this.runtime);
|
||||
const layerOrders = new Map(targets.map((target, index) => [target, layerOrder[index]]));
|
||||
for (let index = 0; index < targets.length; index++) {
|
||||
const target = targets[index];
|
||||
target.sprite.__spTarget = index;
|
||||
this.runtime.addTarget(target);
|
||||
target.updateAllDrawableProperties();
|
||||
}
|
||||
this.runtime.executableTargets.sort((a,b) => a.layerOrder-b.layerOrder);
|
||||
targets.forEach(target => { delete target.layerOrder; });
|
||||
this.runtime.executableTargets.sort((a,b) => layerOrders.get(a)-layerOrders.get(b));
|
||||
this.editingTarget = targets[1] || targets[0];
|
||||
this.runtime.setEditingTarget(this.editingTarget);
|
||||
this.runtime.ioDevices.cloud.setStage(this.runtime.getTargetForStage());
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
"autoplay": "大きな緑の旗を表示せずに自動でプロジェクトを始める",
|
||||
"autoplayHint": "注意:多くのブラウザでは、ウェブサイトが自動で音を再生することができません。そのため、ユーザーがプロジェクトに参加するまで音が再生されない場合があります。",
|
||||
"backgroundColor": "背景の色",
|
||||
"backgroundThrottling": "最小化または非表示になっているときにプロジェクトのスロットリングを許可する",
|
||||
"bakeExtensions": "プロジェクトを実行するたびにダウンロードするのではなく、カスタム拡張機能のキャッシュされたコピーを埋め込むようにしてください",
|
||||
"closeWhenStopped": "プロジェクトを止めたとき、ウィンドウを閉じます",
|
||||
"cloudVariables": "クラウド変数",
|
||||
|
||||
+7
-7
@@ -78,9 +78,9 @@
|
||||
"interpolation": "보간법 활성화",
|
||||
"learnMore": "추가 정보",
|
||||
"listColor": "리스트 색상",
|
||||
"loadingScreen": "로딩 스크린",
|
||||
"loadingScreenImage": "로딩 스크린에 표시할 이미지",
|
||||
"loadingScreenText": "로딩 스크린에 표시할 텍스트",
|
||||
"loadingScreen": "로딩 화면",
|
||||
"loadingScreenImage": "로딩 화면에 표시할 이미지",
|
||||
"loadingScreenText": "로딩 화면에 표시할 텍스트",
|
||||
"loadingScreenTextPlaceholder": "(텍스트 입력)",
|
||||
"maxTextureDimension": "벡터 이미지의 최대 해상도를 높여 커다란 이미지를 더 보기 좋게 만듭니다. 메모리 사용량이 증가하거나 충돌이 발생할 수 있습니다.",
|
||||
"mode": "모드",
|
||||
@@ -95,11 +95,11 @@
|
||||
"packageName": "패키지 이름",
|
||||
"packageNameHelp": "패키지 이름은 앱의 고유한 이름이여야 합니다. 같은 프로젝트는 항상 같은 패키지 이름을 가져야 합니다.",
|
||||
"packagedRuntime": "RAM 공간을 절약하기 위해 불러오기 이후 원시 에셋 데이터 제거",
|
||||
"pageTitle": "페이지 타이틀",
|
||||
"pageTitle": "페이지 제목",
|
||||
"playerOptions": "플레이어 설정",
|
||||
"pointerlock": "마우스 커서 잠금",
|
||||
"pointerlockHelp": "TurboWarp 실험 기능에 기반함.",
|
||||
"preserveRatio": "창 크기가 변경되었을 때 화면 비율 고정하기 (권장됨)",
|
||||
"preserveRatio": "창 크기가 변경되었을 때 화면 비율 고정하기 (권장)",
|
||||
"preview": "미리보기",
|
||||
"projectId": "프로젝트 아이디",
|
||||
"projectIdHelp": "프로젝트 아이디는 클라우드 변수 서버와 연결할 때, 그리고 로컬 클라우드 변수를 저장할 때 사용됩니다. 프로젝트 아이디는 숫자뿐만 아니라 문자열로도 지정 가능합니다.",
|
||||
@@ -137,7 +137,7 @@
|
||||
"unFullscreenOrExit": "전체 화면 나가기 또는 실행 끝내기",
|
||||
"unsafeCloudBehaviors": "안전하지 않은 특수 클라우드 변수 기능",
|
||||
"unsafeCloudBehaviorsWarning": "안전하지 않은 클라우드 변수 기능은 패키징된 프로젝트가 일반적으로 실행되는 공간인 \"샌드박스\"의 외부에서 임의의 코드를 실행할 수 있도록 허용합니다. 이로서 패키징하는 환경에 따라 프로젝트는 컴퓨터에 대한 모든 제어 권한을 부여받으며, 바이러스 설치의 여지가 있습니다. 따라서 패키징 하려는 프로젝트를 신뢰하지 않는 경우 이 기능을 비횔성화 하시기 바랍니다.",
|
||||
"username": "사용자 이름 (각각의 \"#\"은 랜덤한 숫자로 대체됩니다)",
|
||||
"username": "사용자 이름 (각각의 \"#\"은 무작위 숫자로 대체됩니다)",
|
||||
"variableColor": "변수 표시기 색상",
|
||||
"version": "버전",
|
||||
"versionHelp": "앱의 버전 업데이트는 선택 사항이며 프로젝트의 기능에 영향을 미치지 않습니다. 앱의 버전은 사용자의 환경에 따라 프로그램 정보 화면에 나타날 수도 있습니다. 버전은 X.Y.Z 형식으로 나타납니다.",
|
||||
@@ -149,7 +149,7 @@
|
||||
"p4": {
|
||||
"browserNotSupported": "해당 브라우저는 지원되지 않음",
|
||||
"browserNotSupportedDescription": "브라우저를 업데이트 해 주세요.",
|
||||
"cannotAccessProject": "프로젝트 토큰에 액세스할 수 없습니다. 이 현상은 프로젝트가 공유되지 않았거나, 존재한 적이 없거나, ID가 잘못되었거나, 네트워크가 turbowarp.org를 차단한 경우 발생할 수 있습니다.",
|
||||
"cannotAccessProject": "프로젝트 토큰에 액세스할 수 없습니다. 프로젝트가 공유되지 않았거나, 존재한 적이 없거나, ID가 잘못되었거나, 네트워크가 turbowarp.org를 차단했을 수 있습니다.",
|
||||
"cannotAccessProjectCaching": "최근에 공유된 프로젝트의 경우, 몇 분간 이 메시지가 계속 나타날 수 있습니다.",
|
||||
"close": "닫기",
|
||||
"description1": "Scratch 프로젝트를 HTML이나 ZIP 아카이브 파일 또는 Windows, macOS, Linux의 실행 파일로 변환합니다.",
|
||||
|
||||
+2
-2
@@ -13,7 +13,7 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
<Section accent={color}>
|
||||
<!-- <Section accent={color}>
|
||||
<div lang="en">
|
||||
<p>
|
||||
<span class="badge">New!</span>
|
||||
@@ -22,4 +22,4 @@
|
||||
Infinite clones is now actually infinite instead of being limited to 9,999,999,999 (though you may encounter significant practical limitations long before reaching that limit).
|
||||
</p>
|
||||
</div>
|
||||
</Section>
|
||||
</Section> -->
|
||||
@@ -193,7 +193,7 @@
|
||||
try {
|
||||
result = await task.do(runPackager(task, optionsClone));
|
||||
task.done();
|
||||
previewer.setContent(result.blob);
|
||||
previewer.setContent(result.blob, optionsClone.app.windowTitle);
|
||||
} catch (e) {
|
||||
previewer.close();
|
||||
}
|
||||
|
||||
+32
-21
@@ -44,6 +44,14 @@ const getPreviewSource = () => `<!DOCTYPE html>
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
.preview-frame {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -55,6 +63,12 @@ const getPreviewSource = () => `<!DOCTYPE html>
|
||||
<div class="preview-error-message"></div>
|
||||
<div>Go back to the original tab and try again</div>
|
||||
</div>
|
||||
<iframe
|
||||
class="preview-frame"
|
||||
sandbox="allow-scripts allow-pointer-lock allow-popups allow-modals allow-forms allow-downloads"
|
||||
allow="fullscreen; autoplay; camera; microphone; gamepad; clipboard-write"
|
||||
hidden
|
||||
></iframe>
|
||||
<script>
|
||||
(function() {
|
||||
const origin = ${JSON.stringify(origin)};
|
||||
@@ -74,16 +88,13 @@ const getPreviewSource = () => `<!DOCTYPE html>
|
||||
if (hasRun) return;
|
||||
if (e.data.blob) {
|
||||
hasRun = true;
|
||||
const fr = new FileReader();
|
||||
fr.onload = () => {
|
||||
document.open();
|
||||
document.write(fr.result);
|
||||
document.close(); // fixes poor performance in firefox
|
||||
};
|
||||
fr.onerror = () => {
|
||||
err("Something went wrong reading the file: " + fr.error);
|
||||
};
|
||||
fr.readAsText(e.data.blob);
|
||||
if (typeof e.data.title === "string") {
|
||||
document.title = e.data.title;
|
||||
}
|
||||
const frame = document.querySelector(".preview-frame");
|
||||
frame.src = URL.createObjectURL(e.data.blob);
|
||||
frame.hidden = false;
|
||||
frame.focus();
|
||||
}
|
||||
if (typeof e.data.progress === "number") {
|
||||
progressBar.style.width = (e.data.progress * 100) + "%";
|
||||
@@ -97,7 +108,7 @@ const getPreviewSource = () => `<!DOCTYPE html>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
const windowToBlobMap = new WeakMap();
|
||||
const windowToMetadataMap = new WeakMap();
|
||||
|
||||
class Preview {
|
||||
constructor () {
|
||||
@@ -122,11 +133,13 @@ class Preview {
|
||||
}
|
||||
}
|
||||
|
||||
setContent (content) {
|
||||
windowToBlobMap.set(this.window, content);
|
||||
this.window.postMessage({
|
||||
blob: content
|
||||
}, origin);
|
||||
setContent (content, title) {
|
||||
const metadata = {
|
||||
blob: content,
|
||||
title
|
||||
};
|
||||
windowToMetadataMap.set(this.window, metadata);
|
||||
this.window.postMessage(metadata, origin);
|
||||
}
|
||||
|
||||
setProgress (progress, text) {
|
||||
@@ -148,11 +161,9 @@ window.addEventListener('message', (e) => {
|
||||
const data = e.data;
|
||||
if (data && data.preview === 'hello') {
|
||||
const source = e.source;
|
||||
const blob = windowToBlobMap.get(source);
|
||||
if (blob) {
|
||||
source.postMessage({
|
||||
blob
|
||||
}, origin);
|
||||
const metadata = windowToMetadataMap.get(source);
|
||||
if (metadata) {
|
||||
source.postMessage(metadata, origin);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -83,9 +83,10 @@ const mutateScratch3InPlace = (projectData) => {
|
||||
const stage = projectData.targets.find((i) => i.isStage);
|
||||
if (stage) {
|
||||
for (const variable of Object.values(stage.variables)) {
|
||||
// variable is [name, value, isCloud]
|
||||
// variable is [name, value, isCloud?]
|
||||
// isCloud's only allowed values are "not defined" or "true". scratch-parser rejects "false".
|
||||
if (variable[2] && DISABLE_CLOUD_VARIABLES.includes(variable[0])) {
|
||||
variable[2] = false;
|
||||
variable.length = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,25 @@ const PROGRESS_EXTRACTED_COMPRESSED = 0.98;
|
||||
const PROGRESS_FETCHED_PROJECT_JSON = 0.2;
|
||||
const PROGRESS_FETCHED_ASSETS = 0.98;
|
||||
|
||||
/**
|
||||
* JSON.stringify but it also escapes < so that it can't break out of an inline <script>
|
||||
* @param {unknown} value
|
||||
* @returns {string}
|
||||
*/
|
||||
const stringifyForInlineScript = (value) => JSON.stringify(value).replace(/</g, '\\u003c');
|
||||
|
||||
/**
|
||||
* Escape for safe use in shell script.
|
||||
* @param {string} value
|
||||
* @returns {string}
|
||||
*/
|
||||
const escapeShellArgument = (value) => {
|
||||
if (/^[\w.-]+$/.test(value)) {
|
||||
return value;
|
||||
}
|
||||
return `'${value.replace(/'/g, `'\\''`)}'`;
|
||||
};
|
||||
|
||||
const removeUnnecessaryEmptyLines = (string) => string.split('\n')
|
||||
.filter((line, index, array) => {
|
||||
if (index === 0 || index === array.length - 1) return true;
|
||||
@@ -397,7 +416,7 @@ class Packager extends EventTarget {
|
||||
} else if (isLinux) {
|
||||
const startScript = `#!/bin/bash
|
||||
cd "$(dirname "$0")"
|
||||
./${packageName}`;
|
||||
./${escapeShellArgument(packageName)}`;
|
||||
zip.file(`${packageName}/start.sh`, startScript, {
|
||||
unixPermissions: 0o100755
|
||||
});
|
||||
@@ -897,7 +916,7 @@ const {contextBridge, ipcRenderer} = require('electron');
|
||||
// Some Linux distributions can't easily open the executable file from the GUI, so we'll add a simple wrapper that people can use instead.
|
||||
const startScript = `#!/bin/bash
|
||||
cd "$(dirname "$0")"
|
||||
./${packageName}`;
|
||||
./${escapeShellArgument(packageName)}`;
|
||||
zip.file(`${rootPrefix}start.sh`, startScript, {
|
||||
unixPermissions: 0o100755
|
||||
});
|
||||
@@ -975,11 +994,11 @@ cd "$(dirname "$0")"
|
||||
'wss://clouddata.turbowarp.org',
|
||||
'wss://clouddata.turbowarp.xyz'
|
||||
] : this.options.cloudVariables.cloudHost;
|
||||
return `new Scaffolding.Cloud.WebSocketProvider(${JSON.stringify(cloudHost)}, ${JSON.stringify(this.options.projectId)})`;
|
||||
return `new Scaffolding.Cloud.WebSocketProvider(${stringifyForInlineScript(cloudHost)}, ${stringifyForInlineScript(this.options.projectId)})`;
|
||||
}
|
||||
|
||||
makeLocalStorageProvider () {
|
||||
return `new Scaffolding.Cloud.LocalStorageProvider(${JSON.stringify(`cloudvariables:${this.options.projectId}`)})`;
|
||||
return `new Scaffolding.Cloud.LocalStorageProvider(${stringifyForInlineScript(`cloudvariables:${this.options.projectId}`)})`;
|
||||
}
|
||||
|
||||
makeCustomProvider () {
|
||||
@@ -995,7 +1014,7 @@ cd "$(dirname "$0")"
|
||||
result += 'for (const provider of Object.values(providers)) scaffolding.addCloudProvider(provider);\n';
|
||||
for (const variableName of Object.keys(variables)) {
|
||||
const providerToUse = variables[variableName];
|
||||
result += `scaffolding.addCloudProviderOverride(${JSON.stringify(variableName)}, providers[${JSON.stringify(providerToUse)}] || null);\n`;
|
||||
result += `scaffolding.addCloudProviderOverride(${stringifyForInlineScript(variableName)}, providers[${stringifyForInlineScript(providerToUse)}] || null);\n`;
|
||||
}
|
||||
result += '}';
|
||||
return result;
|
||||
@@ -1290,7 +1309,7 @@ cd "$(dirname "$0")"
|
||||
scaffolding = new Scaffolding.Scaffolding();
|
||||
scaffolding.width = ${this.options.stageWidth};
|
||||
scaffolding.height = ${this.options.stageHeight};
|
||||
scaffolding.resizeMode = ${JSON.stringify(this.options.resizeMode)};
|
||||
scaffolding.resizeMode = ${stringifyForInlineScript(this.options.resizeMode)};
|
||||
scaffolding.editableLists = ${this.options.monitors.editableLists};
|
||||
scaffolding.usePackagedRuntime = ${this.options.packagedRuntime};
|
||||
scaffolding.setup();
|
||||
@@ -1298,8 +1317,8 @@ cd "$(dirname "$0")"
|
||||
|
||||
vm = scaffolding.vm;
|
||||
|
||||
scaffolding.setUsername(${JSON.stringify(this.options.username)}.replace(/#/g, () => Math.floor(Math.random() * 10)));
|
||||
scaffolding.setAccentColor(${JSON.stringify(this.options.appearance.accent)});
|
||||
scaffolding.setUsername(${stringifyForInlineScript(this.options.username)}.replace(/#/g, () => Math.floor(Math.random() * 10)));
|
||||
scaffolding.setAccentColor(${stringifyForInlineScript(this.options.appearance.accent)});
|
||||
|
||||
try {
|
||||
${this.options.cloudVariables.mode === 'ws' ?
|
||||
@@ -1441,7 +1460,7 @@ cd "$(dirname "$0")"
|
||||
if (vm.runtime.setEnforcePrivacy) vm.runtime.setEnforcePrivacy(false);
|
||||
|
||||
if (typeof ScaffoldingAddons !== 'undefined') {
|
||||
ScaffoldingAddons.run(scaffolding, ${JSON.stringify(this.getAddonOptions())});
|
||||
ScaffoldingAddons.run(scaffolding, ${stringifyForInlineScript(this.getAddonOptions())});
|
||||
}
|
||||
|
||||
${this.options.closeWhenStopped ? `
|
||||
|
||||
@@ -198,6 +198,7 @@ export function stepBody(blocks, transfer = false) {
|
||||
if (thread.updateMonitor) { execute(this, thread); return; }
|
||||
const target = thread.target, runtime = this.runtime;
|
||||
const variables = thread.__spVariables || (thread.__spVariables = []);
|
||||
${blocks.some(block => block.code.some(code => /\bt\s*=/.test(code))) ? 'let t;' : ''}
|
||||
${blocks.some(block => block.code.some(code => /\bstage\b/.test(code))) ? 'const stage = runtime.getTargetForStage();' : ''}
|
||||
const isStuck = () => {
|
||||
thread.__spStuck = (thread.__spStuck || 0) + 1;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import JSZip from '@turbowarp/jszip';
|
||||
import {downloadProject} from '../../src/packager/download-project'
|
||||
|
||||
const readTestProject = (name) => {
|
||||
@@ -35,6 +36,25 @@ test('sb3 with implied cloud variables', async () => {
|
||||
]);
|
||||
});
|
||||
|
||||
test('sb3 with disabled cloud variables', async () => {
|
||||
const project = await downloadProject(readTestProject('disabled-cloud-variables.sb3'), () => {});
|
||||
expect(project.type).toBe('sb3');
|
||||
expect(project.analysis.stageVariables).toEqual([
|
||||
{
|
||||
name: '☁ potential reporters',
|
||||
isCloud: false
|
||||
},
|
||||
{
|
||||
name: '☁ Implied',
|
||||
isCloud: true
|
||||
}
|
||||
]);
|
||||
const zip = await JSZip.loadAsync(project.arrayBuffer);
|
||||
const json = JSON.parse(await zip.file('project.json').async('text'));
|
||||
expect(json.targets[0].variables['QqN3g5B}a/kJ6O`XZQUd']).toStrictEqual(['☁ potential reporters', '']);
|
||||
expect(json.targets[0].variables['m/1@VP1Ko9amvO.`R4{d']).toStrictEqual(['☁ Implied', 0, true]);
|
||||
});
|
||||
|
||||
test('sb3 with comments', async () => {
|
||||
const project = await downloadProject(readTestProject('comments.sb3'), () => {});
|
||||
expect(project.type).toBe('sb3');
|
||||
|
||||
Binary file not shown.
Reference in new issue
Block a user