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

configuration.mddocs/

Configuration Management

Configuration file discovery and loading system supporting multiple formats, inheritance through extends, flexible search patterns, and custom file extension loaders.

Capabilities

Configuration File Discovery

Discovers configuration files based on search patterns, file extensions, and directory traversal rules defined in the Liftoff instance configuration.

/**
 * Configuration file search specification
 * Used in configFiles array to define how to search for config files
 */
interface ConfigFileSpec {
  /** Base name of the config file to search for */
  name?: string;
  /** Path to search in (required) - can be relative or absolute */
  path: string;
  /** File extensions to try when searching */
  extensions?: string[] | { [ext: string]: string | null };
  /** Base directory for resolving relative paths */
  cwd?: string;
  /** Whether to search up the directory tree until file found */
  findUp?: boolean;
}

Usage Examples:

const Liftoff = require('liftoff');

// Configuration with multiple config file sources
const MyApp = new Liftoff({
  name: 'myapp',
  configFiles: [
    // Look for .myapprc in home directory
    { 
      name: '.myapprc', 
      path: '~',
      extensions: { '.json': null, '.js': null }
    },
    
    // Look for myappfile.js in current directory and up the tree
    { 
      name: 'myappfile', 
      path: '.',
      findUp: true
    },
    
    // Look for project-specific config in custom location
    {
      name: 'project-config',
      path: './config',
      cwd: process.cwd(),
      extensions: ['.json', '.yaml', '.js']
    }
  ],
  extensions: {
    '.js': null,
    '.json': null,
    '.yaml': 'yaml-loader'
  }
});

const env = MyApp.buildEnvironment();
console.log('Found config files:', env.configFiles);
// Might output:
// [
//   '/home/user/.myapprc.json',
//   '/project/myappfile.js', 
//   '/project/config/project-config.yaml'
// ]

Configuration Loading and Inheritance

Loads configuration files and processes inheritance through the extends property, with support for deep merging and circular reference detection.

/**
 * Configuration file structure with inheritance support
 */
interface ConfigurationFile {
  /** Inherit from another configuration file */
  extends?: string | ConfigFileSpec;
  /** Override the discovered config path */
  [configName: string]?: string;
  /** Additional modules to preload */
  preload?: string | string[];
  /** Any other configuration properties */
  [key: string]: any;
}

Configuration Inheritance Process:

  1. File Loading: Load the primary configuration file
  2. Extends Processing: If extends property exists, load the extended configuration
  3. Recursive Loading: Process extends in the extended file (with circular detection)
  4. Deep Merging: Merge configurations with primary config taking precedence
  5. Cleanup: Remove extends property from final configuration

Usage Examples:

// Base configuration file: base-config.js
module.exports = {
  mode: 'development',
  plugins: ['plugin-a', 'plugin-b'],
  settings: {
    verbose: true,
    timeout: 5000
  }
};

// Main configuration file: myappfile.js  
module.exports = {
  extends: './base-config.js',
  mode: 'production',  // Overrides base
  plugins: ['plugin-c'], // Completely replaces base plugins array
  settings: {
    timeout: 10000  // Overrides timeout, keeps verbose: true
  },
  additionalSetting: 'value'
};

// Result after loading and merging:
// {
//   mode: 'production',
//   plugins: ['plugin-c'],
//   settings: {
//     verbose: true,    // From base
//     timeout: 10000    // From main config
//   },
//   additionalSetting: 'value'
// }

Configuration Path Override

Configuration files can override the discovered configuration path using a property that matches the configName.

/**
 * Configuration path override mechanism
 * A config file can specify an alternate config file to use
 */
interface ConfigPathOverride {
  /** Property name matches the Liftoff configName */
  [configName: string]: string;
}

Usage Examples:

const MyApp = new Liftoff({ 
  name: 'myapp',  // configName becomes 'myappfile'
  configFiles: [{ name: '.myapprc', path: '~' }]
});

// .myapprc.json content:
// {
//   "myappfile": "./custom/path/to/config.js",
//   "otherSettings": "value"
// }

const env = MyApp.buildEnvironment();
// env.configPath will be '/full/path/to/custom/path/to/config.js'
// The .myapprc config is used to find the real config, then the real config is loaded

// Relative paths are resolved from the directory containing the override config
// {
//   "myappfile": "../configs/main.js"  // Resolved relative to .myapprc location
// }

Preload Module Configuration

Configuration files can specify additional modules to preload before CLI execution through the preload property.

/**
 * Preload specification in configuration files
 */
interface PreloadConfiguration {
  /** Single module to preload */
  preload?: string;
  /** Multiple modules to preload */
  preload?: string[];
}

Usage Examples:

// Configuration file with preload modules
module.exports = {
  // String format for single module
  preload: 'coffee-script/register',
  
  // Other config properties
  sourceMaps: true,
  plugins: ['babel-plugin-transform-runtime']
};

// Configuration file with multiple preload modules
module.exports = {
  // Array format for multiple modules
  preload: [
    'babel-register',
    'source-map-support/register',
    'coffee-script/register'
  ],
  
  // Babel configuration for the preloaded babel-register
  babel: {
    presets: ['env']
  }
};

// The preload modules are combined with any specified in buildEnvironment options
const env = MyApp.buildEnvironment({
  preload: ['ts-node/register']  // Added to config-based preloads
});
// env.preload = ['ts-node/register', 'coffee-script/register']

File Extension Support

Configuration files support custom file extensions through registered loaders, allowing for TypeScript, CoffeeScript, YAML, and other formats.

/**
 * File extension to loader mapping
 * Loaders are Node.js modules that register file extension handlers
 */
interface ExtensionLoaders {
  /** Extension with no loader (built-in Node.js support) */
  [extension: string]: null;
  /** Extension with loader module name */
  [extension: string]: string;
}

Usage Examples:

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

// Can now load any of these config files:
// - myappfile.js
// - myappfile.json  
// - myappfile.coffee
// - myappfile.ts
// - myappfile.yaml
// - myappfile.toml

// The appropriate loader will be automatically required and registered
MyApp.on('loader:success', function(loaderName, module) {
  console.log('Loaded extension loader:', loaderName);
});

MyApp.on('loader:failure', function(loaderName, error) {
  console.error('Failed to load extension loader:', loaderName, error.message);
});

Advanced Configuration Patterns

Complex configuration scenarios including multiple inheritance levels, development vs production configs, and modular configuration structures.

Multi-level Inheritance:

// base.js - Foundation configuration
module.exports = {
  core: {
    timeout: 5000,
    retries: 3
  }
};

// development.js - Development overrides
module.exports = {
  extends: './base.js',
  core: {
    debug: true,
    timeout: 1000  // Faster timeout for development
  },
  devServer: {
    port: 3000
  }
};

// myappfile.js - Project-specific configuration
module.exports = {
  extends: './development.js',
  project: {
    name: 'my-project',
    version: '1.0.0'
  },
  core: {
    retries: 5  // Override retries for this project
  }
};

// Final merged configuration:
// {
//   core: {
//     timeout: 1000,    // From development.js
//     retries: 5,       // From myappfile.js  
//     debug: true       // From development.js
//   },
//   devServer: {
//     port: 3000        // From development.js
//   },
//   project: {
//     name: 'my-project',
//     version: '1.0.0'  // From myappfile.js
//   }
// }

Error Handling and Validation:

const MyApp = new Liftoff({
  name: 'myapp',
  configFiles: [{ name: 'myappfile', path: '.', findUp: true }]
});

try {
  const env = MyApp.buildEnvironment();
  
  // Validate configuration structure
  if (env.config.length > 0) {
    const config = env.config[0];
    
    // Check for required properties
    if (!config.mode) {
      console.warn('No mode specified in configuration');
    }
    
    // Validate preload modules
    if (config.preload && !Array.isArray(config.preload) && typeof config.preload !== 'string') {
      console.error('Invalid preload configuration: must be string or array');
    }
  }
  
} catch (error) {
  if (error.message.includes('circular extend')) {
    console.error('Circular configuration inheritance detected');
    console.error('Check your extends chain for loops');
  } else if (error.message.includes('Unable to locate')) {
    console.error('Configuration extends references missing file');
    console.error('Verify the path in your extends property');
  } else if (error.message.includes('Encountered error when loading')) {
    console.error('Configuration file has syntax or runtime errors');
    console.error('Check the configuration file for valid JavaScript/JSON');
  } else {
    console.error('Configuration loading failed:', error.message);
  }
}

docs

configuration.md

core-lifecycle.md

environment-building.md

events.md

index.md

tile.json