@glyphtek/scriptit
Version:
A cross-runtime CLI and library for running scripts with environment management, TUI, and support for lambda functions. Optimized for Bun with compatibility for Node.js and Deno.
145 lines • 7.75 kB
JavaScript
// src/lib.ts (or src/index.ts - your library's main export file)
import EventEmitter from "node:events"; // For the 'on' method
import { existsSync as pathExistsSync } from "node:fs";
import path from "node:path";
import { getScriptFiles } from "./common/utils/index.js"; // Import getScriptFiles for script discovery
import { // Type for the executor
createScriptExecutorInstance, // To get an executor instance
loadEnvironment, loadRunnerConfigInternal, // Renamed to avoid conflict if you also export a user-facing loadConfig
} from "./core/index.js";
import { runBlessedTUI } from "./ui/index.js"; // Import from ui/index.ts
// Default paths if not specified
const DEFAULT_CONFIG_FILE_PATH = "runner.config.js";
export async function createScriptRunner(options = {}) {
const emitter = new EventEmitter();
// Handle working directory change if specified
if (options.workingDirectory) {
const targetDir = path.resolve(options.workingDirectory);
if (!pathExistsSync(targetDir)) {
throw new Error(`Working directory does not exist: ${targetDir}`);
}
process.chdir(targetDir);
}
// 1. Load Configuration
// The internal config loader might take slightly different or more structured options
const effectiveConfig = await loadRunnerConfigInternal({
configPath: options.configFile, // Use explicit configFile option
cliOptions: {
// Pass other relevant options as if they were CLI overrides
scriptsDir: options.scriptsDir,
tmpDir: options.tmpDir,
// envFiles from options.envFiles would be merged by loadRunnerConfigInternal
},
defaultConfigValues: {
// Base defaults
envFiles: options.envFiles || [".env"], // Allow override of default envFiles
defaultParams: options.defaultParams || {},
excludePatterns: options.excludePatterns || [], // Include exclude patterns in config
},
});
// 2. Load Environment Variables
// `options.initialEnv` takes highest precedence programmatically
const finalEnvironment = loadEnvironment(effectiveConfig.envFiles.map((f) => path.resolve(f)), // Use resolved envFiles from effectiveConfig
options.initialEnv, effectiveConfig.defaultParams);
// 3. Create Script Executor Instance
// The executor is stateless regarding which script to run, it just knows how to run one
// given a path and a context.
const scriptExecutor = createScriptExecutorInstance({
consoleInterception: {
enabled: options.consoleInterception?.enabled ?? true, // Enable by default
includeLevel: options.consoleInterception?.includeLevel ?? false,
preserveOriginal: options.consoleInterception?.preserveOriginal ?? false,
useColors: options.consoleInterception?.useColors ?? true,
},
});
const instance = {
config: effectiveConfig,
environment: finalEnvironment,
// Method to list available scripts (respecting exclude patterns)
listScripts: async () => {
const scriptPaths = await getScriptFiles(effectiveConfig.scriptsDir, effectiveConfig.excludePatterns);
// Return relative paths from scriptsDir for easier display/usage
return scriptPaths.map((fullPath) => path.relative(effectiveConfig.scriptsDir, fullPath));
},
executeScript: async (scriptPath, executionParams, customLogger) => {
const params = executionParams || Object.create(null);
const absoluteScriptPath = path.isAbsolute(scriptPath)
? scriptPath
: path.resolve(effectiveConfig.scriptsDir, scriptPath);
if (!pathExistsSync(absoluteScriptPath)) {
const err = new Error(`Script file not found: ${absoluteScriptPath}`);
emitter.emit("script:error", absoluteScriptPath, err);
throw err;
}
emitter.emit("script:beforeExecute", absoluteScriptPath, params);
// Build context for this specific execution
const scriptSpecificEnv = {
...finalEnvironment,
...(params.env || {}),
};
const contextLog = customLogger ||
((msg) => {
// Default log for programmatic execution if no custom logger
console.log(`[ScriptRunner Lib] [${path.basename(absoluteScriptPath)}] ${msg}`);
emitter.emit("script:log", absoluteScriptPath, msg);
});
const context = {
env: scriptSpecificEnv,
tmpDir: effectiveConfig.tmpDir,
configPath: effectiveConfig.loadedConfigPath,
log: contextLog,
params: params.params ||
{}, // User-defined params for the script
// Spread other potential context items defined in RunnerConfig or executionParams
...(effectiveConfig.defaultParams || {}), // Already interpolated when finalEnvironment was created
...(params.contextOverrides || {}), // Allow deep override of context
};
try {
const result = await scriptExecutor.run(absoluteScriptPath, context);
const typedResult = result;
emitter.emit("script:afterExecute", absoluteScriptPath, typedResult);
return typedResult;
}
catch (error) {
emitter.emit("script:error", absoluteScriptPath, error);
throw error; // Re-throw for the caller to handle
}
},
runTUI: async () => {
emitter.emit("tui:beforeStart");
// The TUI needs the base config to find scripts and display info.
// It also needs the fully resolved initial environment.
// Values for the TUI's config display panel
const configDisplayValues = {
scriptsDir: path.relative(process.cwd(), effectiveConfig.scriptsDir),
tmpDir: path.relative(process.cwd(), effectiveConfig.tmpDir),
envFiles: effectiveConfig.envFiles,
excludePatterns: effectiveConfig.excludePatterns || [], // Add exclude patterns to display
defaultParamsCount: Object.keys(effectiveConfig.defaultParams || {})
.length,
loadedConfigPath: effectiveConfig.loadedConfigPath
? path.basename(effectiveConfig.loadedConfigPath)
: "Defaults / Not found",
};
// `runBlessedTUI` is the function from `src/tui/ui.ts`
// It will internally use `getScriptFiles` (which uses `config.scriptsDir`)
// and when a script is selected, it will build its own context or call
// a method similar to `instance.executeScript` but tailored for TUI logging.
// For simplicity, TUI can call its own internal execution logic that mirrors `executeScript`.
await runBlessedTUI(effectiveConfig.loadedConfigPath, effectiveConfig.scriptsDir, effectiveConfig.tmpDir, finalEnvironment, // Pass the fully resolved environment
configDisplayValues, emitter);
emitter.emit("tui:afterEnd");
},
on: (eventName, listener) => {
emitter.on(eventName, listener);
return instance;
},
off: (eventName, listener) => {
emitter.off(eventName, listener);
return instance;
},
emit: (eventName, ...args) => emitter.emit(eventName, ...args),
};
return instance;
}
//# sourceMappingURL=lib.js.map