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 = plugins.smartfile.fs.fileExistsSync(this.filePaths.metaJson); if (!metaFileExists) { throw new Error(`meta file does not exist at ${this.filePaths.metaJson}`); } this.metaRepoData = plugins.smartfile.fs.toObjectSync(this.filePaths.metaJson); } /** * 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 plugins.smartfile.memory.toFsSync( JSON.stringify(this.metaRepoData, null, 2), this.filePaths.metaJson, ); // write .gitignore to disk plugins.smartfile.memory.toFsSync(await this.generateGitignore(), this.filePaths.gitIgnore); } /** * 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 projects = plugins.smartfile.fs.toObjectSync(this.filePaths.metaJson).projects; const preExistingFolders = plugins.smartfile.fs.listFoldersSync(this.cwd); 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`) : 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 folders = await plugins.smartfile.fs.listFolders(this.cwd); 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.smartfile.fs.fileExists(this.filePaths.metaJson); if (!fileExists) { await plugins.smartfile.memory.toFs( JSON.stringify({ projects: {}, }), this.filePaths.metaJson, ); logger.log(`success`, `created a new .meta.json in directory ${this.cwd}`); await plugins.smartfile.memory.toFs( JSON.stringify({ name: this.dirName, version: '1.0.0', }), this.filePaths.packageJson, ); 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.smartfile.fs.remove(plugins.path.join(paths.cwd, projectNameArg)); await this.updateLocalRepos(); } }