An easy-to-use platform for EEG experimentation in the classroom
1#!/usr/bin/env node
2/**
3 * Copies the Pyodide runtime from the installed npm package into the renderer's
4 * publicDir so Vite can serve it as static assets.
5 *
6 * Source: node_modules/pyodide/
7 * Dest: src/renderer/utils/webworker/src/pyodide/
8 *
9 * Key files copied:
10 * pyodide.mjs – ESM entry point (imported by the web worker via npm)
11 * pyodide.js – UMD fallback
12 * pyodide.asm.js – compiled Python interpreter
13 * pyodide.asm.wasm – WebAssembly binary
14 * python_stdlib.zip – Python standard library
15 * pyodide-lock.json – package registry (read by InstallMNE.mjs)
16 *
17 * Intentionally skipped:
18 * package.json – would make Vite treat the dir as an npm package
19 * and attempt to transform pyodide.mjs as a module
20 * *.d.ts – TypeScript declaration files, not needed at runtime
21 * *.html – console demo pages
22 * README.md – documentation
23 * *.map – source maps (large, optional for debugging)
24 *
25 * A version stamp (.pyodide-version) is written so subsequent runs are skipped
26 * when the installed version has not changed.
27 *
28 * Usage: node internals/scripts/InstallPyodide.mjs
29 * Runs automatically via the postinstall npm hook.
30 */
31
32import fs from 'fs';
33import path from 'path';
34import { createRequire } from 'module';
35import chalk from 'chalk';
36
37const _require = createRequire(import.meta.url);
38
39const DEST_DIR = path.resolve('src/renderer/utils/webworker/src/pyodide');
40const VERSION_FILE = path.join(DEST_DIR, '.pyodide-version');
41
42// Files to exclude from the copy.
43const SKIP_EXTENSIONS = new Set(['.d.ts', '.map', '.html', '.md']);
44const SKIP_FILES = new Set(['package.json', 'README.md']);
45
46function shouldSkip(filename) {
47 if (SKIP_FILES.has(filename)) return true;
48 for (const ext of SKIP_EXTENSIONS) {
49 if (filename.endsWith(ext)) return true;
50 }
51 return false;
52}
53
54async function main() {
55 // Locate the pyodide package directory via Node's module resolution.
56 let pyodideDir;
57 try {
58 pyodideDir = path.dirname(_require.resolve('pyodide/package.json'));
59 } catch {
60 console.error(
61 chalk.red(
62 'pyodide not found in node_modules. Run `npm install` first.'
63 )
64 );
65 process.exit(1);
66 }
67
68 const version = JSON.parse(
69 fs.readFileSync(path.join(pyodideDir, 'package.json'), 'utf8')
70 ).version;
71
72 // Skip if this version was already installed.
73 if (
74 fs.existsSync(VERSION_FILE) &&
75 fs.readFileSync(VERSION_FILE, 'utf8').trim() === version
76 ) {
77 console.log(chalk.gray(`Pyodide ${version} already installed — skipping.`));
78 return;
79 }
80
81 console.log(
82 chalk.blue.bold(`Installing Pyodide ${version} from node_modules…`)
83 );
84 fs.mkdirSync(DEST_DIR, { recursive: true });
85
86 const files = fs.readdirSync(pyodideDir);
87 for (const file of files) {
88 if (shouldSkip(file)) continue;
89
90 const src = path.join(pyodideDir, file);
91 const dest = path.join(DEST_DIR, file);
92
93 if (fs.statSync(src).isDirectory()) continue;
94
95 process.stdout.write(chalk.blue(` ${file}: `));
96 fs.copyFileSync(src, dest);
97 console.log(chalk.green('copied'));
98 }
99
100 fs.writeFileSync(VERSION_FILE, version);
101 console.log(
102 chalk.green.bold(`\nPyodide ${version} ready at ${DEST_DIR}`)
103 );
104}
105
106main().catch((err) => {
107 console.error(chalk.red('Fatal error:'), err);
108 process.exit(1);
109});