CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-liftoff

Launch your command line tool with ease by handling configuration discovery, local module resolution, and process management.

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

events.mddocs/

Event System

EventEmitter-based system providing lifecycle hooks for module preloading, loader registration, and process respawning with detailed status information and error handling.

Capabilities

Preload Events

Events emitted during module preloading operations, providing hooks for logging, error handling, and module registration customization.

/**
 * Event emitted before attempting to preload a module
 * @param moduleName - Name of the module being preloaded
 */
on('preload:before', (moduleName: string) => void): Liftoff;

/**
 * Event emitted when a module is successfully preloaded
 * @param moduleName - Name of the successfully loaded module
 * @param module - The actual loaded module object
 */
on('preload:success', (moduleName: string, module: any) => void): Liftoff;

/**
 * Event emitted when module preloading fails
 * @param moduleName - Name of the module that failed to load
 * @param error - Error object containing failure details
 */
on('preload:failure', (moduleName: string, error: Error) => void): Liftoff;

Usage Examples:

const Liftoff = require('liftoff');

const MyApp = new Liftoff({
  name: 'myapp',
  extensions: {
    '.coffee': 'coffee-script/register',
    '.ts': 'ts-node/register'
  }
});

// Set up preload event handlers
MyApp.on('preload:before', function(moduleName) {
  console.log(`[PRELOAD] Attempting to load: ${moduleName}`);
});

MyApp.on('preload:success', function(moduleName, module) {
  console.log(`[PRELOAD] Successfully loaded: ${moduleName}`);
  
  // Custom module configuration after successful load
  if (moduleName === 'coffee-script/register') {
    // CoffeeScript is now available for .coffee files
    console.log('CoffeeScript support enabled');
  } else if (moduleName === 'babel-register') {
    // Customize babel configuration
    module.setDefaults({
      presets: ['env'],
      plugins: ['transform-runtime']
    });
    console.log('Babel configured with default presets');
  }
});

MyApp.on('preload:failure', function(moduleName, error) {
  console.error(`[PRELOAD] Failed to load: ${moduleName}`);
  console.error(`[PRELOAD] Error: ${error.message}`);
  
  // Provide fallback or alternative solutions
  if (moduleName === 'coffee-script/register') {
    console.warn('CoffeeScript support unavailable - .coffee files will not be processed');
  } else if (moduleName === 'babel-register') {
    console.warn('Babel transpilation unavailable - modern JS syntax may cause errors');
  }
});

// Execute with preload modules
MyApp.prepare({
  preload: ['babel-register', 'coffee-script/register', 'nonexistent-module']
}, function(env) {
  MyApp.execute(env, function(env, argv) {
    console.log('Application started with preloaded modules');
  });
});

Loader Events

Events emitted when file extension loaders are registered, allowing for monitoring and customization of extension handling.

/**
 * Event emitted when a file extension loader is successfully registered
 * @param loaderName - Name/path of the loader module
 * @param module - The loaded loader module
 */
on('loader:success', (loaderName: string, module: any) => void): Liftoff;

/**
 * Event emitted when a file extension loader fails to load
 * @param loaderName - Name/path of the loader module that failed
 * @param error - Error object containing failure details
 */
on('loader:failure', (loaderName: string, error: Error) => void): Liftoff;

Usage Examples:

const MyApp = new Liftoff({
  name: 'myapp',
  extensions: {
    '.js': null,
    '.json': null,
    '.coffee': 'coffee-script/register',
    '.ts': 'ts-node/register',
    '.yaml': 'js-yaml-loader'
  },
  configFiles: [
    { name: 'myappfile', path: '.', findUp: true }
  ]
});

// Monitor loader registration
MyApp.on('loader:success', function(loaderName, module) {
  console.log(`[LOADER] Successfully registered: ${loaderName}`);
  
  // Configure specific loaders after registration
  if (loaderName === 'ts-node/register') {
    console.log('TypeScript configuration loaded');
    // ts-node is now handling .ts files
  } else if (loaderName === 'js-yaml-loader') {
    console.log('YAML configuration support enabled');
    // YAML files can now be required
  }
});

MyApp.on('loader:failure', function(loaderName, error) {
  console.error(`[LOADER] Failed to register: ${loaderName}`);
  console.error(`[LOADER] Reason: ${error.message}`);
  
  // Handle missing optional loaders gracefully
  if (loaderName === 'coffee-script/register') {
    console.warn('CoffeeScript config files will not be supported');
    console.warn('Install coffee-script package to enable .coffee configs');
  } else if (loaderName === 'ts-node/register') {
    console.warn('TypeScript config files will not be supported');
    console.warn('Install ts-node package to enable .ts configs');
  }
});

// Environment building will trigger loader events as configs are discovered
const env = MyApp.buildEnvironment();
console.log('Environment built with config support for:', Object.keys(MyApp.extensions));

Respawn Events

Events emitted when the process needs to be respawned with v8 flags, providing visibility into process management and flag handling.

/**
 * Event emitted when Liftoff respawns the process for v8 flags
 * @param flags - Array of v8 flags that triggered the respawn
 * @param childProcess - The child process object
 */
on('respawn', (flags: string[], childProcess: object) => void): Liftoff;

Usage Examples:

const Liftoff = require('liftoff');

const MyApp = new Liftoff({
  name: 'myapp',
  v8flags: ['--harmony', '--experimental-modules']
});

// Monitor process respawning
MyApp.on('respawn', function(flags, childProcess) {
  console.log(`[RESPAWN] Process respawned with flags: ${flags.join(' ')}`);
  console.log(`[RESPAWN] Child PID: ${childProcess.pid}`);
  console.log(`[RESPAWN] Original PID: ${process.pid}`);
  
  // Log respawn for debugging
  if (flags.includes('--harmony')) {
    console.log('ES6 harmony features enabled');
  }
  if (flags.includes('--experimental-modules')) {
    console.log('Experimental ES modules enabled');
  }
  
  // Set up child process monitoring
  childProcess.on('exit', function(code, signal) {
    console.log(`[RESPAWN] Child process exited with code ${code}, signal ${signal}`);
  });
});

// Execute - respawn will occur if v8flags detected in process.argv
MyApp.prepare({}, function(env) {
  MyApp.execute(env, function(env, argv) {
    console.log('Application running in respawned process (if respawn occurred)');
    console.log('Process PID:', process.pid);
  });
});

// Example with forced flags causing respawn
MyApp.prepare({}, function(env) {
  MyApp.execute(env, ['--trace-deprecation'], function(env, argv) {
    console.log('Forced respawn with --trace-deprecation flag');
  });
});

Event-Driven Workflow Example

Comprehensive example showing how to use all Liftoff events together for complete lifecycle monitoring and customization.

const Liftoff = require('liftoff');
const chalk = require('chalk');  // For colored output (optional)

const MyTool = new Liftoff({
  name: 'mytool',
  extensions: {
    '.js': null,
    '.json': null,
    '.coffee': 'coffee-script/register',
    '.ts': 'ts-node/register',
    '.babel.js': 'babel-register'
  },
  v8flags: ['--harmony'],
  configFiles: [
    { name: '.mytoolrc', path: '~' },
    { name: 'mytoolfile', path: '.', findUp: true }
  ]
});

// Comprehensive event monitoring
let preloadCount = 0;
let loaderCount = 0;
let respawnOccurred = false;

MyTool.on('preload:before', function(moduleName) {
  process.stdout.write(`⏳ Loading ${moduleName}...`);
});

MyTool.on('preload:success', function(moduleName, module) {
  preloadCount++;
  console.log(` ✅ Loaded ${moduleName}`);
  
  // Module-specific post-load configuration
  if (moduleName === 'babel-register') {
    // Configure Babel after successful load
    if (module.setDefaultOption) {
      module.setDefaultOption('presets', ['env']);
      console.log('   📝 Configured Babel with env preset');
    }
  } else if (moduleName === 'coffee-script/register') {
    console.log('   ☕ CoffeeScript support enabled');
  }
});

MyTool.on('preload:failure', function(moduleName, error) {
  console.log(` ❌ Failed to load ${moduleName}: ${error.message}`);
  
  // Provide helpful installation hints
  if (moduleName.includes('coffee-script')) {
    console.log('   💡 Try: npm install coffee-script');
  } else if (moduleName.includes('babel')) {
    console.log('   💡 Try: npm install babel-register babel-preset-env');
  } else if (moduleName.includes('ts-node')) {
    console.log('   💡 Try: npm install ts-node typescript');
  }
});

MyTool.on('loader:success', function(loaderName, module) {
  loaderCount++;
  console.log(`🔧 Registered loader: ${loaderName}`);
});

MyTool.on('loader:failure', function(loaderName, error) {
  console.warn(`⚠️  Failed to register loader: ${loaderName}`);
  console.warn(`   Reason: ${error.message}`);
});

MyTool.on('respawn', function(flags, childProcess) {
  respawnOccurred = true;
  console.log(`🔄 Respawned with flags: ${flags.join(' ')}`);
  console.log(`   Parent PID: ${process.pid} → Child PID: ${childProcess.pid}`);
});

// Execute the tool with full event monitoring
console.log('🚀 Starting MyTool...\n');

MyTool.prepare({
  preload: ['babel-register', 'source-map-support/register']
}, function(env) {
  console.log('\n📊 Environment Summary:');
  console.log(`   Working Directory: ${env.cwd}`);
  console.log(`   Config File: ${env.configPath || 'none found'}`);
  console.log(`   Local Module: ${env.modulePath || 'none found'}`);
  console.log(`   Preloaded Modules: ${preloadCount}`);
  console.log(`   Registered Loaders: ${loaderCount}`);
  console.log(`   Process Respawned: ${respawnOccurred ? 'yes' : 'no'}`);
  
  MyTool.execute(env, function(env, argv) {
    console.log('\n✨ MyTool is ready!');
    console.log('CLI Arguments:', argv);
    
    // Your tool's main logic here
    if (env.configPath) {
      try {
        const config = require(env.configPath);
        console.log('📄 Configuration loaded:', Object.keys(config));
      } catch (e) {
        console.error('❌ Failed to load config:', e.message);
      }
    }
  });
});

Error Event Handling

Best practices for handling errors in event listeners and preventing unhandled exceptions.

const MyApp = new Liftoff({ name: 'myapp' });

// Always handle errors in event listeners
MyApp.on('preload:failure', function(moduleName, error) {
  // Log the error but don't throw - let execution continue
  console.error(`Module ${moduleName} failed to load:`, error.message);
  
  // Optionally exit for critical modules
  if (moduleName === 'critical-module') {
    console.error('Critical module failed - cannot continue');
    process.exit(1);
  }
});

MyApp.on('loader:failure', function(loaderName, error) {
  // Loader failures are usually non-fatal
  console.warn(`Optional loader ${loaderName} unavailable:`, error.message);
});

// Set up global error handling for unhandled exceptions
process.on('uncaughtException', function(error) {
  console.error('Uncaught exception:', error.message);
  console.error('Stack:', error.stack);
  process.exit(1);
});

process.on('unhandledRejection', function(reason, promise) {
  console.error('Unhandled promise rejection:', reason);
  process.exit(1);
});

docs

configuration.md

core-lifecycle.md

environment-building.md

events.md

index.md

tile.json