feat(ruffle): seed a fresh data volume from the bundled copy
This commit is contained in:
+4
-12
@@ -293,20 +293,12 @@ async function startServer() {
|
||||
logger.warn('Failed to sweep orphaned download files:', error);
|
||||
});
|
||||
|
||||
// Ruffle installs in the background: it downloads from GitHub, and awaiting it
|
||||
// here kept the port closed for minutes on slow storage — indefinitely if the
|
||||
// download stalled. Flash games need it, everything else does not.
|
||||
// Ruffle resolves in the background: seeding copies ~20 MB and the download
|
||||
// fallback reaches GitHub, and awaiting either here kept the port closed for
|
||||
// minutes on slow storage. Flash games need it, everything else does not.
|
||||
void (async () => {
|
||||
try {
|
||||
const ruffleService = new RuffleService();
|
||||
if (!ruffleService.verifyInstallation()) {
|
||||
logger.info('🎮 Ruffle not found, downloading latest version...');
|
||||
await ruffleService.updateRuffle();
|
||||
logger.info('✅ Ruffle installation complete');
|
||||
} else {
|
||||
const version = ruffleService.getCurrentVersion();
|
||||
logger.info(`✅ Ruffle verified (version: ${version || 'unknown'})`);
|
||||
}
|
||||
await new RuffleService().ensureInstalled();
|
||||
} catch (error) {
|
||||
logger.error('Failed to install Ruffle:', error);
|
||||
logger.warn('⚠️ Continuing without Ruffle - Flash games will not work');
|
||||
|
||||
@@ -259,3 +259,48 @@ describe('RuffleService.updateRuffle', () => {
|
||||
expect(nodeFs.readFileSync(nodePath.join(ruffleDir(), 'ruffle.js'), 'utf-8')).toBe('old build');
|
||||
});
|
||||
});
|
||||
|
||||
function installBundledRuffle(): void {
|
||||
const bundled = nodePath.join(distDir, 'ruffle');
|
||||
nodeFs.mkdirSync(bundled, { recursive: true });
|
||||
nodeFs.writeFileSync(nodePath.join(bundled, 'ruffle.js'), 'bundled build');
|
||||
nodeFs.writeFileSync(
|
||||
nodePath.join(bundled, 'package.json'),
|
||||
JSON.stringify({ version: '0.2.0-nightly.2026.1.29' })
|
||||
);
|
||||
}
|
||||
|
||||
describe('RuffleService.ensureInstalled', () => {
|
||||
it('leaves an existing installation alone', async () => {
|
||||
installExistingRuffle();
|
||||
installBundledRuffle();
|
||||
|
||||
const result = await new RuffleService().ensureInstalled();
|
||||
|
||||
expect(result).toBe('present');
|
||||
expect(nodeFs.readFileSync(nodePath.join(ruffleDir(), 'ruffle.js'), 'utf-8')).toBe('old build');
|
||||
expect(axios.get).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('seeds from the bundled copy when nothing is installed', async () => {
|
||||
installBundledRuffle();
|
||||
const service = new RuffleService();
|
||||
|
||||
const result = await service.ensureInstalled();
|
||||
|
||||
expect(result).toBe('seeded');
|
||||
expect(nodeFs.readFileSync(nodePath.join(ruffleDir(), 'ruffle.js'), 'utf-8')).toBe(
|
||||
'bundled build'
|
||||
);
|
||||
expect(service.getCurrentVersion()).toBe('0.2.0-nightly.2026.1.29');
|
||||
expect(axios.get).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('downloads when neither an installation nor a bundled copy exists', async () => {
|
||||
const result = await new RuffleService().ensureInstalled();
|
||||
|
||||
expect(result).toBe('downloaded');
|
||||
expect(nodeFs.readFileSync(nodePath.join(ruffleDir(), 'ruffle.js'), 'utf-8')).toBe('new build');
|
||||
expect(axios.get).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -581,4 +581,48 @@ export class RuffleService {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the Ruffle shipped inside the image into the install directory. A fresh
|
||||
* data volume otherwise has no player at all until a download finishes — and
|
||||
* never gets one on a host that cannot reach GitHub.
|
||||
*/
|
||||
private seedFromBundle(): boolean {
|
||||
if (this.bundledPath === this.installPath) {
|
||||
return false;
|
||||
}
|
||||
if (!fs.existsSync(path.join(this.bundledPath, 'ruffle.js'))) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
this.copyDirectory(this.bundledPath, this.installPath);
|
||||
logger.info(
|
||||
`✅ Ruffle seeded from the bundled copy (version: ${this.getCurrentVersion() ?? 'unknown'})`
|
||||
);
|
||||
return true;
|
||||
} catch (error: unknown) {
|
||||
logger.warn('[RuffleService] Could not seed Ruffle from the bundled copy:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a Ruffle available, preferring what is already installed, then the copy
|
||||
* baked into the image, and only then a download.
|
||||
*/
|
||||
async ensureInstalled(): Promise<'present' | 'seeded' | 'downloaded'> {
|
||||
if (this.verifyInstallation()) {
|
||||
logger.info(`✅ Ruffle verified (version: ${this.getCurrentVersion() ?? 'unknown'})`);
|
||||
return 'present';
|
||||
}
|
||||
|
||||
if (this.seedFromBundle()) {
|
||||
return 'seeded';
|
||||
}
|
||||
|
||||
logger.info('🎮 Ruffle not found, downloading latest version...');
|
||||
await this.updateRuffle();
|
||||
logger.info('✅ Ruffle installation complete');
|
||||
return 'downloaded';
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user