Skip to content

Hooks

Execute code at different stages of the build process.

Available Hooks

HookWhenUse Case
onGenerateRuntimeBefore runtime generationChrome flags, early initialization
onPreBuildBefore build startsValidation, setup, notifications
onModifyAssetsAfter assets copiedTransform files, inject code
onBeforePackageBefore Electron packagingFinal modifications
onPostBuildAfter build completeCleanup, notifications, upload
onBuildErrorOn build failureError reporting
onBuildProgressDuring buildProgress tracking

Hook Signatures

onGenerateRuntime

Inject code that runs before app.ready() in the built game. This is the only way to set Chrome command line flags or perform early initialization.

javascript
onGenerateRuntime(context, settings) {
  // context.gamePath - Source game folder
  // context.config - Game configuration
  // settings - Plugin settings values
  
  // Return JavaScript code as a string
  // This code will run in the Electron main process before app.ready()
  return `_a.commandLine.appendSwitch('disable-frame-rate-limit');`;
}

Important

The returned code runs in the main process runtime. Use _a to reference the Electron app module (already imported). Do not use require('electron') as it's already loaded.

onPreBuild

javascript
async onPreBuild(context, settings) {
  // context.gamePath - Source game folder
  // context.config - Game configuration
  // settings - Plugin settings values
}

onModifyAssets

javascript
async onModifyAssets(context, settings) {
  // context.gamePath - Source game folder
  // context.outputPath - Build output folder
  // context.config - Game configuration
  // context.platform - 'darwin', 'win32', 'linux'
  // context.arch - 'arm64', 'x64', 'ia32'
  // settings - Plugin settings values
}

onBeforePackage

javascript
async onBeforePackage(context, settings) {
  // Same as onModifyAssets
  // Called just before Electron packaging
}

onPostBuild

javascript
async onPostBuild(context, settings) {
  // context.gamePath - Source game folder
  // context.outputPath - Final build folder
  // context.config - Game configuration
  // context.platform - Target platform
  // context.arch - Target architecture
  // settings - Plugin settings values
}

onBuildError

javascript
async onBuildError(context, settings) {
  // context.gamePath - Source game folder
  // context.config - Game configuration
  // context.error - Error message
  // settings - Plugin settings values
}

onBuildProgress

javascript
onBuildProgress(context, settings) {
  // context.gamePath - Source game folder
  // context.config - Game configuration
  // context.percent - Progress 0-100
  // context.message - Status message
  // settings - Plugin settings values
}

Example: Complete Plugin

javascript
module.exports = {
  name: 'Build Pipeline',
  version: '1.0.0',
  
  settings: [
    { id: 'validateAssets', type: 'checkbox', label: 'Validate assets', default: true },
    { id: 'webhookUrl', type: 'text', label: 'Webhook URL' }
  ],
  
  async onPreBuild(context, settings) {
    gemshell.log('Starting build...');
    
    if (settings.validateAssets) {
      // Validate required files exist
      const required = ['index.html'];
      for (const file of required) {
        if (!gemshell.fs.exists(file)) {
          throw new Error(`Missing required file: ${file}`);
        }
      }
      gemshell.log('Asset validation passed');
    }
  },
  
  async onModifyAssets(context, settings) {
    gemshell.log('Processing assets...');
    
    // Add build timestamp
    const timestamp = new Date().toISOString();
    gemshell.transform.injectIntoHtml(
      `<meta name="build-time" content="${timestamp}">`,
      'head'
    );
  },
  
  async onBeforePackage(context, settings) {
    gemshell.log('Preparing for packaging...');
    
    // Remove development files
    const devFiles = ['*.map', '*.ts', 'tsconfig.json'];
    for (const pattern of devFiles) {
      const files = gemshell.glob(pattern);
      for (const file of files) {
        gemshell.fs.remove(file);
      }
    }
  },
  
  async onPostBuild(context, settings) {
    gemshell.log('Build complete!');
    
    // Send webhook notification
    if (settings.webhookUrl) {
      await gemshell.http.post(settings.webhookUrl, {
        event: 'build_complete',
        game: context.config.title,
        version: context.config.version,
        platform: `${context.platform}-${context.arch}`,
        time: new Date().toISOString()
      });
    }
  },
  
  async onBuildError(context, settings) {
    gemshell.error('Build failed:', context.error);
    
    if (settings.webhookUrl) {
      await gemshell.http.post(settings.webhookUrl, {
        event: 'build_failed',
        game: context.config.title,
        error: context.error,
        time: new Date().toISOString()
      });
    }
  },
  
  onBuildProgress(context, settings) {
    // Called frequently, keep lightweight
    if (context.percent % 25 === 0) {
      gemshell.log(`Progress: ${context.percent}%`);
    }
  }
};

Error Handling

Throw errors to abort the build:

javascript
async onPreBuild(context, settings) {
  if (!gemshell.fs.exists('index.html')) {
    throw new Error('index.html is required');
  }
}

The error message will be shown to the user and onBuildError will be called.

Async vs Sync

All hooks can be async:

javascript
async onPostBuild(context, settings) {
  // Async operations work
  await gemshell.http.post(...);
  await someAsyncFunction();
}

Exception: onBuildProgress should be synchronous for performance.