lucy-cli
Version:
Lucy Framework for WIX Studio Editor
376 lines (321 loc) • 14.8 kB
text/typescript
import { dirname } from 'path';
import { fileURLToPath } from 'url';
import { existsSync } from 'fs';
import chalk from 'chalk';
import settings from './settings.json' with { type: "json" };;
import projectPackageJSON from '../package.json' with { type: "json" };;
import { join } from 'path';
import fs from 'fs/promises';
import { init } from './init.js';
import { sync } from './sync.js';
import { runGulp, installPackages, killAllProcesses, cleanupWatchers, createTemplateFolder, updateLucyConfigFromPackageJson } from './helpers.js';
import { prepare } from './prepare.js';
import { spawn, spawnSync } from 'child_process';
import os from 'os';
export type ModulesSettings = {
packageRoot: string;
targetFolder: string;
args: string[];
settings: LucySettings;
}
export type LucySettings = {
modules: {
[libName: string]: {
url: string;
branch: string;
path?: string;
noCompile?: boolean;
};
};
wixSettings: {
compilerOptions: {
composite: boolean;
noEmit: boolean;
lib: string[];
jsx: string;
};
exclude: string[];
};
initialized: boolean;
wixPackages: {
[packageName: string]: string;
};
devPackages: {
[packageName: string]: string;
};
scripts: {
[commandName: string]: string;
};
};
export type ModuleSettings = {
packageRoot: string;
targetFolder: string;
args: string[];
wixConfigPath: string;
lucyConfigPath: string;
packageJsonPath: string;
settings: LucySettings;
lockVersion: boolean;
force: boolean;
veloConfigName: string;
}
export type ProjectSettings = {
// packages?: Record<string, string>;
modules?: Record<string, string>;
lucySettings?: LucySettings;
packageJSON?: Record<string, any>;
}
export const orange = chalk.hex('#FFA500');
export const blue = chalk.blueBright;
export const green = chalk.greenBright;
export const red = chalk.redBright;
export const yellow = chalk.yellow;
export const magenta = chalk.magentaBright;
// eslint-disable-next-line @typescript-eslint/naming-convention
const __filename = fileURLToPath(import.meta.url);
// eslint-disable-next-line @typescript-eslint/naming-convention
const __dirname = dirname(__filename);
// const cwd = process.cwd();
// const command = `watchman watch-del '${cwd}'`;
// killAllProcesses('@wix/cli/bin/wix.cjs'); // Matches processes running the Wix CLI
// killAllProcesses('wix:dev');
process.on('exit', (code) => {
killAllProcesses('@wix/cli/bin/wix.cjs'); // Matches processes running the Wix CLI
killAllProcesses('wix:dev');
cleanupWatchers();
console.log(`🚪 ${magenta.underline('Process exiting with code:')} ${orange(code)}`);
});
process.on('SIGINT', () => {
console.log(`🐕 ${green.underline('Received Ctrl+C (SIGINT), cleaning up...')}`);
killAllProcesses('@wix/cli/bin/wix.cjs'); // Matches processes running the Wix CLI
killAllProcesses('wix:dev');
cleanupWatchers();
process.exit(); // Exit explicitly after handling
});
process.on('SIGTERM', () => {
console.log(`🛑 ${red.underline('Received termination signal (SIGTERM), cleaning up...')}`);
killAllProcesses('@wix/cli/bin/wix.cjs'); // Matches processes running the Wix CLI
killAllProcesses('wix:dev');
cleanupWatchers();
process.exit(); // Exit explicitly after handling
});
process.on('uncaughtException', (error) => {
console.error(`💥 ${red.underline('Uncaught Exception:')}`, error);
killAllProcesses('@wix/cli/bin/wix.cjs'); // Matches processes running the Wix CLI
killAllProcesses('wix:dev');
cleanupWatchers();
process.exit(1); // Exit with an error code
});
process.on('unhandledRejection', (reason, promise) => {
console.error(`🚨 ${yellow.underline('Unhandled Rejection at:')} ${orange(promise)}`);
console.error(`🚨 ${red.underline('Reason:')} ${reason}`); cleanupWatchers();
killAllProcesses('@wix/cli/bin/wix.cjs'); // Matches processes running the Wix CLI
killAllProcesses('wix:dev');
cleanupWatchers();
process.exit(1); // Exit with an error code
});
/**
* Main function
* @returns {Promise<void>}
*/
async function main(): Promise<void> {
// INFO: Module settings
const moduleSettings: ModuleSettings = {
packageRoot: dirname(__dirname),
targetFolder: process.cwd(),
args: process.argv.slice(2),
settings,
wixConfigPath: join(process.cwd(), 'wix.config.json'),
lucyConfigPath: join(process.cwd(), 'lucy.json'),
packageJsonPath: join(process.cwd(), 'package.json'),
force: false,
lockVersion: false,
veloConfigName: 'config.json'
}
let projectSettings: ProjectSettings = {};
if(moduleSettings.args.includes('version') || moduleSettings.args.includes('-v')){
console.log("🐾" + blue.bold(` => ${projectPackageJSON.version}`));
return;
}
if (moduleSettings.args.includes('templates')) {
const templatesPath = join(os.homedir(), '.lucy-cli');
if (!existsSync(templatesPath)) {
console.log((`💩 ${red.underline.bold("=> Lucy templates folder not found at =>")} ${orange(templatesPath)}`));
console.log(chalk.yellow('🐕 Creating templates folder with default template...'));
await createTemplateFolder(moduleSettings);
}
console.log(`🐕 ${blue.underline('Opening templates folder at:')} ${orange(templatesPath)}`);
let command: string;
switch (process.platform) {
case 'darwin': command = 'open'; break;
case 'win32': command = 'start'; break;
default: command = 'xdg-open'; break;
}
const child = spawn(command, [templatesPath], { detached: true, stdio: 'ignore' });
child.on('error', (err) => {
console.error(`💩 ${red.underline.bold('Failed to open folder:')} ${orange(err.message)}`);
});
child.unref();
return;
}
// Run velo sync
if(moduleSettings.args.includes('velo-sync')){
await sync(moduleSettings, projectSettings);
return;
}
if(moduleSettings.args.includes('help') || moduleSettings.args.includes('-h')){
console.log("🦮 " + green.underline.bold(' => Lucy CLI Help'));
console.log("Usage: lucy-cli <command> [options]");
console.log("\nCommands:");
console.log("🦮 " + magenta.bold('init') + " : Initializes caontaining a WIX project to enable full TS support");
console.log("🦮 " + magenta.bold('dev') + " : Starts the development environment. This includes setting up any required services for local development.");
console.log("🦮 " + magenta.bold('build-prod') + " : Builds the project in production mode, optimizing files for deployment.");
console.log("🦮 " + magenta.bold('prepare') + " : Prepares the project by installing packages & initializing git modules, configured in lucy.json");
console.log("🦮 " + magenta.bold('velo-sync') + " : Synchronizes wix collections(velo-sync -h for help)");
console.log("🦮 " + magenta.bold('install') + " : Installs all Wix npm packages listed in the 'lucy.json' file in the project directory.");
console.log("🦮 " + magenta.bold('fix') + " : Runs a fix command to resolve common issues in development or production settings.");
console.log("🦮 " + magenta.bold('docs') + " : Generates documentation for the project.");
console.log("🦮 " + magenta.bold('cypress') + " : Starts the cypress test runner.");
console.log("🦮 " + magenta.bold('templates') + " : Opens the Lucy CLI templates folder.");
console.log("🦮 " + magenta.bold('sync-pkgs') + " : Syncs dependencies from package.json to lucy.json.");
console.log("🦮 " + magenta.bold('e2e') + " : Starts the cypress test runner in CI mode. first argument is the key second is the build id <e2e <somekey <someID>");
console.log("\nOptions:");
console.log("🦮 " + magenta.bold('-h, help') + " : Displays this help message.");
console.log("🦮 " + magenta.bold('-v, version') + " : Displays the current version of Lucy CLI as defined in the project’s package.json.");
console.log("🦮 " + magenta.bold('-f, force') + " : Forces specific commands to execute even if they may lead to potential issues.");
console.log(" Used for functions like deleting obsolete pages or initializing missing components.");
console.log("🦮 " + magenta.bold('-l') + " : Locks package versions to those specified in the configuration file lucy.json");
console.log("\nExamples:");
console.log("🦮 " + magenta.bold('lucy-cli init') + " : Initializes a new Wix project.");
console.log("🦮 " + magenta.bold('lucy-cli dev') + " : Starts the development environment.");
console.log("🦮 " + magenta.bold('lucy-cli sync') + " : Synchronizes database and settings.");
console.log("🦮 " + magenta.bold('lucy-cli install') + " : Installs all Wix npm packages from 'lucy.json'.");
console.log("🦮 " + magenta.bold('lucy-cli dev -f') + " : Starts the dev environment with forced settings.");
console.log("🦮 " + magenta.bold('lucy-cli install -l') + " : Installs Wix npm packages, respecting locked versions specified in the configuration.");
return;
}
if (!existsSync(moduleSettings.wixConfigPath)) {
console.log((`💩 ${red.underline.bold("=> This is not a WIX project =>")} ${orange(moduleSettings.targetFolder)}`));
return;
}
//INFO: Collect project settings
if(moduleSettings.args.includes('-f')) moduleSettings.force = true;
if(moduleSettings.args.includes('-l')) moduleSettings.lockVersion = true;
if(existsSync(moduleSettings.packageJsonPath)) {
const packageJSONraw = await fs.readFile(join(moduleSettings.packageJsonPath), 'utf8');
try {
projectSettings.packageJSON = JSON.parse(packageJSONraw);
if(moduleSettings.force) {
console.log("❗️" + red.underline(' => Forcing'));
moduleSettings.force = true;
}
} catch (parseError) {
console.log((`💩 ${red.underline.bold("=> Error parsing package.json =>")} ${orange(parseError)}`));
return;
}
}
if(existsSync(moduleSettings.lucyConfigPath)) {
try {
const data = await fs.readFile(moduleSettings.lucyConfigPath, 'utf8');
projectSettings.lucySettings = JSON.parse(data);
} catch (parseError) {
console.log((`💩 ${red.underline.bold("=> Error parsing Lucy.json =>")} ${orange(parseError)}`));
}
} else {
if(!moduleSettings.args.includes('init')) {
return console.log(yellow.underline.bold('🐶 => Project not Initialized! Please initialize using "lucy-cli init"'));
};
}
if(!projectSettings.lucySettings?.initialized) {
if(!moduleSettings.args.includes('init')) {
return console.log(yellow.underline.bold('🐶 => Project not Initialized! Please initialize using "lucy-cli init"'));
}
}
if(moduleSettings.args.includes('-l')) moduleSettings.lockVersion = true;
console.log("🐕" + magenta.underline(' => Lucy CLI => RUNNING: ' + orange('Press Ctrl+C to stop.')));
// INFO: Run commands
if(moduleSettings.args.includes('init')){
if(projectSettings.lucySettings?.initialized && !moduleSettings.force) {
console.log((`💩 ${red.underline.bold("=> This project is already initialized =>")} ${orange(moduleSettings.targetFolder)}`));
console.log("🐕" + magenta.underline(' => Use -f to force initialization'));
return;
}
console.log("🐕" + magenta.underline(' => Initializing project'));
init(moduleSettings, projectSettings);
return;
}
if(moduleSettings.args.includes('docs')){
const res = spawnSync('yarn docs', { shell: true, stdio: 'inherit' });
if (res.error) {
return console.log((`💩 ${red.underline.bold("=> Failed to Docs generated => ")} ${orange(res.error.message)}`));
}
return console.log("🐕" + blue.underline(` => Docs generated!`));
}
if(moduleSettings.args.includes('cypress')){
const res = spawnSync('yarn cypress', { shell: true, stdio: 'inherit' });
if (res.error) {
return console.log((`💩 ${red.underline.bold("=> Failed to start cypress => ")} ${orange(res.error.message)}`));
}
return console.log("🐕" + blue.underline(` => Started Cypress`));
}
if (moduleSettings.args.includes('e2e')) {
// Extract arguments
const e2eIndex = moduleSettings.args.indexOf('e2e');
const key = moduleSettings.args[e2eIndex + 1];
const buildId = moduleSettings.args[e2eIndex + 2];
// Validate that both arguments are provided
if (!key && !buildId) {
console.log(`💩 ${red.underline.bold("=> Missing required arguments:")} ${orange("key")} and ${orange("build ID")}`);
process.exit(1);
}
// Run Cypress with the provided arguments
const res = spawnSync(`yarn e2e --key ${key} --ci-build-id ${buildId}`, { shell: true, stdio: 'inherit' });
if (res.error) {
console.log(`💩 ${red.underline.bold("=> Failed to start Cypress =>")} ${orange(res.error.message)}`);
process.exit(1);
}
return console.log("🐕 " + blue.underline(`=> Started Cypress successfully`));
}
if(moduleSettings.args.includes('prepare')){
await prepare( moduleSettings, projectSettings);
return;
}
if(moduleSettings.args.includes('install')){
if(!projectSettings.lucySettings?.initialized) {
console.log((`💩 ${red.underline.bold("=> This project is not initialized =>")} ${orange(moduleSettings.targetFolder)}`));
console.log("🐕" + magenta.underline(' => Use init to initialize'));
return;
}
await installPackages(projectSettings.lucySettings.wixPackages, projectSettings.lucySettings.devPackages, moduleSettings.targetFolder, moduleSettings.lockVersion);
return;
}
if(moduleSettings.args.includes('dev')){
runGulp(moduleSettings, projectSettings, 'dev');
return;
}
if(moduleSettings.args.includes('build-prod')){
runGulp(moduleSettings, projectSettings, 'build-prod');
return;
}
if(moduleSettings.args.includes('fix')){
runGulp(moduleSettings, projectSettings, 'fix-wix');
return;
}
if(moduleSettings.args.includes('sync-pkgs')){
console.log("🐕" + magenta.underline(' => Syncing package.json with lucy.json'));
if (!existsSync(moduleSettings.packageJsonPath)) {
console.log((`💩 ${red.underline.bold("=> package.json not found at =>")} ${orange(moduleSettings.packageJsonPath)}`));
return;
}
if (!existsSync(moduleSettings.lucyConfigPath)) {
console.log((`💩 ${red.underline.bold("=> lucy.json not found at =>")} ${orange(moduleSettings.lucyConfigPath)}`));
return;
}
await updateLucyConfigFromPackageJson(moduleSettings.packageJsonPath, moduleSettings.lucyConfigPath);
return;
}
console.log("🐕" + blue.underline.bold(' => Running dev'));
runGulp(moduleSettings, projectSettings, 'dev');
}
main();