[ty] Fix the Pyodide assets deployment in the ty playground. (#24994)

<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title? (Please prefix
with `[ty]` for ty pull
  requests.)
- Does this pull request include references to any relevant issues?
- Does this PR follow our AI policy
(https://github.com/astral-sh/.github/blob/main/AI_POLICY.md)?
-->

## Summary

<!-- What's the purpose of the change? What does it do, and why? -->
- Adds some graceful degradation to Pyodide loading failures in the ty
playground (with the result that the entire playground UI doesn't
disappear if we only fail to execute a code run).
- Corrects the placement of assets in our playground deployment, and
adds some validation to the deployment workflow as a simple regression
test

Closes https://github.com/astral-sh/ty/issues/3409.

## Test Plan

I've verified the new asset layout in a local build of the playground. I
think that's sufficient, but I will monitor the production playground
deployment and revert this change if necessary.

<!-- How was it tested? -->
This commit is contained in:
Lérè
2026-05-04 19:20:09 -07:00
committed by GitHub
parent a8d3850605
commit 2558b41d61
3 changed files with 39 additions and 29 deletions
@@ -50,6 +50,13 @@ jobs:
- name: "Build ty playground"
run: npm run build --workspace ty-playground
working-directory: playground
- name: "Verify Pyodide assets"
run: |
test -f ty/dist/assets/pyodide.asm.js
test -f ty/dist/assets/pyodide.asm.wasm
test -f ty/dist/assets/pyodide-lock.json
test -f ty/dist/assets/python_stdlib.zip
working-directory: playground
- name: "Deploy to Cloudflare Pages"
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
uses: cloudflare/wrangler-action@9acf94ace14e7dc412b076f2c5c20b8ce93c79cd # v3.15.0
+31 -29
View File
@@ -4,39 +4,40 @@ import { SerializedFiles } from "../Playground";
const SANDBOX_BASE_DIRECTORY = "/playground/";
export async function runPython(workspace: SerializedFiles): Promise<string> {
const pyodide = await loadPyodide({
env: {
HOME: SANDBOX_BASE_DIRECTORY,
},
});
let combinedOutput = "";
const outputHandler = (output: string) => {
combinedOutput += output + "\n";
};
pyodide.setStdout({ batched: outputHandler });
pyodide.setStderr({ batched: outputHandler });
try {
const pyodide = await loadPyodide({
env: {
HOME: SANDBOX_BASE_DIRECTORY,
},
});
for (const [fileName, content] of Object.entries(workspace.files)) {
const lastSeparator = fileName.lastIndexOf("/");
pyodide.setStdout({ batched: outputHandler });
pyodide.setStderr({ batched: outputHandler });
if (lastSeparator !== -1) {
const directory =
SANDBOX_BASE_DIRECTORY + fileName.slice(0, lastSeparator);
pyodide.FS.mkdirTree(directory);
for (const [fileName, content] of Object.entries(workspace.files)) {
const lastSeparator = fileName.lastIndexOf("/");
if (lastSeparator !== -1) {
const directory =
SANDBOX_BASE_DIRECTORY + fileName.slice(0, lastSeparator);
pyodide.FS.mkdirTree(directory);
}
pyodide.FS.writeFile(SANDBOX_BASE_DIRECTORY + fileName, content);
}
pyodide.FS.writeFile(SANDBOX_BASE_DIRECTORY + fileName, content);
}
const dict = pyodide.globals.get("dict");
const globals = dict();
const dict = pyodide.globals.get("dict");
const globals = dict();
try {
// Patch `reveal_type` to print runtime values
pyodide.runPython(`
try {
pyodide.runPython(`
import builtins
def reveal_type(obj):
@@ -46,17 +47,18 @@ export async function runPython(workspace: SerializedFiles): Promise<string> {
builtins.reveal_type = reveal_type`);
pyodide.runPython(workspace.files[workspace.current] ?? "", {
globals,
locals: globals,
filename: workspace.current,
});
pyodide.runPython(workspace.files[workspace.current] ?? "", {
globals,
locals: globals,
filename: workspace.current,
});
} finally {
globals.destroy();
dict.destroy();
}
return combinedOutput;
} catch (error) {
return `Failed to run Python script: ${error}`;
} finally {
globals.destroy();
dict.destroy();
}
}
+1
View File
@@ -28,6 +28,7 @@ export function viteStaticCopyPyodide() {
{
src: [pyodideDir, ...PYODIDE_EXCLUDE],
dest: "assets",
rename: { stripBase: true },
},
],
});