import * as plugins from './meta.plugins.js'; import * as paths from '../paths.js'; import * as interfaces from './meta.interfaces.js'; import { logger } from '../gitzone.logging.js'; export class Meta { public cwd: string; public dirName: string; public filePaths: { metaJson: string; gitIgnore: string; packageJson: string; }; constructor(cwdArg: string) { this.cwd = cwdArg; this.dirName = plugins.path.basename(this.cwd); this.filePaths = { metaJson: plugins.path.join(this.cwd, './.meta.json'), gitIgnore: plugins.path.join(this.cwd, './.gitignore'), packageJson: plugins.path.join(this.cwd, './package.json'), }; } /** * the meta repo data */ public metaRepoData: interfaces.IMetaRepoData; public smartshellInstance = new plugins.smartshell.Smartshell({ executor: 'bash', }); /** * sorts the metaRepoData */ public async sortMetaRepoData() { const stringifiedMetadata = plugins.smartjson.stringify( this.metaRepoData, [], ); this.metaRepoData = plugins.smartjson.parse(stringifiedMetadata); } /** * reads the meta file from disk */ public async readDirectory() { await this.syncToRemote(true); logger.log('info', `reading directory`); const metaFileExists = await plugins.smartfs .file(this.filePaths.metaJson) .exists(); if (!metaFileExists) { throw new Error(`meta file does not exist at ${this.filePaths.metaJson}`); } const content = (await plugins.smartfs .file(this.filePaths.metaJson) .encoding('utf8') .read()) as string; this.metaRepoData = JSON.parse(content); } /** * generates the gitignore file and stores it on disk */ public async generateGitignore(): Promise { await this.sortMetaRepoData(); let gitignoreString = `# ignored repo directories\n`; gitignoreString += `.nogit/\n`; gitignoreString += `.pnpm-store/\n`; for (const key of Object.keys(this.metaRepoData.projects)) { gitignoreString = `${gitignoreString}${key}\n`; } return gitignoreString; } /** * write to disk */ public async writeToDisk() { // write .meta.json to disk await plugins.smartfs .file(this.filePaths.metaJson) .encoding('utf8') .write(JSON.stringify(this.metaRepoData, null, 2)); // write .gitignore to disk await plugins.smartfs .file(this.filePaths.gitIgnore) .encoding('utf8') .write(await this.generateGitignore()); } /** * push to remote */ public async syncToRemote(gitCleanArg = false) { logger.log('info', `syncing from origin master`); await this.smartshellInstance.exec( `cd ${this.cwd} && git pull origin master`, ); if (gitCleanArg) { logger.log('info', `cleaning the repository from old directories`); await this.smartshellInstance.exec(`cd ${this.cwd} && git clean -fd`); } logger.log('info', `syncing to remote origin master`); await this.smartshellInstance.exec( `cd ${this.cwd} && git push origin master`, ); } /** * update the locally cloned repositories */ public async updateLocalRepos() { await this.syncToRemote(); const metaContent = (await plugins.smartfs .file(this.filePaths.metaJson) .encoding('utf8') .read()) as string; const projects = JSON.parse(metaContent).projects; const entries = await plugins.smartfs.directory(this.cwd).list(); const preExistingFolders: string[] = []; for (const entry of entries) { try { const stats = await plugins.smartfs .file(plugins.path.join(this.cwd, entry.path)) .stat(); if (stats.isDirectory) { preExistingFolders.push(entry.name); } } catch { // Skip entries that can't be accessed } } for (const preExistingFolderArg of preExistingFolders) { if ( preExistingFolderArg !== '.git' && !Object.keys(projects).find((projectFolder) => projectFolder.startsWith(preExistingFolderArg), ) ) { const response = await plugins.smartinteraction.SmartInteract.getCliConfirmation( `Do you want to delete superfluous directory >>${preExistingFolderArg}<< ?`, true, ); if (response) { logger.log('warn', `Deleting >>${preExistingFolderArg}< is already cloned`); } else { missingRepos.push(key); } } catch { missingRepos.push(key); } } logger.log('info', `found ${missingRepos.length} missing repos`); for (const missingRepo of missingRepos) { await this.smartshellInstance.exec( `cd ${this.cwd} && git clone ${this.metaRepoData.projects[missingRepo]} ${missingRepo}`, ); } logger.log('info', `write changes to disk`); await this.writeToDisk(); logger.log('info', `persist changes with a git commit`); await this.smartshellInstance.exec( `cd ${this.cwd} && git add -A && git commit -m "updated structure"`, ); await this.syncToRemote(); // go recursive const listEntries = await plugins.smartfs.directory(this.cwd).list(); const folders: string[] = []; for (const entry of listEntries) { try { const stats = await plugins.smartfs .file(plugins.path.join(this.cwd, entry.path)) .stat(); if (stats.isDirectory) { folders.push(entry.name); } } catch { // Skip entries that can't be accessed } } const childMetaRepositories: string[] = []; for (const folder of folders) { logger.log('info', folder); } console.log('Recursion still needs to be implemented'); } // project manipulation /** * init a new meta project */ public async initProject() { await this.syncToRemote(true); const fileExists = await plugins.smartfs .file(this.filePaths.metaJson) .exists(); if (!fileExists) { await plugins.smartfs .file(this.filePaths.metaJson) .encoding('utf8') .write( JSON.stringify({ projects: {}, }), ); logger.log( `success`, `created a new .meta.json in directory ${this.cwd}`, ); await plugins.smartfs .file(this.filePaths.packageJson) .encoding('utf8') .write( JSON.stringify({ name: this.dirName, version: '1.0.0', }), ); logger.log( `success`, `created a new package.json in directory ${this.cwd}`, ); } else { logger.log( `error`, `directory ${this.cwd} already has a .metaJson file. Doing nothing.`, ); } await this.smartshellInstance.exec( `cd ${this.cwd} && git add -A && git commit -m "feat(project): init meta project for ${this.dirName}"`, ); await this.updateLocalRepos(); } /** * adds a project */ public async addProject(projectNameArg: string, gitUrlArg) { await this.readDirectory(); const existingProject = this.metaRepoData.projects[projectNameArg]; if (existingProject) { throw new Error( 'Project already exists! Please remove it first before adding it again.', ); } this.metaRepoData.projects[projectNameArg] = gitUrlArg; await this.sortMetaRepoData(); await this.writeToDisk(); await this.smartshellInstance.exec( `cd ${this.cwd} && git add -A && git commit -m "feat(project): add ${projectNameArg}"`, ); await this.updateLocalRepos(); } /** * removes a project */ public async removeProject(projectNameArg: string) { await this.readDirectory(); const existingProject = this.metaRepoData.projects[projectNameArg]; if (!existingProject) { logger.log( 'error', `Project ${projectNameArg} does not exist! So it cannot be removed`, ); return; } delete this.metaRepoData.projects[projectNameArg]; logger.log('info', 'removing project from .meta.json'); await this.sortMetaRepoData(); await this.writeToDisk(); logger.log('info', 'removing directory from cwd'); await plugins.smartfs .directory(plugins.path.join(paths.cwd, projectNameArg)) .recursive() .delete(); await this.updateLocalRepos(); } }