mirror of
https://github.com/theoludwig/programming-challenges.git
synced 2025-05-18 12:02:53 +02:00
chore: better Prettier config for easier reviews
This commit is contained in:
@ -1,10 +1,10 @@
|
||||
import fs from 'node:fs'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import fs from "node:fs"
|
||||
import { fileURLToPath } from "node:url"
|
||||
|
||||
import validateProjectName from 'validate-npm-package-name'
|
||||
import validateProjectName from "validate-npm-package-name"
|
||||
|
||||
import { isExistingPath } from '../utils/isExistingPath.js'
|
||||
import { template } from './Template.js'
|
||||
import { isExistingPath } from "../utils/isExistingPath.js"
|
||||
import { template } from "./Template.js"
|
||||
|
||||
export interface ChallengeOptions {
|
||||
name: string
|
||||
@ -15,7 +15,7 @@ export interface GenerateChallengeOptions extends ChallengeOptions {
|
||||
}
|
||||
|
||||
export class Challenge implements ChallengeOptions {
|
||||
public static BASE_URL = new URL('../../challenges/', import.meta.url)
|
||||
public static BASE_URL = new URL("../../challenges/", import.meta.url)
|
||||
public name: string
|
||||
public path: string
|
||||
|
||||
@ -33,7 +33,7 @@ export class Challenge implements ChallengeOptions {
|
||||
}
|
||||
|
||||
public static async generate(
|
||||
options: GenerateChallengeOptions
|
||||
options: GenerateChallengeOptions,
|
||||
): Promise<Challenge> {
|
||||
const { name, githubUser } = options
|
||||
const challenge = new Challenge({ name })
|
||||
@ -42,13 +42,13 @@ export class Challenge implements ChallengeOptions {
|
||||
}
|
||||
const isValidName = validateProjectName(name).validForNewPackages
|
||||
if (!isValidName) {
|
||||
throw new Error('Invalid challenge name.')
|
||||
throw new Error("Invalid challenge name.")
|
||||
}
|
||||
await fs.promises.mkdir(challenge.path)
|
||||
await template.challenge({
|
||||
destination: challenge.path,
|
||||
githubUser,
|
||||
name
|
||||
name,
|
||||
})
|
||||
return challenge
|
||||
}
|
||||
|
@ -1,18 +1,18 @@
|
||||
import { execaCommand } from 'execa'
|
||||
import ms from 'ms'
|
||||
import { execaCommand } from "execa"
|
||||
import ms from "ms"
|
||||
|
||||
import { parseCommandOutput } from '../utils/parseCommandOutput.js'
|
||||
import { parseCommandOutput } from "../utils/parseCommandOutput.js"
|
||||
|
||||
export interface DockerRunResult {
|
||||
stdout: string
|
||||
}
|
||||
|
||||
export class Docker {
|
||||
public static readonly CONTAINER_BASE_TAG = 'programming-challenges'
|
||||
public static readonly CONTAINER_BASE_TAG = "programming-challenges"
|
||||
public static readonly SIGSEGV_EXIT_CODE = 139
|
||||
public static readonly MAXIMUM_TIMEOUT = '1 minute'
|
||||
public static readonly MAXIMUM_TIMEOUT = "1 minute"
|
||||
public static readonly MAXIMUM_TIMEOUT_MILLISECONDS = ms(
|
||||
Docker.MAXIMUM_TIMEOUT
|
||||
Docker.MAXIMUM_TIMEOUT,
|
||||
)
|
||||
|
||||
public getContainerTag(id: string): string {
|
||||
@ -23,7 +23,7 @@ export class Docker {
|
||||
try {
|
||||
const { stdout } = await execaCommand(
|
||||
`docker images -q --filter=reference="${Docker.CONTAINER_BASE_TAG}:*"`,
|
||||
{ shell: true }
|
||||
{ shell: true },
|
||||
)
|
||||
return parseCommandOutput(stdout)
|
||||
} catch {
|
||||
@ -37,8 +37,8 @@ export class Docker {
|
||||
if (images.length === 0) {
|
||||
return
|
||||
}
|
||||
await execaCommand(`docker rmi -f ${images.join(' ')}`, {
|
||||
shell: true
|
||||
await execaCommand(`docker rmi -f ${images.join(" ")}`, {
|
||||
shell: true,
|
||||
})
|
||||
} catch {}
|
||||
}
|
||||
@ -55,8 +55,8 @@ export class Docker {
|
||||
const subprocess = execaCommand(
|
||||
`docker run --interactive --rm ${this.getContainerTag(id)}`,
|
||||
{
|
||||
input
|
||||
}
|
||||
input,
|
||||
},
|
||||
)
|
||||
try {
|
||||
const { stdout, stderr } = await subprocess
|
||||
@ -64,12 +64,12 @@ export class Docker {
|
||||
throw new Error(stderr)
|
||||
}
|
||||
return {
|
||||
stdout
|
||||
stdout,
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (error.exitCode === Docker.SIGSEGV_EXIT_CODE) {
|
||||
throw new Error(
|
||||
"Docker run failed.\nSIGSEGV indicates a segmentation fault (attempts to access a memory location that it's not allowed to access)."
|
||||
"Docker run failed.\nSIGSEGV indicates a segmentation fault (attempts to access a memory location that it's not allowed to access).",
|
||||
)
|
||||
}
|
||||
throw new Error(`Docker run failed.\n${error.message as string}`)
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { execaCommand } from 'execa'
|
||||
import { execaCommand } from "execa"
|
||||
|
||||
import { Challenge } from './Challenge.js'
|
||||
import { Solution } from './Solution.js'
|
||||
import { parseCommandOutput } from '../utils/parseCommandOutput.js'
|
||||
import { Challenge } from "./Challenge.js"
|
||||
import { Solution } from "./Solution.js"
|
||||
import { parseCommandOutput } from "../utils/parseCommandOutput.js"
|
||||
|
||||
const solutionsRegex =
|
||||
/challenges\/[\S\s]*\/solutions\/(c|cpp|cs|dart|java|javascript|python|rust|typescript)\/[\S\s]*\/(.*).(c|cpp|cs|dart|java|js|py|rs|ts)/
|
||||
@ -26,11 +26,11 @@ export class GitAffected implements GitAffectedOptions {
|
||||
|
||||
public async getFilesUsingBaseAndHead(
|
||||
base: string,
|
||||
head: string
|
||||
head: string,
|
||||
): Promise<string[]> {
|
||||
try {
|
||||
const { stdout } = await execaCommand(
|
||||
`git diff --name-only --relative ${base} ${head}`
|
||||
`git diff --name-only --relative ${base} ${head}`,
|
||||
)
|
||||
return parseCommandOutput(stdout)
|
||||
} catch {
|
||||
@ -39,13 +39,13 @@ export class GitAffected implements GitAffectedOptions {
|
||||
}
|
||||
|
||||
public async getUncommittedFiles(): Promise<string[]> {
|
||||
return await this.getFilesUsingBaseAndHead('HEAD', '.')
|
||||
return await this.getFilesUsingBaseAndHead("HEAD", ".")
|
||||
}
|
||||
|
||||
public async getLatestPushedCommit(): Promise<string> {
|
||||
const latestCommit = this.base != null ? '~1' : ''
|
||||
const latestCommit = this.base != null ? "~1" : ""
|
||||
const { stdout } = await execaCommand(
|
||||
`git rev-parse origin/master${latestCommit}`
|
||||
`git rev-parse origin/master${latestCommit}`,
|
||||
)
|
||||
return stdout
|
||||
}
|
||||
@ -53,12 +53,12 @@ export class GitAffected implements GitAffectedOptions {
|
||||
public async getUnpushedFiles(): Promise<string[]> {
|
||||
return await this.getFilesUsingBaseAndHead(
|
||||
await this.getLatestPushedCommit(),
|
||||
'.'
|
||||
".",
|
||||
)
|
||||
}
|
||||
|
||||
public async getAffectedSolutionsFromFiles(
|
||||
files: string[]
|
||||
files: string[],
|
||||
): Promise<Solution[]> {
|
||||
const affectedSolutionsPaths = files.filter((filePath) => {
|
||||
return solutionsRegex.test(filePath)
|
||||
@ -68,10 +68,10 @@ export class GitAffected implements GitAffectedOptions {
|
||||
})
|
||||
const affectedLanguages = affectedDockerPaths.map((filePath) => {
|
||||
const [, , programmingLanguageName] = filePath
|
||||
.replaceAll('\\', '/')
|
||||
.split('/')
|
||||
.replaceAll("\\", "/")
|
||||
.split("/")
|
||||
if (programmingLanguageName == null) {
|
||||
throw new Error('programmingLanguageName is null')
|
||||
throw new Error("programmingLanguageName is null")
|
||||
}
|
||||
return programmingLanguageName
|
||||
})
|
||||
@ -80,19 +80,18 @@ export class GitAffected implements GitAffectedOptions {
|
||||
})
|
||||
const affectedChallengesFromInputOutput = affectedInputOutput.map(
|
||||
(filePath) => {
|
||||
const [, challengeName] = filePath.replaceAll('\\', '/').split('/')
|
||||
const [, challengeName] = filePath.replaceAll("\\", "/").split("/")
|
||||
if (challengeName == null) {
|
||||
throw new Error('challengeName is null')
|
||||
throw new Error("challengeName is null")
|
||||
}
|
||||
return new Challenge({ name: challengeName })
|
||||
}
|
||||
},
|
||||
)
|
||||
const solutionsChallenges = await Solution.getManyByPaths(
|
||||
affectedSolutionsPaths
|
||||
)
|
||||
const solutionsDocker = await Solution.getManyByProgrammingLanguages(
|
||||
affectedLanguages
|
||||
affectedSolutionsPaths,
|
||||
)
|
||||
const solutionsDocker =
|
||||
await Solution.getManyByProgrammingLanguages(affectedLanguages)
|
||||
const solutions: Solution[] = [...solutionsDocker, ...solutionsChallenges]
|
||||
for (const challenge of affectedChallengesFromInputOutput) {
|
||||
const solutionsByChallenge = await Solution.getManyByChallenge(challenge)
|
||||
@ -113,10 +112,10 @@ export class GitAffected implements GitAffectedOptions {
|
||||
public async getAffectedSolutionsFromGit(): Promise<Solution[]> {
|
||||
let files = [
|
||||
...(await this.getUnpushedFiles()),
|
||||
...(await this.getUncommittedFiles())
|
||||
...(await this.getUncommittedFiles()),
|
||||
]
|
||||
if (this.base != null) {
|
||||
files.push(...(await this.getFilesUsingBaseAndHead(this.base, '.')))
|
||||
files.push(...(await this.getFilesUsingBaseAndHead(this.base, ".")))
|
||||
}
|
||||
files = Array.from(new Set(files))
|
||||
return await this.getAffectedSolutionsFromFiles(files)
|
||||
|
@ -1,19 +1,19 @@
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import path from 'node:path'
|
||||
import fs from 'node:fs'
|
||||
import { performance } from 'node:perf_hooks'
|
||||
import { fileURLToPath } from "node:url"
|
||||
import path from "node:path"
|
||||
import fs from "node:fs"
|
||||
import { performance } from "node:perf_hooks"
|
||||
|
||||
import chalk from 'chalk'
|
||||
import ora from 'ora'
|
||||
import chalk from "chalk"
|
||||
import ora from "ora"
|
||||
|
||||
import { isExistingPath } from '../utils/isExistingPath.js'
|
||||
import { Challenge } from './Challenge.js'
|
||||
import { copyDirectory } from '../utils/copyDirectory.js'
|
||||
import { template } from './Template.js'
|
||||
import { docker } from './Docker.js'
|
||||
import { Test } from './Test.js'
|
||||
import { SolutionTestsResult } from './SolutionTestsResult.js'
|
||||
import { TemporaryFolder } from './TemporaryFolder.js'
|
||||
import { isExistingPath } from "../utils/isExistingPath.js"
|
||||
import { Challenge } from "./Challenge.js"
|
||||
import { copyDirectory } from "../utils/copyDirectory.js"
|
||||
import { template } from "./Template.js"
|
||||
import { docker } from "./Docker.js"
|
||||
import { Test } from "./Test.js"
|
||||
import { SolutionTestsResult } from "./SolutionTestsResult.js"
|
||||
import { TemporaryFolder } from "./TemporaryFolder.js"
|
||||
|
||||
export interface GetSolutionOptions {
|
||||
programmingLanguageName: string
|
||||
@ -45,9 +45,9 @@ export class Solution implements SolutionOptions {
|
||||
this.name = name
|
||||
this.path = path.join(
|
||||
challenge.path,
|
||||
'solutions',
|
||||
"solutions",
|
||||
programmingLanguageName,
|
||||
name
|
||||
name,
|
||||
)
|
||||
this.temporaryFolder = new TemporaryFolder()
|
||||
}
|
||||
@ -57,7 +57,7 @@ export class Solution implements SolutionOptions {
|
||||
await copyDirectory(this.path, this.temporaryFolder.path)
|
||||
await template.docker({
|
||||
programmingLanguage: this.programmingLanguageName,
|
||||
destination: this.temporaryFolder.path
|
||||
destination: this.temporaryFolder.path,
|
||||
})
|
||||
process.chdir(this.temporaryFolder.path)
|
||||
try {
|
||||
@ -74,16 +74,16 @@ export class Solution implements SolutionOptions {
|
||||
|
||||
public async run(input: string, output: boolean = false): Promise<void> {
|
||||
await this.setup()
|
||||
const loader = ora('Running...').start()
|
||||
const loader = ora("Running...").start()
|
||||
try {
|
||||
const start = performance.now()
|
||||
const { stdout } = await docker.run(input, this.temporaryFolder.id)
|
||||
const end = performance.now()
|
||||
const elapsedTimeMilliseconds = end - start
|
||||
loader.succeed(chalk.bold.green('Success!'))
|
||||
loader.succeed(chalk.bold.green("Success!"))
|
||||
SolutionTestsResult.printBenchmark(elapsedTimeMilliseconds)
|
||||
if (output) {
|
||||
console.log(`${chalk.bold('Output:')}`)
|
||||
console.log(`${chalk.bold("Output:")}`)
|
||||
console.log(stdout)
|
||||
}
|
||||
} catch (error: any) {
|
||||
@ -101,7 +101,7 @@ export class Solution implements SolutionOptions {
|
||||
const solution = new Solution({
|
||||
name,
|
||||
challenge,
|
||||
programmingLanguageName
|
||||
programmingLanguageName,
|
||||
})
|
||||
if (await isExistingPath(solution.path)) {
|
||||
throw new Error(`The solution already exists: ${name}.`)
|
||||
@ -111,7 +111,7 @@ export class Solution implements SolutionOptions {
|
||||
destination: solution.path,
|
||||
githubUser,
|
||||
programmingLanguageName: solution.programmingLanguageName,
|
||||
name: solution.name
|
||||
name: solution.name,
|
||||
})
|
||||
return solution
|
||||
}
|
||||
@ -119,25 +119,25 @@ export class Solution implements SolutionOptions {
|
||||
static async get(options: GetSolutionOptions): Promise<Solution> {
|
||||
const { name, challengeName, programmingLanguageName } = options
|
||||
const challenge = new Challenge({
|
||||
name: challengeName
|
||||
name: challengeName,
|
||||
})
|
||||
const solution = new Solution({
|
||||
name,
|
||||
challenge,
|
||||
programmingLanguageName
|
||||
programmingLanguageName,
|
||||
})
|
||||
if (!(await isExistingPath(solution.path))) {
|
||||
throw new Error('The solution was not found.')
|
||||
throw new Error("The solution was not found.")
|
||||
}
|
||||
return solution
|
||||
}
|
||||
|
||||
static async getManyByChallenge(challenge: Challenge): Promise<Solution[]> {
|
||||
const solutionsPath = path.join(challenge.path, 'solutions')
|
||||
const solutionsPath = path.join(challenge.path, "solutions")
|
||||
const languagesSolution = (await fs.promises.readdir(solutionsPath)).filter(
|
||||
(name) => {
|
||||
return name !== '.gitkeep'
|
||||
}
|
||||
return name !== ".gitkeep"
|
||||
},
|
||||
)
|
||||
const paths: string[] = []
|
||||
for (const language of languagesSolution) {
|
||||
@ -152,21 +152,21 @@ export class Solution implements SolutionOptions {
|
||||
}
|
||||
|
||||
static async getManyByProgrammingLanguages(
|
||||
programmingLanguages?: string[]
|
||||
programmingLanguages?: string[],
|
||||
): Promise<Solution[]> {
|
||||
const languages =
|
||||
programmingLanguages ?? (await template.getProgrammingLanguages())
|
||||
const challengesPath = fileURLToPath(
|
||||
new URL('../../challenges', import.meta.url)
|
||||
new URL("../../challenges", import.meta.url),
|
||||
)
|
||||
const challenges = await fs.promises.readdir(challengesPath)
|
||||
const paths: string[] = []
|
||||
for (const challenge of challenges) {
|
||||
const solutionsPath = path.join(challengesPath, challenge, 'solutions')
|
||||
const solutionsPath = path.join(challengesPath, challenge, "solutions")
|
||||
const languagesSolution = (
|
||||
await fs.promises.readdir(solutionsPath)
|
||||
).filter((name) => {
|
||||
return name !== '.gitkeep' && languages.includes(name)
|
||||
return name !== ".gitkeep" && languages.includes(name)
|
||||
})
|
||||
for (const language of languagesSolution) {
|
||||
const solutionPath = (
|
||||
@ -194,7 +194,7 @@ export class Solution implements SolutionOptions {
|
||||
}
|
||||
return solutions.map((solution) => {
|
||||
const [, challengeName, , programmingLanguageName, solutionName] =
|
||||
solution.replaceAll('\\', '/').split('/')
|
||||
solution.replaceAll("\\", "/").split("/")
|
||||
|
||||
if (
|
||||
challengeName == null ||
|
||||
@ -206,10 +206,10 @@ export class Solution implements SolutionOptions {
|
||||
|
||||
return new Solution({
|
||||
challenge: new Challenge({
|
||||
name: challengeName
|
||||
name: challengeName,
|
||||
}),
|
||||
name: solutionName,
|
||||
programmingLanguageName
|
||||
programmingLanguageName,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
import logSymbols from 'log-symbols'
|
||||
import chalk from 'chalk'
|
||||
import { table } from 'table'
|
||||
import logSymbols from "log-symbols"
|
||||
import chalk from "chalk"
|
||||
import { table } from "table"
|
||||
|
||||
import type { Solution } from './Solution.js'
|
||||
import type { Test } from './Test.js'
|
||||
import type { Solution } from "./Solution.js"
|
||||
import type { Test } from "./Test.js"
|
||||
|
||||
export interface SolutionTestsResultOptions {
|
||||
tests: Test[]
|
||||
@ -22,7 +22,7 @@ export class SolutionTestsResult implements SolutionTestsResultOptions {
|
||||
public isSuccess: boolean
|
||||
public elapsedTimeMilliseconds: number
|
||||
public static readonly SUCCESS_MESSAGE = `${chalk.bold.green(
|
||||
'Success:'
|
||||
"Success:",
|
||||
)} Tests passed! 🎉`
|
||||
|
||||
constructor(options: SolutionTestsResultOptions) {
|
||||
@ -40,14 +40,14 @@ export class SolutionTestsResult implements SolutionTestsResultOptions {
|
||||
const { shouldPrintBenchmark = false, shouldPrintTableResult = false } =
|
||||
options
|
||||
const name = `${this.solution.challenge.name}/${this.solution.programmingLanguageName}/${this.solution.name}`
|
||||
console.log(`${chalk.bold('Name:')} ${name}\n`)
|
||||
console.log(`${chalk.bold("Name:")} ${name}\n`)
|
||||
const tableResult = [
|
||||
[
|
||||
chalk.bold('N°'),
|
||||
chalk.bold('Input'),
|
||||
chalk.bold('Expected'),
|
||||
chalk.bold('Received')
|
||||
]
|
||||
chalk.bold("N°"),
|
||||
chalk.bold("Input"),
|
||||
chalk.bold("Expected"),
|
||||
chalk.bold("Received"),
|
||||
],
|
||||
]
|
||||
let totalFailedTests = 0
|
||||
let totalCorrectTests = 0
|
||||
@ -58,13 +58,13 @@ export class SolutionTestsResult implements SolutionTestsResultOptions {
|
||||
totalCorrectTests += 1
|
||||
} else {
|
||||
console.log(logSymbols.error, testLabel)
|
||||
const expected = test.output.split('\n').join('\n')
|
||||
const received = test.stdout.split('\n').join('\n')
|
||||
const expected = test.output.split("\n").join("\n")
|
||||
const received = test.stdout.split("\n").join("\n")
|
||||
tableResult.push([
|
||||
test.testNumber.toString(),
|
||||
`"${test.input}"`,
|
||||
`"${expected}"`,
|
||||
`"${received}"`
|
||||
`"${received}"`,
|
||||
])
|
||||
totalFailedTests += 1
|
||||
}
|
||||
@ -78,19 +78,19 @@ export class SolutionTestsResult implements SolutionTestsResultOptions {
|
||||
? chalk.bold.green(`${totalCorrectTests} passed`)
|
||||
: chalk.bold.red(`${totalFailedTests} failed`)
|
||||
console.log(
|
||||
`${chalk.bold('Tests:')} ${testsResult}, ${this.tests.length} total`
|
||||
`${chalk.bold("Tests:")} ${testsResult}, ${this.tests.length} total`,
|
||||
)
|
||||
if (shouldPrintBenchmark) {
|
||||
SolutionTestsResult.printBenchmark(this.elapsedTimeMilliseconds)
|
||||
}
|
||||
if (!isSuccess) {
|
||||
throw new Error('Tests failed, try again!')
|
||||
throw new Error("Tests failed, try again!")
|
||||
}
|
||||
console.log('\n------------------------------\n')
|
||||
console.log("\n------------------------------\n")
|
||||
}
|
||||
|
||||
public static printBenchmark(elapsedTimeMilliseconds: number): void {
|
||||
const elapsedTime = elapsedTimeMilliseconds / 1000
|
||||
console.log(`${chalk.bold('Benchmark:')} ${elapsedTime} seconds`)
|
||||
console.log(`${chalk.bold("Benchmark:")} ${elapsedTime} seconds`)
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,19 @@
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import fs from 'node:fs'
|
||||
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 replaceInFileDefault from "replace-in-file"
|
||||
import date from "date-and-time"
|
||||
|
||||
import { copyDirectory } from '../utils/copyDirectory.js'
|
||||
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')
|
||||
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
|
||||
@ -42,8 +42,8 @@ export interface ReplaceInDestinationOptions {
|
||||
|
||||
class Template {
|
||||
private getDescription(githubUser?: string): string {
|
||||
const dateString = date.format(new Date(), 'D MMMM Y', true)
|
||||
let description = 'Created'
|
||||
const dateString = date.format(new Date(), "D MMMM Y", true)
|
||||
let description = "Created"
|
||||
if (githubUser != null) {
|
||||
description += ` by [@${githubUser}](https://github.com/${githubUser})`
|
||||
}
|
||||
@ -52,19 +52,19 @@ class Template {
|
||||
}
|
||||
|
||||
private async replaceInDestination(
|
||||
options: ReplaceInDestinationOptions
|
||||
options: ReplaceInDestinationOptions,
|
||||
): Promise<void> {
|
||||
const { name, description, destination } = options
|
||||
const readmePath = path.join(destination, 'README.md')
|
||||
const readmePath = path.join(destination, "README.md")
|
||||
await replaceInFile({
|
||||
files: [readmePath],
|
||||
from: /{{ name }}/g,
|
||||
to: name
|
||||
to: name,
|
||||
})
|
||||
await replaceInFile({
|
||||
files: [readmePath],
|
||||
from: /{{ description }}/g,
|
||||
to: description
|
||||
to: description,
|
||||
})
|
||||
}
|
||||
|
||||
@ -80,11 +80,11 @@ class Template {
|
||||
githubUser,
|
||||
name,
|
||||
challengeName,
|
||||
programmingLanguageName
|
||||
programmingLanguageName,
|
||||
} = options
|
||||
const templateLanguagePath = path.join(
|
||||
TEMPLATE_SOLUTION_PATH,
|
||||
programmingLanguageName
|
||||
programmingLanguageName,
|
||||
)
|
||||
await this.verifySupportedProgrammingLanguage(programmingLanguageName)
|
||||
await fs.promises.mkdir(destination, { recursive: true })
|
||||
@ -93,7 +93,7 @@ class Template {
|
||||
await this.replaceInDestination({
|
||||
name: `${challengeName}/${programmingLanguageName}/${name}`,
|
||||
description: this.getDescription(githubUser),
|
||||
destination
|
||||
destination,
|
||||
})
|
||||
}
|
||||
|
||||
@ -103,23 +103,23 @@ class Template {
|
||||
await this.replaceInDestination({
|
||||
name,
|
||||
description: this.getDescription(githubUser),
|
||||
destination
|
||||
destination,
|
||||
})
|
||||
}
|
||||
|
||||
public async getProgrammingLanguages(): Promise<string[]> {
|
||||
const languages = await fs.promises.readdir(TEMPLATE_SOLUTION_PATH)
|
||||
return languages.filter((language) => {
|
||||
return language !== 'base'
|
||||
return language !== "base"
|
||||
})
|
||||
}
|
||||
|
||||
public async verifySupportedProgrammingLanguage(
|
||||
language: string
|
||||
language: string,
|
||||
): Promise<void> {
|
||||
const languages = await this.getProgrammingLanguages()
|
||||
if (!languages.includes(language)) {
|
||||
throw new Error('This programming language is not supported yet.')
|
||||
throw new Error("This programming language is not supported yet.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import fs from 'node:fs'
|
||||
import crypto from 'node:crypto'
|
||||
import { fileURLToPath } from "node:url"
|
||||
import fs from "node:fs"
|
||||
import crypto from "node:crypto"
|
||||
|
||||
import { docker } from './Docker.js'
|
||||
import { docker } from "./Docker.js"
|
||||
|
||||
export class TemporaryFolder {
|
||||
public readonly id: string
|
||||
@ -24,7 +24,7 @@ export class TemporaryFolder {
|
||||
public static async cleanAll(): Promise<void> {
|
||||
try {
|
||||
const temporaryPath = fileURLToPath(
|
||||
new URL('../../temp', import.meta.url)
|
||||
new URL("../../temp", import.meta.url),
|
||||
)
|
||||
await fs.promises.rm(temporaryPath, { recursive: true, force: true })
|
||||
await docker.removeImages()
|
||||
|
@ -1,11 +1,11 @@
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { performance } from 'node:perf_hooks'
|
||||
import fs from "node:fs"
|
||||
import path from "node:path"
|
||||
import { performance } from "node:perf_hooks"
|
||||
|
||||
import type { Solution } from './Solution.js'
|
||||
import { docker } from './Docker.js'
|
||||
import { SolutionTestsResult } from './SolutionTestsResult.js'
|
||||
import { TemporaryFolder } from './TemporaryFolder.js'
|
||||
import type { Solution } from "./Solution.js"
|
||||
import { docker } from "./Docker.js"
|
||||
import { SolutionTestsResult } from "./SolutionTestsResult.js"
|
||||
import { TemporaryFolder } from "./TemporaryFolder.js"
|
||||
|
||||
export interface InputOutput {
|
||||
input: string
|
||||
@ -45,7 +45,7 @@ export class Test implements TestOptions {
|
||||
}
|
||||
|
||||
static async runAll(solution: Solution): Promise<SolutionTestsResult> {
|
||||
const testsPath = path.join(solution.challenge.path, 'test')
|
||||
const testsPath = path.join(solution.challenge.path, "test")
|
||||
const testsFolders = await fs.promises.readdir(testsPath)
|
||||
const testsNumbers = testsFolders.map((test) => {
|
||||
return Number(test)
|
||||
@ -63,11 +63,11 @@ export class Test implements TestOptions {
|
||||
}
|
||||
|
||||
static async getInputOutput(testPath: string): Promise<InputOutput> {
|
||||
const inputPath = path.join(testPath, 'input.txt')
|
||||
const outputPath = path.join(testPath, 'output.txt')
|
||||
const input = await fs.promises.readFile(inputPath, { encoding: 'utf-8' })
|
||||
const inputPath = path.join(testPath, "input.txt")
|
||||
const outputPath = path.join(testPath, "output.txt")
|
||||
const input = await fs.promises.readFile(inputPath, { encoding: "utf-8" })
|
||||
const output = await fs.promises.readFile(outputPath, {
|
||||
encoding: 'utf-8'
|
||||
encoding: "utf-8",
|
||||
})
|
||||
return { input, output }
|
||||
}
|
||||
@ -101,7 +101,7 @@ export class Test implements TestOptions {
|
||||
try {
|
||||
const { stdout } = await docker.run(
|
||||
input,
|
||||
options.solution.temporaryFolder.id
|
||||
options.solution.temporaryFolder.id,
|
||||
)
|
||||
const test = new Test({
|
||||
path: options.path,
|
||||
@ -109,12 +109,12 @@ export class Test implements TestOptions {
|
||||
input,
|
||||
output,
|
||||
stdout,
|
||||
isSuccess: stdout === output
|
||||
isSuccess: stdout === output,
|
||||
})
|
||||
return test
|
||||
} catch (error: any) {
|
||||
throw new Error(
|
||||
`solution: ${options.solution.path}\n${error.message as string}\n`
|
||||
`solution: ${options.solution.path}\n${error.message as string}\n`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1,143 +1,143 @@
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import crypto from 'node:crypto'
|
||||
import test from "node:test"
|
||||
import assert from "node:assert/strict"
|
||||
import crypto from "node:crypto"
|
||||
|
||||
import sinon from 'sinon'
|
||||
import sinon from "sinon"
|
||||
|
||||
import { Challenge } from '../Challenge.js'
|
||||
import { GitAffected } from '../GitAffected.js'
|
||||
import { Solution } from '../Solution.js'
|
||||
import { parseCommandOutput } from '../../utils/parseCommandOutput.js'
|
||||
import { Challenge } from "../Challenge.js"
|
||||
import { GitAffected } from "../GitAffected.js"
|
||||
import { Solution } from "../Solution.js"
|
||||
import { parseCommandOutput } from "../../utils/parseCommandOutput.js"
|
||||
|
||||
const gitAffected = new GitAffected()
|
||||
|
||||
await test('services/GitAffected', async (t) => {
|
||||
await test("services/GitAffected", async (t) => {
|
||||
t.afterEach(() => {
|
||||
sinon.restore()
|
||||
})
|
||||
|
||||
t.beforeEach(() => {
|
||||
sinon.stub(crypto, 'randomUUID').value(() => {
|
||||
return 'uuid'
|
||||
sinon.stub(crypto, "randomUUID").value(() => {
|
||||
return "uuid"
|
||||
})
|
||||
})
|
||||
|
||||
await t.test('parseCommandOutput', async (t) => {
|
||||
await t.test('returns the right output array', async () => {
|
||||
assert.deepStrictEqual(parseCommandOutput('1.txt\n 2.txt '), [
|
||||
'1.txt',
|
||||
'2.txt'
|
||||
await t.test("parseCommandOutput", async (t) => {
|
||||
await t.test("returns the right output array", async () => {
|
||||
assert.deepStrictEqual(parseCommandOutput("1.txt\n 2.txt "), [
|
||||
"1.txt",
|
||||
"2.txt",
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
await t.test('getAffectedSolutionsFromFiles', async (t) => {
|
||||
await t.test('returns the affected solutions', async () => {
|
||||
await t.test("getAffectedSolutionsFromFiles", async (t) => {
|
||||
await t.test("returns the affected solutions", async () => {
|
||||
const files = [
|
||||
'challenges/hello-world/solutions/javascript/function/solution.js',
|
||||
'challenges/is-palindrome/solutions/c/function/input.c'
|
||||
"challenges/hello-world/solutions/javascript/function/solution.js",
|
||||
"challenges/is-palindrome/solutions/c/function/input.c",
|
||||
]
|
||||
const solutions = await gitAffected.getAffectedSolutionsFromFiles(files)
|
||||
assert.deepStrictEqual(solutions, [
|
||||
new Solution({
|
||||
challenge: new Challenge({ name: 'hello-world' }),
|
||||
name: 'function',
|
||||
programmingLanguageName: 'javascript'
|
||||
challenge: new Challenge({ name: "hello-world" }),
|
||||
name: "function",
|
||||
programmingLanguageName: "javascript",
|
||||
}),
|
||||
new Solution({
|
||||
challenge: new Challenge({ name: 'is-palindrome' }),
|
||||
name: 'function',
|
||||
programmingLanguageName: 'c'
|
||||
})
|
||||
challenge: new Challenge({ name: "is-palindrome" }),
|
||||
name: "function",
|
||||
programmingLanguageName: "c",
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
await t.test(
|
||||
'returns the affected solutions from Dockerfile changes',
|
||||
"returns the affected solutions from Dockerfile changes",
|
||||
async () => {
|
||||
const files = ['templates/docker/javascript/Dockerfile']
|
||||
const files = ["templates/docker/javascript/Dockerfile"]
|
||||
const solutions = await gitAffected.getAffectedSolutionsFromFiles(files)
|
||||
assert.deepStrictEqual(
|
||||
solutions[0],
|
||||
new Solution({
|
||||
challenge: new Challenge({ name: 'camel-case' }),
|
||||
name: 'function',
|
||||
programmingLanguageName: 'javascript'
|
||||
})
|
||||
challenge: new Challenge({ name: "camel-case" }),
|
||||
name: "function",
|
||||
programmingLanguageName: "javascript",
|
||||
}),
|
||||
)
|
||||
assert.deepStrictEqual(
|
||||
solutions[1],
|
||||
new Solution({
|
||||
challenge: new Challenge({ name: 'first-non-repeating-character' }),
|
||||
name: 'function',
|
||||
programmingLanguageName: 'javascript'
|
||||
})
|
||||
challenge: new Challenge({ name: "first-non-repeating-character" }),
|
||||
name: "function",
|
||||
programmingLanguageName: "javascript",
|
||||
}),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
await t.test(
|
||||
'returns the affected solutions from Docker template changes',
|
||||
"returns the affected solutions from Docker template changes",
|
||||
async () => {
|
||||
const files = ['templates/docker/javascript/package.json']
|
||||
const files = ["templates/docker/javascript/package.json"]
|
||||
const solutions = await gitAffected.getAffectedSolutionsFromFiles(files)
|
||||
assert.deepStrictEqual(
|
||||
solutions[0],
|
||||
new Solution({
|
||||
challenge: new Challenge({ name: 'camel-case' }),
|
||||
name: 'function',
|
||||
programmingLanguageName: 'javascript'
|
||||
})
|
||||
challenge: new Challenge({ name: "camel-case" }),
|
||||
name: "function",
|
||||
programmingLanguageName: "javascript",
|
||||
}),
|
||||
)
|
||||
assert.deepStrictEqual(
|
||||
solutions[1],
|
||||
new Solution({
|
||||
challenge: new Challenge({ name: 'first-non-repeating-character' }),
|
||||
name: 'function',
|
||||
programmingLanguageName: 'javascript'
|
||||
})
|
||||
challenge: new Challenge({ name: "first-non-repeating-character" }),
|
||||
name: "function",
|
||||
programmingLanguageName: "javascript",
|
||||
}),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
await t.test(
|
||||
'returns the affected solutions from input/output files',
|
||||
"returns the affected solutions from input/output files",
|
||||
async () => {
|
||||
const files = ['challenges/hello-world/test/1/input.txt']
|
||||
const files = ["challenges/hello-world/test/1/input.txt"]
|
||||
const solutions = await gitAffected.getAffectedSolutionsFromFiles(files)
|
||||
assert.deepStrictEqual(
|
||||
solutions[0],
|
||||
new Solution({
|
||||
challenge: new Challenge({ name: 'hello-world' }),
|
||||
name: 'function',
|
||||
programmingLanguageName: 'c'
|
||||
})
|
||||
challenge: new Challenge({ name: "hello-world" }),
|
||||
name: "function",
|
||||
programmingLanguageName: "c",
|
||||
}),
|
||||
)
|
||||
assert.deepStrictEqual(
|
||||
solutions[1],
|
||||
new Solution({
|
||||
challenge: new Challenge({ name: 'hello-world' }),
|
||||
name: 'function',
|
||||
programmingLanguageName: 'cpp'
|
||||
})
|
||||
challenge: new Challenge({ name: "hello-world" }),
|
||||
name: "function",
|
||||
programmingLanguageName: "cpp",
|
||||
}),
|
||||
)
|
||||
assert.deepStrictEqual(
|
||||
solutions[2],
|
||||
new Solution({
|
||||
challenge: new Challenge({ name: 'hello-world' }),
|
||||
name: 'function',
|
||||
programmingLanguageName: 'cs'
|
||||
})
|
||||
challenge: new Challenge({ name: "hello-world" }),
|
||||
name: "function",
|
||||
programmingLanguageName: "cs",
|
||||
}),
|
||||
)
|
||||
assert.deepStrictEqual(
|
||||
solutions[3],
|
||||
new Solution({
|
||||
challenge: new Challenge({ name: 'hello-world' }),
|
||||
name: 'function',
|
||||
programmingLanguageName: 'dart'
|
||||
})
|
||||
challenge: new Challenge({ name: "hello-world" }),
|
||||
name: "function",
|
||||
programmingLanguageName: "dart",
|
||||
}),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
})
|
||||
})
|
||||
|
Reference in New Issue
Block a user