import path from 'node:path' import { fileURLToPath } from 'node:url' import fs from 'node:fs' import replaceInFileDefault from 'replace-in-file' import date from 'date-and-time' import { copyDirectory } from '../utils/copyDirectory.js' const { replaceInFile } = replaceInFileDefault const TEMPLATE_PATH = fileURLToPath(new URL('../../templates', import.meta.url)) const TEMPLATE_DOCKER_PATH = path.join(TEMPLATE_PATH, 'docker') const TEMPLATE_CHALLENGE_PATH = path.join(TEMPLATE_PATH, 'challenge') const TEMPLATE_SOLUTION_PATH = path.join(TEMPLATE_PATH, 'solution') const TEMPLATE_SOLUTION_BASE_PATH = path.join(TEMPLATE_SOLUTION_PATH, 'base') export interface TemplateDockerOptions { programmingLanguage: string destination: string } export interface TemplateChallengeOptions { name: string githubUser?: string destination: string } export interface TemplateSolutionOptions { challengeName: string programmingLanguageName: string name: string githubUser?: string destination: string } export interface ReplaceInDestinationOptions { destination: string name: string description: string } class Template { private getDescription(githubUser?: string): string { const dateString = date.format(new Date(), 'D MMMM Y', true) let description = 'Created' if (githubUser != null) { description += ` by [@${githubUser}](https://github.com/${githubUser})` } description += ` on ${dateString}.` return description } private async replaceInDestination( options: ReplaceInDestinationOptions ): Promise { const { name, description, destination } = options const readmePath = path.join(destination, 'README.md') await replaceInFile({ files: [readmePath], from: /{{ name }}/g, to: name }) await replaceInFile({ files: [readmePath], from: /{{ description }}/g, to: description }) } public async docker(options: TemplateDockerOptions): Promise { const { programmingLanguage, destination } = options const sourcePath = path.join(TEMPLATE_DOCKER_PATH, programmingLanguage) await copyDirectory(sourcePath, destination) } public async solution(options: TemplateSolutionOptions): Promise { const { destination, githubUser, name, challengeName, programmingLanguageName } = options const templateLanguagePath = path.join( TEMPLATE_SOLUTION_PATH, programmingLanguageName ) await this.verifySupportedProgrammingLanguage(programmingLanguageName) await fs.promises.mkdir(destination, { recursive: true }) await copyDirectory(templateLanguagePath, destination) await copyDirectory(TEMPLATE_SOLUTION_BASE_PATH, destination) await this.replaceInDestination({ name: `${challengeName}/${programmingLanguageName}/${name}`, description: this.getDescription(githubUser), destination }) } public async challenge(options: TemplateChallengeOptions): Promise { const { destination, githubUser, name } = options await copyDirectory(TEMPLATE_CHALLENGE_PATH, destination) await this.replaceInDestination({ name, description: this.getDescription(githubUser), destination }) } public async getProgrammingLanguages(): Promise { const languages = await fs.promises.readdir(TEMPLATE_SOLUTION_PATH) return languages.filter((language) => { return language !== 'base' }) } public async verifySupportedProgrammingLanguage( language: string ): Promise { const languages = await this.getProgrammingLanguages() if (!languages.includes(language)) { throw new Error('This programming language is not supported yet.') } } } export const template = new Template()