Skip to content

Migration Guide

This guide helps you migrate between different versions of ScriptIt, understand breaking changes, and maintain compatibility across upgrades.

Version Compatibility

Current Version Support

ScriptIt follows semantic versioning (SemVer) with the following support policy:

  • Major versions (1.x → 2.x): Breaking changes, migration required
  • Minor versions (1.1 → 1.2): New features, backward compatible
  • Patch versions (1.1.1 → 1.1.2): Bug fixes, fully compatible

Supported Versions

VersionStatusSupport LevelEnd of Life
2.x✅ CurrentFull support-
1.x🟡 MaintenanceSecurity fixes only2025-12-31
0.x❌ DeprecatedNo support2024-06-30

Migration Strategies

1. Gradual Migration

Migrate scripts incrementally to minimize risk:

bash
# Step 1: Install new version alongside old
npm install @glyphtek/scriptit@latest --save-dev

# Step 2: Test individual scripts
scriptit exec test-script.js --runtime=node

# Step 3: Migrate configuration
cp scriptit.config.js scriptit.config.js.backup
# Update configuration file

# Step 4: Update scripts one by one
# Step 5: Remove old version when complete

2. Parallel Environment

Run both versions side by side during transition:

javascript
// package.json
{
  "scripts": {
    "scriptit-old": "scriptit-v1 exec",
    "scriptit-new": "scriptit exec",
    "migrate-test": "npm run scriptit-new -- test-script.js"
  },
  "dependencies": {
    "@glyphtek/scriptit": "^2.0.0"
  },
  "devDependencies": {
    "@glyphtek/scriptit-v1": "npm:@glyphtek/scriptit@^1.0.0"
  }
}

3. Feature Flag Migration

Use environment variables to control migration:

javascript
// scriptit.config.js
const useV2Features = process.env.SCRIPTIT_V2 === 'true';

export default {
  scriptsDir: 'scripts',
  
  // V2 features
  ...(useV2Features && {
    consoleInterception: { enabled: true },
    eventSystem: { enabled: true }
  }),
  
  // Legacy compatibility
  ...(!useV2Features && {
    legacyMode: true
  })
};

Version-Specific Migration Guides

Migrating from 1.x to 2.x

Breaking Changes

  1. Configuration File Format
  2. Script Context Object
  3. CLI Command Changes
  4. Runtime Detection

Configuration Migration

Old format (1.x):

javascript
// scriptit.config.js (v1)
module.exports = {
  scriptsPath: './scripts',
  tempPath: './tmp',
  defaultRuntime: 'node',
  colors: true
};

New format (2.x):

javascript
// scriptit.config.js (v2)
export default {
  scriptsDir: './scripts',  // renamed from scriptsPath
  tmpDir: './tmp',          // renamed from tempPath
  consoleInterception: {    // replaces colors
    enabled: true
  }
};

Migration script:

javascript
// migrate-config.js
export const description = "Migrate v1 config to v2 format";

export async function execute(context) {
  const console = context.console || global.console;
  const fs = await import('fs/promises');
  const path = await import('path');
  
  const configPath = './scriptit.config.js';
  
  try {
    // Read old config
    const oldConfig = await import(path.resolve(configPath));
    const config = oldConfig.default || oldConfig;
    
    // Convert to new format
    const newConfig = {
      scriptsDir: config.scriptsPath || './scripts',
      tmpDir: config.tempPath || './tmp',
      consoleInterception: {
        enabled: config.colors !== false
      }
    };
    
    // Write new config
    const configContent = `export default ${JSON.stringify(newConfig, null, 2)};`;
    await fs.writeFile(configPath, configContent);
    
    console.log('✅ Configuration migrated to v2 format');
    return { migrated: true, newConfig };
    
  } catch (error) {
    console.error('❌ Migration failed:', error.message);
    throw error;
  }
}

Script Context Migration

Old context (1.x):

javascript
// v1 script
export async function execute(context) {
  const { env, tmpPath, params } = context;
  
  console.log('Environment:', env.NODE_ENV);
  console.log('Temp path:', tmpPath);
}

New context (2.x):

javascript
// v2 script
export async function execute(context) {
  const console = context.console || global.console;  // New: colored console
  const { env, tmpDir, params, log, configPath } = context;  // tmpDir renamed, new properties
  
  console.log('Environment:', env.NODE_ENV);
  console.log('Temp directory:', tmpDir);  // renamed from tmpPath
  context.log('Execution log');             // new logging method
}

Compatibility wrapper:

javascript
// compatibility-wrapper.js
export function wrapV1Script(v1Execute) {
  return async function execute(context) {
    // Create v1-compatible context
    const v1Context = {
      ...context,
      tmpPath: context.tmpDir  // Map new property to old name
    };
    
    return await v1Execute(v1Context);
  };
}

// Usage in migrated script
import { wrapV1Script } from './compatibility-wrapper.js';

async function v1Execute(context) {
  // Original v1 script logic
  console.log('Temp path:', context.tmpPath);
}

export const execute = wrapV1Script(v1Execute);

CLI Command Migration

Old commands (1.x):

bash
# v1 commands
scriptit run script.js
scriptit list
scriptit tui

New commands (2.x):

bash
# v2 commands
scriptit exec script.js    # renamed from 'run'
scriptit list             # unchanged
scriptit run              # now launches TUI (renamed from 'tui')

Migration aliases:

bash
# Add to package.json scripts for compatibility
{
  "scripts": {
    "scriptit-run": "scriptit exec",  # v1 compatibility
    "scriptit-tui": "scriptit run"    # v1 compatibility
  }
}

Migrating from 0.x to 1.x

Major Changes

  1. Module System: CommonJS → ES Modules
  2. Script Structure: Function exports → Named exports
  3. Configuration: JSON → JavaScript

Module System Migration

Old format (0.x):

javascript
// script.js (v0)
module.exports = async function(context) {
  console.log('Hello from v0');
  return { success: true };
};

New format (1.x+):

javascript
// script.js (v1+)
export const description = "Migrated from v0";

export async function execute(context) {
  const console = context.console || global.console;
  console.log('Hello from v1+');
  return { success: true };
}

Automated migration script:

javascript
// migrate-v0-scripts.js
export const description = "Migrate v0 scripts to v1+ format";

export async function execute(context) {
  const console = context.console || global.console;
  const fs = await import('fs/promises');
  const path = await import('path');
  const glob = await import('glob');
  
  const scriptsDir = context.env.SCRIPTS_DIR || './scripts';
  const scriptFiles = glob.sync(`${scriptsDir}/**/*.js`);
  
  console.log(`Found ${scriptFiles.length} script files to migrate`);
  
  for (const filePath of scriptFiles) {
    try {
      const content = await fs.readFile(filePath, 'utf8');
      
      // Check if it's a v0 script
      if (content.includes('module.exports') && !content.includes('export')) {
        const migrated = migrateV0Script(content);
        
        // Backup original
        await fs.writeFile(`${filePath}.v0.backup`, content);
        
        // Write migrated version
        await fs.writeFile(filePath, migrated);
        
        console.log(`✅ Migrated: ${filePath}`);
      } else {
        console.log(`⏭️  Skipped: ${filePath} (already migrated)`);
      }
    } catch (error) {
      console.error(`❌ Failed to migrate ${filePath}:`, error.message);
    }
  }
  
  return { migrated: scriptFiles.length };
}

function migrateV0Script(content) {
  // Simple regex-based migration (enhance as needed)
  let migrated = content
    .replace(/module\.exports\s*=\s*async\s+function\s*\([^)]*\)\s*{/, 
             'export const description = "Migrated from v0";\n\nexport async function execute(context) {\n  const console = context.console || global.console;')
    .replace(/module\.exports\s*=\s*function\s*\([^)]*\)\s*{/, 
             'export const description = "Migrated from v0";\n\nexport function execute(context) {\n  const console = context.console || global.console;');
  
  return migrated;
}

Compatibility Patterns

Backward Compatibility Layer

Create a compatibility layer for gradual migration:

javascript
// compatibility.js
export function createCompatibilityLayer(version) {
  switch (version) {
    case '1.x':
      return {
        wrapContext: (context) => ({
          ...context,
          tmpPath: context.tmpDir  // v1 compatibility
        }),
        
        wrapScript: (script) => async (context) => {
          const wrappedContext = this.wrapContext(context);
          return await script(wrappedContext);
        }
      };
      
    case '0.x':
      return {
        wrapScript: (script) => async (context) => {
          // Convert v0 module.exports to v1+ execute function
          if (typeof script === 'function') {
            return await script(context);
          }
          throw new Error('Invalid v0 script format');
        }
      };
      
    default:
      throw new Error(`Unsupported version: ${version}`);
  }
}

Feature Detection

Detect available features for cross-version compatibility:

javascript
// feature-detection.js
export function detectFeatures() {
  const features = {
    coloredConsole: false,
    eventSystem: false,
    tuiSupport: false,
    lifecycleHooks: false
  };
  
  try {
    // Test for colored console
    if (typeof process !== 'undefined' && process.stdout?.isTTY) {
      features.coloredConsole = true;
    }
    
    // Test for event system (v2+)
    const { createScriptRunner } = await import('@glyphtek/scriptit');
    const runner = await createScriptRunner({});
    if (typeof runner.on === 'function') {
      features.eventSystem = true;
    }
    
    // Test for TUI support
    if (typeof runner.runTUI === 'function') {
      features.tuiSupport = true;
    }
    
    // Test for lifecycle hooks
    features.lifecycleHooks = true; // Available in all versions
    
  } catch (error) {
    // Feature not available
  }
  
  return features;
}

// Usage in scripts
export async function execute(context) {
  const console = context.console || global.console;
  const features = await detectFeatures();
  
  if (features.coloredConsole) {
    console.info('Using colored console output');
  } else {
    console.log('Using standard console output');
  }
  
  return { features };
}

Version-Agnostic Scripts

Write scripts that work across multiple versions:

javascript
// version-agnostic.js
export const description = "Script that works across ScriptIt versions";

export async function execute(context) {
  // Safe console access (works in all versions)
  const console = context.console || global.console;
  
  // Safe temp directory access
  const tmpDir = context.tmpDir || context.tmpPath || './tmp';
  
  // Safe environment access
  const env = context.env || process.env;
  
  // Safe parameter access
  const params = context.params || {};
  
  // Safe logging (v2+ feature with fallback)
  const log = context.log || console.log;
  
  console.log('Script running on ScriptIt');
  log('Execution started');
  
  // Feature-specific code
  if (context.configPath) {
    console.log(`Config loaded from: ${context.configPath}`);
  }
  
  return {
    version: 'agnostic',
    tmpDir,
    hasConfigPath: !!context.configPath
  };
}

Testing Migration

Migration Test Suite

Create comprehensive tests for migration:

javascript
// migration.test.js
import { describe, test, expect, beforeAll } from 'bun:test';
import { createScriptRunner } from '@glyphtek/scriptit';

describe('Migration Tests', () => {
  let runner;
  
  beforeAll(async () => {
    runner = await createScriptRunner({
      scriptsDir: './test-scripts',
      tmpDir: './tmp/migration-test'
    });
  });
  
  test('should run v1 migrated script', async () => {
    const result = await runner.executeScript('migrated-v1-script.js');
    expect(result.success).toBe(true);
  });
  
  test('should handle v0 compatibility wrapper', async () => {
    const result = await runner.executeScript('v0-wrapped-script.js');
    expect(result.success).toBe(true);
  });
  
  test('should work with version-agnostic script', async () => {
    const result = await runner.executeScript('version-agnostic.js');
    expect(result.version).toBe('agnostic');
  });
});

Validation Scripts

Create scripts to validate migration success:

javascript
// validate-migration.js
export const description = "Validate migration from previous version";

export async function execute(context) {
  const console = context.console || global.console;
  const issues = [];
  
  console.info('🔍 Validating migration...');
  
  // Check configuration format
  try {
    if (context.configPath) {
      const config = await import(context.configPath);
      if (config.scriptsPath) {
        issues.push('Configuration still uses old "scriptsPath" property');
      }
      if (config.tempPath) {
        issues.push('Configuration still uses old "tempPath" property');
      }
    }
  } catch (error) {
    issues.push(`Configuration validation failed: ${error.message}`);
  }
  
  // Check script structure
  const fs = await import('fs/promises');
  const glob = await import('glob');
  
  try {
    const scriptFiles = glob.sync('./scripts/**/*.js');
    
    for (const file of scriptFiles) {
      const content = await fs.readFile(file, 'utf8');
      
      if (content.includes('module.exports')) {
        issues.push(`Script ${file} still uses CommonJS exports`);
      }
      
      if (!content.includes('export') && !content.includes('//')) {
        issues.push(`Script ${file} may not have proper exports`);
      }
    }
  } catch (error) {
    issues.push(`Script validation failed: ${error.message}`);
  }
  
  // Report results
  if (issues.length === 0) {
    console.log('✅ Migration validation passed');
    return { valid: true };
  } else {
    console.warn('⚠️  Migration validation found issues:');
    issues.forEach(issue => console.warn(`  - ${issue}`));
    return { valid: false, issues };
  }
}

Rollback Procedures

Automated Rollback

Create rollback scripts for failed migrations:

javascript
// rollback-migration.js
export const description = "Rollback failed migration";

export async function execute(context) {
  const console = context.console || global.console;
  const fs = await import('fs/promises');
  const glob = await import('glob');
  
  console.info('🔄 Starting migration rollback...');
  
  // Restore configuration backup
  try {
    const configBackup = './scriptit.config.js.backup';
    const configExists = await fs.access(configBackup).then(() => true).catch(() => false);
    
    if (configExists) {
      await fs.copyFile(configBackup, './scriptit.config.js');
      console.log('✅ Configuration restored from backup');
    }
  } catch (error) {
    console.warn('⚠️  Could not restore configuration:', error.message);
  }
  
  // Restore script backups
  const backupFiles = glob.sync('./scripts/**/*.v0.backup');
  let restoredCount = 0;
  
  for (const backupFile of backupFiles) {
    try {
      const originalFile = backupFile.replace('.v0.backup', '');
      await fs.copyFile(backupFile, originalFile);
      restoredCount++;
    } catch (error) {
      console.warn(`⚠️  Could not restore ${backupFile}:`, error.message);
    }
  }
  
  console.log(`✅ Restored ${restoredCount} script files`);
  
  // Downgrade package version
  console.info('📦 To complete rollback, run:');
  console.info('npm install @glyphtek/scriptit@<previous-version>');
  
  return { 
    configRestored: true, 
    scriptsRestored: restoredCount,
    backupFiles: backupFiles.length
  };
}

Manual Rollback Steps

  1. Stop all ScriptIt processes
  2. Restore configuration backup
  3. Restore script backups
  4. Downgrade package version
  5. Validate rollback
bash
# Manual rollback commands
cp scriptit.config.js.backup scriptit.config.js
find scripts -name "*.backup" -exec sh -c 'cp "$1" "${1%.backup}"' _ {} \;
npm install @glyphtek/scriptit@1.0.0
scriptit exec validate-rollback.js

Best Practices for Migration

1. Always Backup

bash
# Create comprehensive backup before migration
tar -czf scriptit-backup-$(date +%Y%m%d).tar.gz \
  scripts/ \
  scriptit.config.js \
  package.json \
  .env*

2. Test in Staging

bash
# Test migration in staging environment first
NODE_ENV=staging scriptit exec validate-migration.js
NODE_ENV=staging npm test

3. Gradual Rollout

javascript
// Feature flag for gradual migration
const MIGRATION_PERCENTAGE = parseInt(process.env.MIGRATION_PERCENTAGE || '0');

export async function execute(context) {
  const useNewFeature = Math.random() * 100 < MIGRATION_PERCENTAGE;
  
  if (useNewFeature) {
    return await newImplementation(context);
  } else {
    return await legacyImplementation(context);
  }
}

4. Monitor After Migration

javascript
// monitoring-script.js
export async function execute(context) {
  const console = context.console || global.console;
  
  // Monitor key metrics after migration
  const metrics = {
    scriptExecutions: 0,
    errors: 0,
    performance: []
  };
  
  // Implementation...
  
  console.log('📊 Post-migration metrics:', metrics);
  return metrics;
}

Getting Help

Migration Support

  • Documentation: Check version-specific documentation
  • Community: Join ScriptIt community forums
  • Issues: Report migration issues on GitHub
  • Professional Support: Contact for enterprise migration assistance

Common Migration Issues

  1. Configuration format errors
  2. Script export format incompatibility
  3. Runtime detection changes
  4. Dependency version conflicts

Migration Checklist

  • [ ] Backup current installation
  • [ ] Read migration guide for your version
  • [ ] Test migration in staging
  • [ ] Update configuration files
  • [ ] Migrate scripts incrementally
  • [ ] Run validation tests
  • [ ] Monitor post-migration
  • [ ] Document any custom changes

Released under the MIT License.