import { exec } from 'child_process'; import * as fs from 'fs'; import { createGunzip } from 'node:zlib'; import * as path from 'path'; import { simpleGit } from 'simple-git'; import { extract } from 'tar'; import { getCommitId } from './helpers/git'; import * as logger from './helpers/logger'; import { CreateSolutionInput } from './helpers/types'; // eslint-disable-next-line @typescript-eslint/explicit-function-return-type const getOra = async () => (await import('ora')).default; // Add helper function for sanitizing the package name const sanitizePackageName = (name: string): string => { // Lowercase and replace invalid characters with a hyphen. return name .trim() .toLowerCase() .replace(/[^a-z0-9-_]/g, '-') .replace(/-+/g, '-') .replace(/^-|-$/g, ''); }; export const createSolution = async ( options: CreateSolutionInput, ): Promise => { logger.debug('CreateSolution options', JSON.stringify(options)); await downloadAndUnzipTemplate(options); process.chdir(options.target); await adjustSolution(options); if (!options.noGit) { await initializeGit(options); } if (!options.noInstall) { await installDependencies(options); } }; const downloadAndUnzipTemplate = async ( options: CreateSolutionInput, ): Promise => { const downloadOra = (await getOra())('Downloading the template...'); downloadOra.start(); const url = `https://github.com/Axinom/mosaic-media-template/archive/refs/${options.gitReferenceType}/${options.gitReference}.tar.gz`; const response = await fetch(url); if (response.ok) { downloadOra.succeed('Template downloaded'); await extractArchive(response, options); } else { downloadOra.fail( `Failed to download the template ('${response.statusText}')`, ); logger.debug( `Failed to download the template from ${url}`, JSON.stringify(response), ); throw new Error(response.statusText); } }; const extractArchive = async ( response: Response, options: CreateSolutionInput, ): Promise => { const unzipOra = (await getOra())('Unzipping the template...'); const buffer = await response.arrayBuffer(); return new Promise((resolve, reject) => { const targetPath = path.resolve(options.target); fs.mkdirSync(targetPath, { recursive: true }); const gunzip = createGunzip(); const extractor = extract({ cwd: targetPath, strip: 1 }); gunzip.pipe(extractor); gunzip.on('error', (err) => { unzipOra.fail('Failed to unzip the template'); logger.error(err); reject(err); }); extractor.on('error', (err) => { unzipOra.fail('Failed to extract the template'); logger.error(err); reject(err); }); extractor.on('finish', () => { unzipOra.succeed('Template extracted'); resolve(); }); gunzip.write(Buffer.from(buffer)); gunzip.end(); }); }; const adjustSolution = async (options: CreateSolutionInput): Promise => { const adjustOra = (await getOra())('Adjusting package.json...').start(); try { const packageJsonPath = path.join(process.cwd(), 'package.json'); const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')); // Use sanitized package name packageJson.name = sanitizePackageName(options.projectName); packageJson.mosaic = { baseVersion: `${await getCommitId(options)} (${options.gitReference})`, }; fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2)); adjustOra.succeed('package.json adjusted'); } catch (error) { adjustOra.warn('Failed to adjust package.json'); logger.error(error); } }; const initializeGit = async (_options: CreateSolutionInput): Promise => { const gitOra = (await getOra())('Initializing git repository...').start(); try { const git = simpleGit(); await git.init().add('./*').commit('Initial commit'); gitOra.succeed('Git repository initialized'); } catch (error) { gitOra.warn( 'Failed to initialize git repository. Make sure git is installed.', ); logger.error(error); } }; const installDependencies = async ( _options: CreateSolutionInput, ): Promise => { const installOra = (await getOra())('Installing dependencies...').start(); try { // Install dependencies await new Promise((resolve, reject) => { const childProcess = exec('yarn install'); childProcess.stdout?.on('data', (data) => { installOra.text = `Installing dependencies: ${data.toString()}`; logger.debug(data); }); childProcess.stderr?.on('data', (data) => { logger.debug(data); }); childProcess.on('close', (code) => { if (code === 0) { installOra.succeed('Dependencies installed'); resolve(); } else { if (code === 127) { installOra.warn( `Failed to install dependencies. Please make sure yarn is installed.`, ); resolve(); } else { reject(new Error(`Command exited with code ${code}`)); } } }); }); } catch (error) { installOra.warn('Failed to install dependencies'); logger.error(error); } };