1
1
mirror of https://github.com/theoludwig/programming-challenges.git synced 2024-12-08 00:45:29 +01:00

chore: better Prettier config for easier reviews

This commit is contained in:
Théo LUDWIG 2023-10-23 23:16:24 +02:00
parent d7d5f9a5ac
commit 1e2736b8aa
Signed by: theoludwig
GPG Key ID: ADFE5A563D718F3B
70 changed files with 1476 additions and 1382 deletions

View File

@ -1,8 +1,8 @@
--- ---
name: '🐛 Bug Report' name: "🐛 Bug Report"
about: 'Report an unexpected problem or unintended behavior.' about: "Report an unexpected problem or unintended behavior."
title: '[Bug]' title: "[Bug]"
labels: 'bug' labels: "bug"
--- ---
<!-- <!--

View File

@ -1,8 +1,8 @@
--- ---
name: '📜 Documentation' name: "📜 Documentation"
about: 'Correct spelling errors, improvements or additions to documentation files (README, CONTRIBUTING...).' about: "Correct spelling errors, improvements or additions to documentation files (README, CONTRIBUTING...)."
title: '[Documentation]' title: "[Documentation]"
labels: 'documentation' labels: "documentation"
--- ---
<!-- Please make sure your issue has not already been fixed. --> <!-- Please make sure your issue has not already been fixed. -->

View File

@ -1,8 +1,8 @@
--- ---
name: '✨ Feature Request' name: "✨ Feature Request"
about: 'Suggest a new feature idea.' about: "Suggest a new feature idea."
title: '[Feature]' title: "[Feature]"
labels: 'feature request' labels: "feature request"
--- ---
<!-- Please make sure your issue has not already been fixed. --> <!-- Please make sure your issue has not already been fixed. -->

View File

@ -1,8 +1,8 @@
--- ---
name: '🔧 Improvement' name: "🔧 Improvement"
about: 'Improve structure/format/performance/refactor/tests of the code.' about: "Improve structure/format/performance/refactor/tests of the code."
title: '[Improvement]' title: "[Improvement]"
labels: 'improvement' labels: "improvement"
--- ---
<!-- Please make sure your issue has not already been fixed. --> <!-- Please make sure your issue has not already been fixed. -->

View File

@ -1,8 +1,8 @@
--- ---
name: '🙋 Question' name: "🙋 Question"
about: 'Further information is requested.' about: "Further information is requested."
title: '[Question]' title: "[Question]"
labels: 'question' labels: "question"
--- ---
### Question ### Question

View File

@ -1,4 +1,4 @@
name: 'challenges' name: "challenges"
on: on:
push: push:
@ -8,39 +8,39 @@ on:
jobs: jobs:
test-solutions: test-solutions:
runs-on: 'ubuntu-latest' runs-on: "ubuntu-latest"
timeout-minutes: 30 timeout-minutes: 30
steps: steps:
- uses: 'actions/checkout@v4.0.0' - uses: "actions/checkout@v4.0.0"
with: with:
fetch-depth: 0 fetch-depth: 0
- name: 'Setup Docker' - name: "Setup Docker"
uses: 'actions-hub/docker/cli@master' uses: "actions-hub/docker/cli@master"
env: env:
SKIP_LOGIN: true SKIP_LOGIN: true
- name: 'Setup Node.js' - name: "Setup Node.js"
uses: 'actions/setup-node@v3.8.1' uses: "actions/setup-node@v3.8.1"
with: with:
node-version: 'lts/*' node-version: "lts/*"
cache: 'npm' cache: "npm"
- name: 'Install dependencies' - name: "Install dependencies"
run: 'npm clean-install' run: "npm clean-install"
- name: 'Build' - name: "Build"
run: 'npm run build' run: "npm run build"
- name: 'Install programming-challenges' - name: "Install programming-challenges"
run: 'npm install --global' run: "npm install --global"
- uses: 'nrwl/last-successful-commit-action@v1' - uses: "nrwl/last-successful-commit-action@v1"
id: 'last_successful_commit' id: "last_successful_commit"
with: with:
branch: 'master' branch: "master"
workflow_id: 'challenges.yml' workflow_id: "challenges.yml"
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}
- name: 'Test' - name: "Test"
run: 'programming-challenges run test --affected --base=${{ steps.last_successful_commit.outputs.commit_hash }}' run: "programming-challenges run test --affected --base=${{ steps.last_successful_commit.outputs.commit_hash }}"

View File

@ -1,4 +1,4 @@
name: 'cli' name: "cli"
on: on:
push: push:
@ -8,64 +8,64 @@ on:
jobs: jobs:
lint: lint:
runs-on: 'ubuntu-latest' runs-on: "ubuntu-latest"
steps: steps:
- uses: 'actions/checkout@v4.0.0' - uses: "actions/checkout@v4.0.0"
- name: 'Setup Node.js' - name: "Setup Node.js"
uses: 'actions/setup-node@v3.8.1' uses: "actions/setup-node@v3.8.1"
with: with:
node-version: 'lts/*' node-version: "lts/*"
cache: 'npm' cache: "npm"
- name: 'Install dependencies' - name: "Install dependencies"
run: 'npm clean-install' run: "npm clean-install"
- run: 'npm run lint:commit -- --to "${{ github.sha }}"' - run: 'npm run lint:commit -- --to "${{ github.sha }}"'
- run: 'npm run lint:editorconfig' - run: "npm run lint:editorconfig"
- run: 'npm run lint:markdown' - run: "npm run lint:markdown"
- run: 'npm run lint:eslint' - run: "npm run lint:eslint"
build: build:
runs-on: 'ubuntu-latest' runs-on: "ubuntu-latest"
steps: steps:
- uses: 'actions/checkout@v4.0.0' - uses: "actions/checkout@v4.0.0"
- name: 'Setup Node.js' - name: "Setup Node.js"
uses: 'actions/setup-node@v3.8.1' uses: "actions/setup-node@v3.8.1"
with: with:
node-version: 'lts/*' node-version: "lts/*"
cache: 'npm' cache: "npm"
- name: 'Install dependencies' - name: "Install dependencies"
run: 'npm clean-install' run: "npm clean-install"
- name: 'Build' - name: "Build"
run: 'npm run build' run: "npm run build"
- run: 'npm run build:typescript' - run: "npm run build:typescript"
test: test:
runs-on: 'ubuntu-latest' runs-on: "ubuntu-latest"
steps: steps:
- uses: 'actions/checkout@v4.0.0' - uses: "actions/checkout@v4.0.0"
- name: 'Setup Docker' - name: "Setup Docker"
uses: 'actions-hub/docker/cli@master' uses: "actions-hub/docker/cli@master"
env: env:
SKIP_LOGIN: true SKIP_LOGIN: true
- name: 'Setup Node.js' - name: "Setup Node.js"
uses: 'actions/setup-node@v3.8.1' uses: "actions/setup-node@v3.8.1"
with: with:
node-version: 'lts/*' node-version: "lts/*"
cache: 'npm' cache: "npm"
- name: 'Install dependencies' - name: "Install dependencies"
run: 'npm clean-install' run: "npm clean-install"
- name: 'Build' - name: "Build"
run: 'npm run build' run: "npm run build"
- name: 'Test' - name: "Test"
run: 'npm run test' run: "npm run test"

View File

@ -1,10 +1,10 @@
image: 'gitpod/workspace-full' image: "gitpod/workspace-full"
tasks: tasks:
- name: 'programming-challenges' - name: "programming-challenges"
before: 'npm clean-install' before: "npm clean-install"
init: 'npm run build' init: "npm run build"
command: 'npm install --global && programming-challenges' command: "npm install --global && programming-challenges"
github: github:
prebuilds: prebuilds:

View File

@ -49,7 +49,7 @@ Gitpod will automatically setup an environment for you.
#### Prerequisites #### Prerequisites
- [Node.js](https://nodejs.org/) >= 20.0.0 - [Node.js](https://nodejs.org/) >= 20.0.0
- [npm](https://npmjs.com/) >= 9.0.0 - [npm](https://npmjs.com/) >= 10.0.0
- [Docker](https://www.docker.com/) - [Docker](https://www.docker.com/)
#### Installation #### Installation

View File

@ -17,8 +17,8 @@ make?
- **Line 1:** Single integer `N` for the number of ingredients. - **Line 1:** Single integer `N` for the number of ingredients.
- **`N` next lines:** One for each ingredient. Each of these lines contains two positive integers: - **`N` next lines:** One for each ingredient. Each of these lines contains two positive integers:
the first one is the required quantity of this ingredient per cake, the second one is the quantity of the first one is the required quantity of this ingredient per cake, the second one is the quantity of
this ingredient you have in your kitchen. this ingredient you have in your kitchen.
### Output ### Output

View File

@ -1,20 +1,20 @@
import readline from 'node:readline' import readline from "node:readline"
const input = [] const input = []
const readlineInterface = readline.createInterface({ const readlineInterface = readline.createInterface({
input: process.stdin, input: process.stdin,
output: process.stdout output: process.stdout,
}) })
readlineInterface.on('line', (value) => { readlineInterface.on("line", (value) => {
input.push(value) input.push(value)
}) })
readlineInterface.on('close', solution) readlineInterface.on("close", solution)
function solution() { function solution() {
const string = input[0] const string = input[0]
const output = string const output = string
.trim() .trim()
.split(' ') .split(" ")
.map((word, index) => { .map((word, index) => {
const isFirstElement = index === 0 const isFirstElement = index === 0
if (isFirstElement) { if (isFirstElement) {
@ -22,6 +22,6 @@ function solution() {
} }
return word[0].toUpperCase() + word.slice(1) return word[0].toUpperCase() + word.slice(1)
}) })
.join('') .join("")
console.log(output) console.log(output)
} }

View File

@ -21,7 +21,7 @@ This data is comprised of lines, each of which represents a defibrillator. Each
- Contact Phone number - Contact Phone number
- Longitude (degrees) - Longitude (degrees)
- Latitude (degrees) - Latitude (degrees)
These fields are separated by a semicolon (`;`). These fields are separated by a semicolon (`;`).
**Beware:** the decimal numbers use the comma (,) as decimal separator. Remember to turn the comma (,) into dot (.) if necessary in order to use the data in your program. **Beware:** the decimal numbers use the comma (,) as decimal separator. Remember to turn the comma (,) into dot (.) if necessary in order to use the data in your program.

View File

@ -1,14 +1,14 @@
import readline from 'node:readline' import readline from "node:readline"
const input = [] const input = []
const readlineInterface = readline.createInterface({ const readlineInterface = readline.createInterface({
input: process.stdin, input: process.stdin,
output: process.stdout output: process.stdout,
}) })
readlineInterface.on('line', (value) => { readlineInterface.on("line", (value) => {
input.push(value) input.push(value)
}) })
readlineInterface.on('close', solution) readlineInterface.on("close", solution)
const firstNonRepeatingCharacter = (string) => { const firstNonRepeatingCharacter = (string) => {
const lettersCount = {} const lettersCount = {}
@ -18,7 +18,7 @@ const firstNonRepeatingCharacter = (string) => {
lettersCount[character] = { lettersCount[character] = {
total: 1, total: 1,
firstIndex: index, firstIndex: index,
value: character value: character,
} }
} else { } else {
lettersCount[character].total += 1 lettersCount[character].total += 1
@ -36,7 +36,7 @@ const firstNonRepeatingCharacter = (string) => {
} }
} }
if (result == null) { if (result == null) {
return '' return ""
} }
return result.value return result.value
} }

View File

@ -7,7 +7,7 @@ export const getMaximumFrequencyDeviation = (string) => {
/** @type {string[]} */ /** @type {string[]} */
const subStrings = [] const subStrings = []
for (let index = 0; index < string.length; index++) { for (let index = 0; index < string.length; index++) {
let subString = '' let subString = ""
for (let subIndex = index; subIndex < string.length; subIndex++) { for (let subIndex = index; subIndex < string.length; subIndex++) {
subString += string[subIndex] subString += string[subIndex]
subStrings.push(subString) subStrings.push(subString)

View File

@ -1,13 +1,13 @@
import readline from 'node:readline' import readline from "node:readline"
import { getMaximumFrequencyDeviation } from './getMaximumFrequencyDeviation.js' import { getMaximumFrequencyDeviation } from "./getMaximumFrequencyDeviation.js"
const input = [] const input = []
const readlineInterface = readline.createInterface({ const readlineInterface = readline.createInterface({
input: process.stdin, input: process.stdin,
output: process.stdout output: process.stdout,
}) })
readlineInterface.on('line', (value) => { readlineInterface.on("line", (value) => {
input.push(value) input.push(value)
}) })
@ -15,4 +15,4 @@ const solution = () => {
console.log(getMaximumFrequencyDeviation(input[0])) console.log(getMaximumFrequencyDeviation(input[0]))
} }
readlineInterface.on('close', solution) readlineInterface.on("close", solution)

View File

@ -1,14 +1,14 @@
import readline from 'node:readline' import readline from "node:readline"
const input = [] const input = []
const readlineInterface = readline.createInterface({ const readlineInterface = readline.createInterface({
input: process.stdin, input: process.stdin,
output: process.stdout output: process.stdout,
}) })
readlineInterface.on('line', (value) => { readlineInterface.on("line", (value) => {
input.push(value) input.push(value)
}) })
readlineInterface.on('close', solution) readlineInterface.on("close", solution)
function solution() { function solution() {
console.log(`Hello, ${input[0]}!`) console.log(`Hello, ${input[0]}!`)

View File

@ -1,14 +1,14 @@
import readline from 'node:readline' import readline from "node:readline"
const input: string[] = [] const input: string[] = []
const readlineInterface = readline.createInterface({ const readlineInterface = readline.createInterface({
input: process.stdin, input: process.stdin,
output: process.stdout output: process.stdout,
}) })
readlineInterface.on('line', (value) => { readlineInterface.on("line", (value) => {
input.push(value) input.push(value)
}) })
readlineInterface.on('close', solution) readlineInterface.on("close", solution)
function solution(): void { function solution(): void {
console.log(`Hello, ${input[0]}!`) console.log(`Hello, ${input[0]}!`)

View File

@ -1,17 +1,17 @@
import readline from 'node:readline' import readline from "node:readline"
const input = [] const input = []
const readlineInterface = readline.createInterface({ const readlineInterface = readline.createInterface({
input: process.stdin, input: process.stdin,
output: process.stdout output: process.stdout,
}) })
readlineInterface.on('line', (value) => { readlineInterface.on("line", (value) => {
input.push(value) input.push(value)
}) })
readlineInterface.on('close', solution) readlineInterface.on("close", solution)
function solution() { function solution() {
const string = input[0].replace(/ /g, '').toLowerCase() const string = input[0].replace(/ /g, "").toLowerCase()
const isPalindrome = string.split('').reverse().join('') === string const isPalindrome = string.split("").reverse().join("") === string
console.log(isPalindrome) console.log(isPalindrome)
} }

View File

@ -1,4 +1,4 @@
import readline from 'node:readline' import readline from "node:readline"
/** /**
* *
@ -12,7 +12,7 @@ const leftPad = (string, resultLength, padString) => {
if (resultLength <= 0) { if (resultLength <= 0) {
return string return string
} }
let pad = '' let pad = ""
while (resultLength !== 0) { while (resultLength !== 0) {
if (resultLength & 1) { if (resultLength & 1) {
pad += padString pad += padString
@ -32,9 +32,9 @@ const solution = () => {
const input = [] const input = []
const readlineInterface = readline.createInterface({ const readlineInterface = readline.createInterface({
input: process.stdin, input: process.stdin,
output: process.stdout output: process.stdout,
}) })
readlineInterface.on('line', (value) => { readlineInterface.on("line", (value) => {
input.push(value) input.push(value)
}) })
readlineInterface.on('close', solution) readlineInterface.on("close", solution)

View File

@ -1,23 +1,23 @@
import readline from 'node:readline' import readline from "node:readline"
const input = [] const input = []
const readlineInterface = readline.createInterface({ const readlineInterface = readline.createInterface({
input: process.stdin, input: process.stdin,
output: process.stdout output: process.stdout,
}) })
readlineInterface.on('line', (value) => { readlineInterface.on("line", (value) => {
input.push(value) input.push(value)
}) })
readlineInterface.on('close', solution) readlineInterface.on("close", solution)
const operations = { const operations = {
'+': (number1, number2) => number1 + number2, "+": (number1, number2) => number1 + number2,
'-': (number1, number2) => number1 - number2, "-": (number1, number2) => number1 - number2,
'*': (number1, number2) => number1 * number2, "*": (number1, number2) => number1 * number2,
'/': (number1, number2) => number1 / number2 "/": (number1, number2) => number1 / number2,
} }
function solution () { function solution() {
console.log(reversePolishNotation(input[0])) console.log(reversePolishNotation(input[0]))
} }
@ -26,7 +26,7 @@ function reversePolishNotation(value) {
return 0 return 0
} }
const stack = [] const stack = []
const values = value.split(' ') const values = value.split(" ")
values.forEach((value) => { values.forEach((value) => {
const number = Number(value) const number = Number(value)
if (!isNaN(number)) { if (!isNaN(number)) {

View File

@ -26,9 +26,9 @@ Here are the rules for building a Roman numeral:
- `42` is written as `XLII` - `42` is written as `XLII`
- `2448` is written as `MMCDXLVIII`. - `2448` is written as `MMCDXLVIII`.
| Symbol | I | V | X | L | C | D | M | | Symbol | I | V | X | L | C | D | M |
|--------|---|---|----|----|-----|-----|------| | ------ | --- | --- | --- | --- | --- | --- | ---- |
| Value | 1 | 5 | 10 | 50 | 100 | 500 | 1000 | | Value | 1 | 5 | 10 | 50 | 100 | 500 | 1000 |
### Input ### Input

View File

@ -2,6 +2,6 @@
Created by [@theoludwig](https://github.com/theoludwig) on 29 June 2021. Created by [@theoludwig](https://github.com/theoludwig) on 29 June 2021.
| Algorithm | Best Case | Average Case | Worst Case | | Algorithm | Best Case | Average Case | Worst Case |
| ----------------------------------------------------------- | ----------- | ------------ | ----------- | | ----------------------------------------------------- | --------- | ------------ | ---------- |
| [Bubble sort](https://wikipedia.org/wiki/Bubble_sort) | O(n) | O(n²) | O(n²) | | [Bubble sort](https://wikipedia.org/wiki/Bubble_sort) | O(n) | O(n²) | O(n²) |

View File

@ -2,6 +2,6 @@
Created by [@theoludwig](https://github.com/theoludwig) on 29 June 2021. Created by [@theoludwig](https://github.com/theoludwig) on 29 June 2021.
| Algorithm | Best Case | Average Case | Worst Case | | Algorithm | Best Case | Average Case | Worst Case |
| ----------------------------------------------------------- | ----------- | ------------ | ----------- | | ----------------------------------------------------------- | --------- | ------------ | ---------- |
| [Insertion sort](https://wikipedia.org/wiki/Insertion_sort) | O(n) | O(n²) | O(n²) | | [Insertion sort](https://wikipedia.org/wiki/Insertion_sort) | O(n) | O(n²) | O(n²) |

View File

@ -2,6 +2,6 @@
Created by [@theoludwig](https://github.com/theoludwig) on 29 June 2021. Created by [@theoludwig](https://github.com/theoludwig) on 29 June 2021.
| Algorithm | Best Case | Average Case | Worst Case | | Algorithm | Best Case | Average Case | Worst Case |
| ----------------------------------------------------------- | ----------- | ------------ | ----------- | | --------------------------------------------------- | ----------- | ------------ | ----------- |
| [Merge sort](https://wikipedia.org/wiki/Merge_sort) | O(n log(n)) | O(n log(n)) | O(n log(n)) | | [Merge sort](https://wikipedia.org/wiki/Merge_sort) | O(n log(n)) | O(n log(n)) | O(n log(n)) |

View File

@ -2,6 +2,6 @@
Created by [@theoludwig](https://github.com/theoludwig) on 29 June 2021. Created by [@theoludwig](https://github.com/theoludwig) on 29 June 2021.
| Algorithm | Best Case | Average Case | Worst Case | | Algorithm | Best Case | Average Case | Worst Case |
| ----------------------------------------------------------- | ----------- | ------------ | ----------- | | ----------------------------------------------------- | --------- | ------------ | ---------- |
| [Bubble sort](https://wikipedia.org/wiki/Bubble_sort) | O(n) | O(n²) | O(n²) | | [Bubble sort](https://wikipedia.org/wiki/Bubble_sort) | O(n) | O(n²) | O(n²) |

View File

@ -2,6 +2,6 @@
Created by [@theoludwig](https://github.com/theoludwig) on 29 June 2021. Created by [@theoludwig](https://github.com/theoludwig) on 29 June 2021.
| Algorithm | Best Case | Average Case | Worst Case | | Algorithm | Best Case | Average Case | Worst Case |
| ----------------------------------------------------------- | ----------- | ------------ | ----------- | | ----------------------------------------------------------- | --------- | ------------ | ---------- |
| [Insertion sort](https://wikipedia.org/wiki/Insertion_sort) | O(n) | O(n²) | O(n²) | | [Insertion sort](https://wikipedia.org/wiki/Insertion_sort) | O(n) | O(n²) | O(n²) |

View File

@ -2,6 +2,6 @@
Created by [@theoludwig](https://github.com/theoludwig) on 29 June 2021. Created by [@theoludwig](https://github.com/theoludwig) on 29 June 2021.
| Algorithm | Best Case | Average Case | Worst Case | | Algorithm | Best Case | Average Case | Worst Case |
| ----------------------------------------------------------- | ----------- | ------------ | ----------- | | --------------------------------------------------- | ----------- | ------------ | ----------- |
| [Merge sort](https://wikipedia.org/wiki/Merge_sort) | O(n log(n)) | O(n log(n)) | O(n log(n)) | | [Merge sort](https://wikipedia.org/wiki/Merge_sort) | O(n log(n)) | O(n log(n)) | O(n log(n)) |

View File

@ -2,6 +2,6 @@
Created by [@theoludwig](https://github.com/theoludwig) on 29 June 2021. Created by [@theoludwig](https://github.com/theoludwig) on 29 June 2021.
| Algorithm | Best Case | Average Case | Worst Case | | Algorithm | Best Case | Average Case | Worst Case |
| ----------------------------------------------------------- | ----------- | ------------ | ----------- | | ----------------------------------------------------- | --------- | ------------ | ---------- |
| [Bubble sort](https://wikipedia.org/wiki/Bubble_sort) | O(n) | O(n²) | O(n²) | | [Bubble sort](https://wikipedia.org/wiki/Bubble_sort) | O(n) | O(n²) | O(n²) |

View File

@ -1,14 +1,14 @@
import readline from 'node:readline' import readline from "node:readline"
const numbers = [] const numbers = []
const readlineInterface = readline.createInterface({ const readlineInterface = readline.createInterface({
input: process.stdin, input: process.stdin,
output: process.stdout output: process.stdout,
}) })
readlineInterface.on('line', (value) => { readlineInterface.on("line", (value) => {
numbers.push(Number(value)) numbers.push(Number(value))
}) })
readlineInterface.on('close', solution) readlineInterface.on("close", solution)
function solution() { function solution() {
const sortedNumbers = bubbleSort(numbers.slice(1)) const sortedNumbers = bubbleSort(numbers.slice(1))
@ -17,7 +17,7 @@ function solution() {
}) })
} }
function bubbleSort (numbersInput) { function bubbleSort(numbersInput) {
const numbers = [...numbersInput] const numbers = [...numbersInput]
for (let index1 = 0; index1 < numbers.length; index1++) { for (let index1 = 0; index1 < numbers.length; index1++) {
for (let index2 = 0; index2 < numbers.length - index1 - 1; index2++) { for (let index2 = 0; index2 < numbers.length - index1 - 1; index2++) {

View File

@ -1,14 +1,14 @@
import readline from 'node:readline' import readline from "node:readline"
const numbers = [] const numbers = []
const readlineInterface = readline.createInterface({ const readlineInterface = readline.createInterface({
input: process.stdin, input: process.stdin,
output: process.stdout output: process.stdout,
}) })
readlineInterface.on('line', (value) => { readlineInterface.on("line", (value) => {
numbers.push(Number(value)) numbers.push(Number(value))
}) })
readlineInterface.on('close', solution) readlineInterface.on("close", solution)
function solution() { function solution() {
const sortedNumbers = nativeSort(numbers.slice(1)) const sortedNumbers = nativeSort(numbers.slice(1))
@ -17,6 +17,6 @@ function solution() {
}) })
} }
function nativeSort (numbers) { function nativeSort(numbers) {
return numbers.sort((number1, number2) => number1 - number2) return numbers.sort((number1, number2) => number1 - number2)
} }

View File

@ -2,6 +2,6 @@
Created by [@theoludwig](https://github.com/theoludwig) on 29 June 2021. Created by [@theoludwig](https://github.com/theoludwig) on 29 June 2021.
| Algorithm | Best Case | Average Case | Worst Case | | Algorithm | Best Case | Average Case | Worst Case |
| ----------------------------------------------------------- | ----------- | ------------ | ----------- | | ----------------------------------------------------------- | --------- | ------------ | ---------- |
| [Insertion sort](https://wikipedia.org/wiki/Insertion_sort) | O(n) | O(n²) | O(n²) | | [Insertion sort](https://wikipedia.org/wiki/Insertion_sort) | O(n) | O(n²) | O(n²) |

View File

@ -1,14 +1,14 @@
import readline from 'node:readline' import readline from "node:readline"
const numbers = [] const numbers = []
const readlineInterface = readline.createInterface({ const readlineInterface = readline.createInterface({
input: process.stdin, input: process.stdin,
output: process.stdout output: process.stdout,
}) })
readlineInterface.on('line', (value) => { readlineInterface.on("line", (value) => {
numbers.push(Number(value)) numbers.push(Number(value))
}) })
readlineInterface.on('close', solution) readlineInterface.on("close", solution)
function solution() { function solution() {
const sortedNumbers = insertionSort(numbers.slice(1)) const sortedNumbers = insertionSort(numbers.slice(1))

View File

@ -2,6 +2,6 @@
Created by [@theoludwig](https://github.com/theoludwig) on 29 June 2021. Created by [@theoludwig](https://github.com/theoludwig) on 29 June 2021.
| Algorithm | Best Case | Average Case | Worst Case | | Algorithm | Best Case | Average Case | Worst Case |
| ----------------------------------------------------------- | ----------- | ------------ | ----------- | | --------------------------------------------------- | ----------- | ------------ | ----------- |
| [Merge sort](https://wikipedia.org/wiki/Merge_sort) | O(n log(n)) | O(n log(n)) | O(n log(n)) | | [Merge sort](https://wikipedia.org/wiki/Merge_sort) | O(n log(n)) | O(n log(n)) | O(n log(n)) |

View File

@ -1,14 +1,14 @@
import readline from 'node:readline' import readline from "node:readline"
const numbers = [] const numbers = []
const readlineInterface = readline.createInterface({ const readlineInterface = readline.createInterface({
input: process.stdin, input: process.stdin,
output: process.stdout output: process.stdout,
}) })
readlineInterface.on('line', (value) => { readlineInterface.on("line", (value) => {
numbers.push(Number(value)) numbers.push(Number(value))
}) })
readlineInterface.on('close', solution) readlineInterface.on("close", solution)
function solution() { function solution() {
const sortedNumbers = mergeSort(numbers.slice(1)) const sortedNumbers = mergeSort(numbers.slice(1))
@ -17,14 +17,14 @@ function solution() {
}) })
} }
function divideArray (numbers) { function divideArray(numbers) {
const middle = Math.round(numbers.length / 2) const middle = Math.round(numbers.length / 2)
const left = numbers.slice(0, middle) const left = numbers.slice(0, middle)
const right = numbers.slice(middle) const right = numbers.slice(middle)
return [left, right] return [left, right]
} }
function merge (numbers1, numbers2) { function merge(numbers1, numbers2) {
let indexNumbers1 = 0 let indexNumbers1 = 0
let indexNumbers2 = 0 let indexNumbers2 = 0
const result = [] const result = []
@ -46,7 +46,7 @@ function merge (numbers1, numbers2) {
return result return result
} }
function mergeSort (numbers) { function mergeSort(numbers) {
if (numbers.length <= 1) { if (numbers.length <= 1) {
return numbers return numbers
} }

View File

@ -2,6 +2,6 @@
Created by [@theoludwig](https://github.com/theoludwig) on 29 June 2021. Created by [@theoludwig](https://github.com/theoludwig) on 29 June 2021.
| Algorithm | Best Case | Average Case | Worst Case | | Algorithm | Best Case | Average Case | Worst Case |
| ----------------------------------------------------------- | ----------- | ------------ | ----------- | | ----------------------------------------------------- | --------- | ------------ | ---------- |
| [Bubble sort](https://wikipedia.org/wiki/Bubble_sort) | O(n) | O(n²) | O(n²) | | [Bubble sort](https://wikipedia.org/wiki/Bubble_sort) | O(n) | O(n²) | O(n²) |

View File

@ -2,6 +2,6 @@
Created by [@theoludwig](https://github.com/theoludwig) on 29 June 2021. Created by [@theoludwig](https://github.com/theoludwig) on 29 June 2021.
| Algorithm | Best Case | Average Case | Worst Case | | Algorithm | Best Case | Average Case | Worst Case |
| ----------------------------------------------------------- | ----------- | ------------ | ----------- | | ----------------------------------------------------------- | --------- | ------------ | ---------- |
| [Insertion sort](https://wikipedia.org/wiki/Insertion_sort) | O(n) | O(n²) | O(n²) | | [Insertion sort](https://wikipedia.org/wiki/Insertion_sort) | O(n) | O(n²) | O(n²) |

View File

@ -2,6 +2,6 @@
Created by [@theoludwig](https://github.com/theoludwig) on 29 June 2021. Created by [@theoludwig](https://github.com/theoludwig) on 29 June 2021.
| Algorithm | Best Case | Average Case | Worst Case | | Algorithm | Best Case | Average Case | Worst Case |
| ----------------------------------------------------------- | ----------- | ------------ | ----------- | | --------------------------------------------------- | ----------- | ------------ | ----------- |
| [Merge sort](https://wikipedia.org/wiki/Merge_sort) | O(n log(n)) | O(n log(n)) | O(n log(n)) | | [Merge sort](https://wikipedia.org/wiki/Merge_sort) | O(n log(n)) | O(n log(n)) | O(n log(n)) |

View File

@ -1,15 +1,15 @@
import { Builtins, Cli } from 'clipanion' import { Builtins, Cli } from "clipanion"
import { GenerateChallengeCommand } from './commands/generate/challenge.js' import { GenerateChallengeCommand } from "./commands/generate/challenge.js"
import { GenerateSolutionCommand } from './commands/generate/solution.js' import { GenerateSolutionCommand } from "./commands/generate/solution.js"
import { RunSolutionCommand } from './commands/run/solution.js' import { RunSolutionCommand } from "./commands/run/solution.js"
import { RunTestCommand } from './commands/run/test.js' import { RunTestCommand } from "./commands/run/test.js"
import { SearchCommand } from './commands/search/index.js' import { SearchCommand } from "./commands/search/index.js"
export const cli = new Cli({ export const cli = new Cli({
binaryLabel: 'programming-challenges', binaryLabel: "programming-challenges",
binaryName: 'programming-challenges', binaryName: "programming-challenges",
binaryVersion: '1.0.0' binaryVersion: "1.0.0",
}) })
cli.register(Builtins.HelpCommand) cli.register(Builtins.HelpCommand)
cli.register(Builtins.VersionCommand) cli.register(Builtins.VersionCommand)

View File

@ -1,34 +1,34 @@
import test from 'node:test' import test from "node:test"
import assert from 'node:assert/strict' import assert from "node:assert/strict"
import { PassThrough } from 'node:stream' import { PassThrough } from "node:stream"
import path from 'node:path' import path from "node:path"
import fs from 'node:fs' import fs from "node:fs"
import sinon from 'sinon' import sinon from "sinon"
import fsMock from 'mock-fs' import fsMock from "mock-fs"
import chalk from 'chalk' import chalk from "chalk"
import getStream from 'get-stream' import getStream from "get-stream"
import date from 'date-and-time' import date from "date-and-time"
import { cli } from '../../../cli.js' import { cli } from "../../../cli.js"
import { isExistingPath } from '../../../utils/isExistingPath.js' import { isExistingPath } from "../../../utils/isExistingPath.js"
const input = ['generate', 'challenge'] const input = ["generate", "challenge"]
const githubUser = 'theoludwig' const githubUser = "theoludwig"
const challenge = 'aaaa-test-jest' const challenge = "aaaa-test-jest"
const inputChallenge = `--challenge=${challenge}` const inputChallenge = `--challenge=${challenge}`
const inputGitHubUser = `--github-user=${githubUser}` const inputGitHubUser = `--github-user=${githubUser}`
await test('programming-challenges generate challenge', async (t) => { await test("programming-challenges generate challenge", async (t) => {
t.beforeEach(() => { t.beforeEach(() => {
fsMock( fsMock(
{ {
[process.cwd()]: fsMock.load(process.cwd(), { [process.cwd()]: fsMock.load(process.cwd(), {
recursive: true, recursive: true,
lazy: true lazy: true,
}) }),
}, },
{ createCwd: false } { createCwd: false },
) )
}) })
@ -37,28 +37,28 @@ await test('programming-challenges generate challenge', async (t) => {
sinon.restore() sinon.restore()
}) })
await t.test('succeeds and generate the new challenge', async () => { await t.test("succeeds and generate the new challenge", async () => {
sinon.stub(console, 'log').value(() => {}) sinon.stub(console, "log").value(() => {})
const consoleLogSpy = sinon.spy(console, 'log') const consoleLogSpy = sinon.spy(console, "log")
const dateString = date.format(new Date(), 'D MMMM Y', true) const dateString = date.format(new Date(), "D MMMM Y", true)
const stream = new PassThrough() const stream = new PassThrough()
const exitCode = await cli.run( const exitCode = await cli.run(
[...input, inputChallenge, inputGitHubUser], [...input, inputChallenge, inputGitHubUser],
{ {
stdin: process.stdin, stdin: process.stdin,
stdout: stream, stdout: stream,
stderr: stream stderr: stream,
} },
) )
stream.end() stream.end()
assert.strictEqual(exitCode, 0) assert.strictEqual(exitCode, 0)
const challengePath = path.join(process.cwd(), 'challenges', challenge) const challengePath = path.join(process.cwd(), "challenges", challenge)
const readmePath = path.join(challengePath, 'README.md') const readmePath = path.join(challengePath, "README.md")
const readmeContent = await fs.promises.readFile(readmePath, { const readmeContent = await fs.promises.readFile(readmePath, {
encoding: 'utf-8' encoding: "utf-8",
}) })
const successMessage = `${chalk.bold.green( const successMessage = `${chalk.bold.green(
'Success:' "Success:",
)} created the new challenge at ${challengePath}.` )} created the new challenge at ${challengePath}.`
assert.strictEqual(consoleLogSpy.calledWith(successMessage), true) assert.strictEqual(consoleLogSpy.calledWith(successMessage), true)
assert.strictEqual(await isExistingPath(challengePath), true) assert.strictEqual(await isExistingPath(challengePath), true)
@ -75,17 +75,17 @@ Description of the challenge...
## Examples ## Examples
See the \`test\` folder for examples of input/output. See the \`test\` folder for examples of input/output.
` `,
) )
}) })
await t.test('fails without options', async () => { await t.test("fails without options", async () => {
const stream = new PassThrough() const stream = new PassThrough()
const promise = getStream(stream) const promise = getStream(stream)
const exitCode = await cli.run(input, { const exitCode = await cli.run(input, {
stdin: process.stdin, stdin: process.stdin,
stdout: stream, stdout: stream,
stderr: stream stderr: stream,
}) })
stream.end() stream.end()
assert.strictEqual(exitCode, 1) assert.strictEqual(exitCode, 1)
@ -93,47 +93,49 @@ See the \`test\` folder for examples of input/output.
assert.match(output, /Unknown Syntax Error/) assert.match(output, /Unknown Syntax Error/)
}) })
await t.test('fails with already existing challenge', async () => { await t.test("fails with already existing challenge", async () => {
sinon.stub(console, 'error').value(() => {}) sinon.stub(console, "error").value(() => {})
const consoleErrorSpy = sinon.spy(console, 'error') const consoleErrorSpy = sinon.spy(console, "error")
const stream = new PassThrough() const stream = new PassThrough()
const exitCode = await cli.run( const exitCode = await cli.run(
[...input, '--challenge=hello-world', inputGitHubUser], [...input, "--challenge=hello-world", inputGitHubUser],
{ {
stdin: process.stdin, stdin: process.stdin,
stdout: stream, stdout: stream,
stderr: stream stderr: stream,
} },
) )
stream.end() stream.end()
assert.strictEqual(exitCode, 1) assert.strictEqual(exitCode, 1)
assert.strictEqual( assert.strictEqual(
consoleErrorSpy.calledWith( consoleErrorSpy.calledWith(
`${chalk.bold.red('Error:')} The challenge already exists: hello-world.` `${chalk.bold.red(
"Error:",
)} The challenge already exists: hello-world.`,
), ),
true true,
) )
}) })
await t.test('fails with invalid challenge name', async () => { await t.test("fails with invalid challenge name", async () => {
sinon.stub(console, 'error').value(() => {}) sinon.stub(console, "error").value(() => {})
const consoleErrorSpy = sinon.spy(console, 'error') const consoleErrorSpy = sinon.spy(console, "error")
const stream = new PassThrough() const stream = new PassThrough()
const exitCode = await cli.run( const exitCode = await cli.run(
[...input, '--challenge=hEllO-world', inputGitHubUser], [...input, "--challenge=hEllO-world", inputGitHubUser],
{ {
stdin: process.stdin, stdin: process.stdin,
stdout: stream, stdout: stream,
stderr: stream stderr: stream,
} },
) )
stream.end() stream.end()
assert.strictEqual(exitCode, 1) assert.strictEqual(exitCode, 1)
assert.strictEqual( assert.strictEqual(
consoleErrorSpy.calledWith( consoleErrorSpy.calledWith(
`${chalk.bold.red('Error:')} Invalid challenge name.` `${chalk.bold.red("Error:")} Invalid challenge name.`,
), ),
true true,
) )
}) })
}) })

View File

@ -1,35 +1,35 @@
import test from 'node:test' import test from "node:test"
import assert from 'node:assert/strict' import assert from "node:assert/strict"
import { PassThrough } from 'node:stream' import { PassThrough } from "node:stream"
import path from 'node:path' import path from "node:path"
import fs from 'node:fs' import fs from "node:fs"
import sinon from 'sinon' import sinon from "sinon"
import fsMock from 'mock-fs' import fsMock from "mock-fs"
import chalk from 'chalk' import chalk from "chalk"
import getStream from 'get-stream' import getStream from "get-stream"
import date from 'date-and-time' import date from "date-and-time"
import { cli } from '../../../cli.js' import { cli } from "../../../cli.js"
import { isExistingPath } from '../../../utils/isExistingPath.js' import { isExistingPath } from "../../../utils/isExistingPath.js"
const input = ['generate', 'solution'] const input = ["generate", "solution"]
const githubUser = 'theoludwig' const githubUser = "theoludwig"
const challenge = 'hello-world' const challenge = "hello-world"
const language = 'c' const language = "c"
const solution = 'new-solution' const solution = "new-solution"
const inputChallenge = `--challenge=${challenge}` const inputChallenge = `--challenge=${challenge}`
const inputGitHubUser = `--github-user=${githubUser}` const inputGitHubUser = `--github-user=${githubUser}`
const inputLanguage = `--language=${language}` const inputLanguage = `--language=${language}`
const inputSolution = `--solution=${solution}` const inputSolution = `--solution=${solution}`
await test('programming-challenges generate solution', async (t) => { await test("programming-challenges generate solution", async (t) => {
t.beforeEach(() => { t.beforeEach(() => {
fsMock( fsMock(
{ {
[process.cwd()]: fsMock.load(process.cwd(), { recursive: true }) [process.cwd()]: fsMock.load(process.cwd(), { recursive: true }),
}, },
{ createCwd: false } { createCwd: false },
) )
}) })
@ -38,35 +38,35 @@ await test('programming-challenges generate solution', async (t) => {
sinon.restore() sinon.restore()
}) })
await t.test('succeeds and generate the new solution', async () => { await t.test("succeeds and generate the new solution", async () => {
sinon.stub(console, 'log').value(() => {}) sinon.stub(console, "log").value(() => {})
const consoleLogSpy = sinon.spy(console, 'log') const consoleLogSpy = sinon.spy(console, "log")
const dateString = date.format(new Date(), 'D MMMM Y', true) const dateString = date.format(new Date(), "D MMMM Y", true)
const stream = new PassThrough() const stream = new PassThrough()
const exitCode = await cli.run( const exitCode = await cli.run(
[...input, inputChallenge, inputGitHubUser, inputLanguage, inputSolution], [...input, inputChallenge, inputGitHubUser, inputLanguage, inputSolution],
{ {
stdin: process.stdin, stdin: process.stdin,
stdout: stream, stdout: stream,
stderr: stream stderr: stream,
} },
) )
stream.end() stream.end()
assert.strictEqual(exitCode, 0) assert.strictEqual(exitCode, 0)
const solutionPath = path.join( const solutionPath = path.join(
process.cwd(), process.cwd(),
'challenges', "challenges",
challenge, challenge,
'solutions', "solutions",
language, language,
solution solution,
) )
const readmePath = path.join(solutionPath, 'README.md') const readmePath = path.join(solutionPath, "README.md")
const readmeContent = await fs.promises.readFile(readmePath, { const readmeContent = await fs.promises.readFile(readmePath, {
encoding: 'utf-8' encoding: "utf-8",
}) })
const successMessage = `${chalk.bold.green( const successMessage = `${chalk.bold.green(
'Success:' "Success:",
)} created the new solution at ${solutionPath}.` )} created the new solution at ${solutionPath}.`
assert.strictEqual(consoleLogSpy.calledWith(successMessage), true) assert.strictEqual(consoleLogSpy.calledWith(successMessage), true)
assert.strictEqual(await isExistingPath(solutionPath), true) assert.strictEqual(await isExistingPath(solutionPath), true)
@ -75,15 +75,15 @@ await test('programming-challenges generate solution', async (t) => {
`# ${challenge}/${language}/${solution} `# ${challenge}/${language}/${solution}
Created by [@${githubUser}](https://github.com/${githubUser}) on ${dateString}. Created by [@${githubUser}](https://github.com/${githubUser}) on ${dateString}.
` `,
) )
}) })
await t.test("fails with challenges that doesn't exist", async () => { await t.test("fails with challenges that doesn't exist", async () => {
sinon.stub(console, 'error').value(() => {}) sinon.stub(console, "error").value(() => {})
const consoleErrorSpy = sinon.spy(console, 'error') const consoleErrorSpy = sinon.spy(console, "error")
const stream = new PassThrough() const stream = new PassThrough()
const invalidChallenge = 'aaa-jest-challenge' const invalidChallenge = "aaa-jest-challenge"
const inputInvalidChallenge = `--challenge=${invalidChallenge}` const inputInvalidChallenge = `--challenge=${invalidChallenge}`
const exitCode = await cli.run( const exitCode = await cli.run(
[ [
@ -91,30 +91,30 @@ Created by [@${githubUser}](https://github.com/${githubUser}) on ${dateString}.
inputInvalidChallenge, inputInvalidChallenge,
inputGitHubUser, inputGitHubUser,
inputLanguage, inputLanguage,
inputSolution inputSolution,
], ],
{ {
stdin: process.stdin, stdin: process.stdin,
stdout: stream, stdout: stream,
stderr: stream stderr: stream,
} },
) )
stream.end() stream.end()
assert.strictEqual(exitCode, 1) assert.strictEqual(exitCode, 1)
assert.strictEqual( assert.strictEqual(
consoleErrorSpy.calledWith( consoleErrorSpy.calledWith(
chalk.bold.red('Error:') + chalk.bold.red("Error:") +
` The challenge doesn't exist yet: ${invalidChallenge}.` ` The challenge doesn't exist yet: ${invalidChallenge}.`,
), ),
true true,
) )
}) })
await t.test('fails with solution that already exist', async () => { await t.test("fails with solution that already exist", async () => {
sinon.stub(console, 'error').value(() => {}) sinon.stub(console, "error").value(() => {})
const consoleErrorSpy = sinon.spy(console, 'error') const consoleErrorSpy = sinon.spy(console, "error")
const stream = new PassThrough() const stream = new PassThrough()
const invalidSolution = 'function' const invalidSolution = "function"
const inputInvalidSolution = `--solution=${invalidSolution}` const inputInvalidSolution = `--solution=${invalidSolution}`
const exitCode = await cli.run( const exitCode = await cli.run(
[ [
@ -122,30 +122,30 @@ Created by [@${githubUser}](https://github.com/${githubUser}) on ${dateString}.
inputChallenge, inputChallenge,
inputGitHubUser, inputGitHubUser,
inputLanguage, inputLanguage,
inputInvalidSolution inputInvalidSolution,
], ],
{ {
stdin: process.stdin, stdin: process.stdin,
stdout: stream, stdout: stream,
stderr: stream stderr: stream,
} },
) )
stream.end() stream.end()
assert.strictEqual(exitCode, 1) assert.strictEqual(exitCode, 1)
assert.strictEqual( assert.strictEqual(
consoleErrorSpy.calledWith( consoleErrorSpy.calledWith(
chalk.bold.red('Error:') + chalk.bold.red("Error:") +
` The solution already exists: ${invalidSolution}.` ` The solution already exists: ${invalidSolution}.`,
), ),
true true,
) )
}) })
await t.test('fails with invalid language', async () => { await t.test("fails with invalid language", async () => {
sinon.stub(console, 'error').value(() => {}) sinon.stub(console, "error").value(() => {})
const consoleErrorSpy = sinon.spy(console, 'error') const consoleErrorSpy = sinon.spy(console, "error")
const stream = new PassThrough() const stream = new PassThrough()
const invalidLanguage = 'invalid' const invalidLanguage = "invalid"
const inputInvalidLanguage = `--language=${invalidLanguage}` const inputInvalidLanguage = `--language=${invalidLanguage}`
const exitCode = await cli.run( const exitCode = await cli.run(
[ [
@ -153,32 +153,32 @@ Created by [@${githubUser}](https://github.com/${githubUser}) on ${dateString}.
inputChallenge, inputChallenge,
inputGitHubUser, inputGitHubUser,
inputSolution, inputSolution,
inputInvalidLanguage inputInvalidLanguage,
], ],
{ {
stdin: process.stdin, stdin: process.stdin,
stdout: stream, stdout: stream,
stderr: stream stderr: stream,
} },
) )
stream.end() stream.end()
assert.strictEqual(exitCode, 1) assert.strictEqual(exitCode, 1)
assert.strictEqual( assert.strictEqual(
consoleErrorSpy.calledWith( consoleErrorSpy.calledWith(
chalk.bold.red('Error:') + chalk.bold.red("Error:") +
' This programming language is not supported yet.' " This programming language is not supported yet.",
), ),
true true,
) )
}) })
await t.test('fails without options', async () => { await t.test("fails without options", async () => {
const stream = new PassThrough() const stream = new PassThrough()
const promise = getStream(stream) const promise = getStream(stream)
const exitCode = await cli.run(input, { const exitCode = await cli.run(input, {
stdin: process.stdin, stdin: process.stdin,
stdout: stream, stdout: stream,
stderr: stream stderr: stream,
}) })
stream.end() stream.end()
assert.strictEqual(exitCode, 1) assert.strictEqual(exitCode, 1)

View File

@ -1,43 +1,43 @@
import { Command, Option } from 'clipanion' import { Command, Option } from "clipanion"
import * as typanion from 'typanion' import * as typanion from "typanion"
import chalk from 'chalk' import chalk from "chalk"
import { Challenge } from '../../services/Challenge.js' import { Challenge } from "../../services/Challenge.js"
export class GenerateChallengeCommand extends Command { export class GenerateChallengeCommand extends Command {
public static override paths = [['generate', 'challenge']] public static override paths = [["generate", "challenge"]]
public static override usage = { public static override usage = {
description: 'Create the basic files needed for a new challenge.' description: "Create the basic files needed for a new challenge.",
} }
public challenge = Option.String('--challenge', { public challenge = Option.String("--challenge", {
description: 'The new challenge name to generate.', description: "The new challenge name to generate.",
required: true, required: true,
validator: typanion.isString() validator: typanion.isString(),
}) })
public githubUser = Option.String('--github-user', { public githubUser = Option.String("--github-user", {
description: 'Your GitHub user.', description: "Your GitHub user.",
required: true, required: true,
validator: typanion.isString() validator: typanion.isString(),
}) })
public async execute(): Promise<number> { public async execute(): Promise<number> {
try { try {
const challenge = await Challenge.generate({ const challenge = await Challenge.generate({
name: this.challenge, name: this.challenge,
githubUser: this.githubUser githubUser: this.githubUser,
}) })
console.log( console.log(
`${chalk.bold.green('Success:')} created the new challenge at ${ `${chalk.bold.green("Success:")} created the new challenge at ${
challenge.path challenge.path
}.` }.`,
) )
return 0 return 0
} catch (error) { } catch (error) {
if (error instanceof Error) { if (error instanceof Error) {
console.error(`${chalk.bold.red('Error:')} ${error.message}`) console.error(`${chalk.bold.red("Error:")} ${error.message}`)
} }
return 1 return 1
} }

View File

@ -1,38 +1,38 @@
import { Command, Option } from 'clipanion' import { Command, Option } from "clipanion"
import * as typanion from 'typanion' import * as typanion from "typanion"
import chalk from 'chalk' import chalk from "chalk"
import { Solution } from '../../services/Solution.js' import { Solution } from "../../services/Solution.js"
export class GenerateSolutionCommand extends Command { export class GenerateSolutionCommand extends Command {
public static override paths = [['generate', 'solution']] public static override paths = [["generate", "solution"]]
public static override usage = { public static override usage = {
description: 'Create the basic files needed for a new solution.' description: "Create the basic files needed for a new solution.",
} }
public challenge = Option.String('--challenge', { public challenge = Option.String("--challenge", {
description: 'The challenge name you want to generate a solution for.', description: "The challenge name you want to generate a solution for.",
required: true, required: true,
validator: typanion.isString() validator: typanion.isString(),
}) })
public githubUser = Option.String('--github-user', { public githubUser = Option.String("--github-user", {
description: 'Your GitHub user.', description: "Your GitHub user.",
required: true, required: true,
validator: typanion.isString() validator: typanion.isString(),
}) })
public solutionName = Option.String('--solution', { public solutionName = Option.String("--solution", {
description: 'The new solution name to generate.', description: "The new solution name to generate.",
required: true, required: true,
validator: typanion.isString() validator: typanion.isString(),
}) })
public programmingLanguage = Option.String('--language', { public programmingLanguage = Option.String("--language", {
description: 'The programming language to use to solve the challenge.', description: "The programming language to use to solve the challenge.",
required: true, required: true,
validator: typanion.isString() validator: typanion.isString(),
}) })
public async execute(): Promise<number> { public async execute(): Promise<number> {
@ -41,17 +41,17 @@ export class GenerateSolutionCommand extends Command {
name: this.solutionName, name: this.solutionName,
githubUser: this.githubUser, githubUser: this.githubUser,
challengeName: this.challenge, challengeName: this.challenge,
programmingLanguageName: this.programmingLanguage programmingLanguageName: this.programmingLanguage,
}) })
console.log( console.log(
`${chalk.bold.green('Success:')} created the new solution at ${ `${chalk.bold.green("Success:")} created the new solution at ${
solution.path solution.path
}.` }.`,
) )
return 0 return 0
} catch (error) { } catch (error) {
if (error instanceof Error) { if (error instanceof Error) {
console.error(`${chalk.bold.red('Error:')} ${error.message}`) console.error(`${chalk.bold.red("Error:")} ${error.message}`)
} }
return 1 return 1
} }

View File

@ -1,38 +1,38 @@
import test from 'node:test' import test from "node:test"
import assert from 'node:assert/strict' import assert from "node:assert/strict"
import { PassThrough } from 'node:stream' import { PassThrough } from "node:stream"
import path from 'node:path' import path from "node:path"
import sinon from 'sinon' import sinon from "sinon"
import chalk from 'chalk' import chalk from "chalk"
import { cli } from '../../../cli.js' import { cli } from "../../../cli.js"
const input = ['run', 'solution'] const input = ["run", "solution"]
const challenge = 'hello-world' const challenge = "hello-world"
const language = 'c' const language = "c"
const solution = 'function' const solution = "function"
const inputPath = path.join( const inputPath = path.join(
process.cwd(), process.cwd(),
'challenges', "challenges",
challenge, challenge,
'test', "test",
'1', "1",
'input.txt' "input.txt",
) )
const inputChallenge = `--challenge=${challenge}` const inputChallenge = `--challenge=${challenge}`
const inputLanguage = `--language=${language}` const inputLanguage = `--language=${language}`
const inputSolution = `--solution=${solution}` const inputSolution = `--solution=${solution}`
const inputInputPath = `--input-path=${inputPath}` const inputInputPath = `--input-path=${inputPath}`
await test('programming-challenges run solution', async (t) => { await test("programming-challenges run solution", async (t) => {
t.afterEach(() => { t.afterEach(() => {
sinon.restore() sinon.restore()
}) })
await t.test('succeeds', async () => { await t.test("succeeds", async () => {
sinon.stub(console, 'log').value(() => {}) sinon.stub(console, "log").value(() => {})
const consoleLogSpy = sinon.spy(console, 'log') const consoleLogSpy = sinon.spy(console, "log")
const stream = new PassThrough() const stream = new PassThrough()
const exitCode = await cli.run( const exitCode = await cli.run(
[ [
@ -41,13 +41,13 @@ await test('programming-challenges run solution', async (t) => {
inputSolution, inputSolution,
inputLanguage, inputLanguage,
inputInputPath, inputInputPath,
'--output' "--output",
], ],
{ {
stdin: process.stdin, stdin: process.stdin,
stdout: stream, stdout: stream,
stderr: stream stderr: stream,
} },
) )
stream.end() stream.end()
assert.strictEqual(exitCode, 0) assert.strictEqual(exitCode, 0)
@ -55,10 +55,10 @@ await test('programming-challenges run solution', async (t) => {
}) })
await t.test("fails with solution that doesn't exist", async () => { await t.test("fails with solution that doesn't exist", async () => {
sinon.stub(console, 'error').value(() => {}) sinon.stub(console, "error").value(() => {})
const consoleErrorSpy = sinon.spy(console, 'error') const consoleErrorSpy = sinon.spy(console, "error")
const stream = new PassThrough() const stream = new PassThrough()
const invalidSolution = 'invalid' const invalidSolution = "invalid"
const inputInvalidSolution = `--solution=${invalidSolution}` const inputInvalidSolution = `--solution=${invalidSolution}`
const exitCode = await cli.run( const exitCode = await cli.run(
[ [
@ -66,29 +66,29 @@ await test('programming-challenges run solution', async (t) => {
inputChallenge, inputChallenge,
inputInvalidSolution, inputInvalidSolution,
inputLanguage, inputLanguage,
inputInputPath inputInputPath,
], ],
{ {
stdin: process.stdin, stdin: process.stdin,
stdout: stream, stdout: stream,
stderr: stream stderr: stream,
} },
) )
stream.end() stream.end()
assert.strictEqual(exitCode, 1) assert.strictEqual(exitCode, 1)
assert.strictEqual( assert.strictEqual(
consoleErrorSpy.calledWith( consoleErrorSpy.calledWith(
chalk.bold.red('Error:') + ' The solution was not found.' chalk.bold.red("Error:") + " The solution was not found.",
), ),
true true,
) )
}) })
await t.test('fails with invalid language', async () => { await t.test("fails with invalid language", async () => {
sinon.stub(console, 'error').value(() => {}) sinon.stub(console, "error").value(() => {})
const consoleErrorSpy = sinon.spy(console, 'error') const consoleErrorSpy = sinon.spy(console, "error")
const stream = new PassThrough() const stream = new PassThrough()
const invalidLanguage = 'invalid' const invalidLanguage = "invalid"
const inputInvalidLanguage = `--language=${invalidLanguage}` const inputInvalidLanguage = `--language=${invalidLanguage}`
const exitCode = await cli.run( const exitCode = await cli.run(
[ [
@ -96,30 +96,30 @@ await test('programming-challenges run solution', async (t) => {
inputChallenge, inputChallenge,
inputSolution, inputSolution,
inputInvalidLanguage, inputInvalidLanguage,
inputInputPath inputInputPath,
], ],
{ {
stdin: process.stdin, stdin: process.stdin,
stdout: stream, stdout: stream,
stderr: stream stderr: stream,
} },
) )
stream.end() stream.end()
assert.strictEqual(exitCode, 1) assert.strictEqual(exitCode, 1)
assert.strictEqual( assert.strictEqual(
consoleErrorSpy.calledWith( consoleErrorSpy.calledWith(
chalk.bold.red('Error:') + chalk.bold.red("Error:") +
' This programming language is not supported yet.' " This programming language is not supported yet.",
), ),
true true,
) )
}) })
await t.test('fails with invalid `input-path`', async () => { await t.test("fails with invalid `input-path`", async () => {
sinon.stub(console, 'error').value(() => {}) sinon.stub(console, "error").value(() => {})
const consoleErrorSpy = sinon.spy(console, 'error') const consoleErrorSpy = sinon.spy(console, "error")
const stream = new PassThrough() const stream = new PassThrough()
const invalidInputPath = 'invalid' const invalidInputPath = "invalid"
const inputInvalidInputPath = `--input-path=${invalidInputPath}` const inputInvalidInputPath = `--input-path=${invalidInputPath}`
const inputPath = path.resolve(process.cwd(), invalidInputPath) const inputPath = path.resolve(process.cwd(), invalidInputPath)
const exitCode = await cli.run( const exitCode = await cli.run(
@ -128,22 +128,22 @@ await test('programming-challenges run solution', async (t) => {
inputChallenge, inputChallenge,
inputSolution, inputSolution,
inputLanguage, inputLanguage,
inputInvalidInputPath inputInvalidInputPath,
], ],
{ {
stdin: process.stdin, stdin: process.stdin,
stdout: stream, stdout: stream,
stderr: stream stderr: stream,
} },
) )
stream.end() stream.end()
assert.strictEqual(exitCode, 1) assert.strictEqual(exitCode, 1)
assert.strictEqual( assert.strictEqual(
consoleErrorSpy.calledWith( consoleErrorSpy.calledWith(
chalk.bold.red('Error:') + chalk.bold.red("Error:") +
` The \`input-path\` doesn't exist: ${inputPath}.` ` The \`input-path\` doesn't exist: ${inputPath}.`,
), ),
true true,
) )
}) })
}) })

View File

@ -1,125 +1,125 @@
import test from 'node:test' import test from "node:test"
import assert from 'node:assert/strict' import assert from "node:assert/strict"
import { PassThrough } from 'node:stream' import { PassThrough } from "node:stream"
import sinon from 'sinon' import sinon from "sinon"
import chalk from 'chalk' import chalk from "chalk"
import { cli } from '../../../cli.js' import { cli } from "../../../cli.js"
import { SolutionTestsResult } from '../../../services/SolutionTestsResult.js' import { SolutionTestsResult } from "../../../services/SolutionTestsResult.js"
const input = ['run', 'test'] const input = ["run", "test"]
const challenge = 'hello-world' const challenge = "hello-world"
const language = 'c' const language = "c"
const solution = 'function' const solution = "function"
const inputChallenge = `--challenge=${challenge}` const inputChallenge = `--challenge=${challenge}`
const inputLanguage = `--language=${language}` const inputLanguage = `--language=${language}`
const inputSolution = `--solution=${solution}` const inputSolution = `--solution=${solution}`
await test('programming-challenges run test', async (t) => { await test("programming-challenges run test", async (t) => {
t.afterEach(() => { t.afterEach(() => {
sinon.restore() sinon.restore()
}) })
await t.test('succeeds', async () => { await t.test("succeeds", async () => {
sinon.stub(console, 'log').value(() => {}) sinon.stub(console, "log").value(() => {})
const consoleLogSpy = sinon.spy(console, 'log') const consoleLogSpy = sinon.spy(console, "log")
const stream = new PassThrough() const stream = new PassThrough()
const exitCode = await cli.run( const exitCode = await cli.run(
[...input, inputChallenge, inputSolution, inputLanguage], [...input, inputChallenge, inputSolution, inputLanguage],
{ {
stdin: process.stdin, stdin: process.stdin,
stdout: stream, stdout: stream,
stderr: stream stderr: stream,
} },
) )
stream.end() stream.end()
assert.strictEqual(exitCode, 0) assert.strictEqual(exitCode, 0)
assert.strictEqual( assert.strictEqual(
consoleLogSpy.calledWith( consoleLogSpy.calledWith(
`${chalk.bold('Name:')} ${challenge}/${language}/${solution}\n` `${chalk.bold("Name:")} ${challenge}/${language}/${solution}\n`,
), ),
true true,
) )
assert.strictEqual( assert.strictEqual(
consoleLogSpy.calledWith( consoleLogSpy.calledWith(
`${chalk.bold('Tests:')} ${chalk.bold.green('3 passed')}, 3 total` `${chalk.bold("Tests:")} ${chalk.bold.green("3 passed")}, 3 total`,
), ),
true true,
) )
assert.strictEqual( assert.strictEqual(
consoleLogSpy.calledWith(SolutionTestsResult.SUCCESS_MESSAGE), consoleLogSpy.calledWith(SolutionTestsResult.SUCCESS_MESSAGE),
true true,
) )
}) })
await t.test("fails with solution that doesn't exist", async () => { await t.test("fails with solution that doesn't exist", async () => {
sinon.stub(console, 'error').value(() => {}) sinon.stub(console, "error").value(() => {})
const consoleErrorSpy = sinon.spy(console, 'error') const consoleErrorSpy = sinon.spy(console, "error")
const stream = new PassThrough() const stream = new PassThrough()
const invalidSolution = 'invalid' const invalidSolution = "invalid"
const inputInvalidSolution = `--solution=${invalidSolution}` const inputInvalidSolution = `--solution=${invalidSolution}`
const exitCode = await cli.run( const exitCode = await cli.run(
[...input, inputChallenge, inputInvalidSolution, inputLanguage], [...input, inputChallenge, inputInvalidSolution, inputLanguage],
{ {
stdin: process.stdin, stdin: process.stdin,
stdout: stream, stdout: stream,
stderr: stream stderr: stream,
} },
) )
stream.end() stream.end()
assert.strictEqual(exitCode, 1) assert.strictEqual(exitCode, 1)
assert.strictEqual( assert.strictEqual(
consoleErrorSpy.calledWith( consoleErrorSpy.calledWith(
chalk.bold.red('Error:') + ' The solution was not found.' chalk.bold.red("Error:") + " The solution was not found.",
), ),
true true,
) )
}) })
await t.test('fails with invalid language', async () => { await t.test("fails with invalid language", async () => {
sinon.stub(console, 'error').value(() => {}) sinon.stub(console, "error").value(() => {})
const consoleErrorSpy = sinon.spy(console, 'error') const consoleErrorSpy = sinon.spy(console, "error")
const stream = new PassThrough() const stream = new PassThrough()
const invalidLanguage = 'invalid' const invalidLanguage = "invalid"
const inputInvalidLanguage = `--language=${invalidLanguage}` const inputInvalidLanguage = `--language=${invalidLanguage}`
const exitCode = await cli.run( const exitCode = await cli.run(
[...input, inputChallenge, inputSolution, inputInvalidLanguage], [...input, inputChallenge, inputSolution, inputInvalidLanguage],
{ {
stdin: process.stdin, stdin: process.stdin,
stdout: stream, stdout: stream,
stderr: stream stderr: stream,
} },
) )
stream.end() stream.end()
assert.strictEqual(exitCode, 1) assert.strictEqual(exitCode, 1)
assert.strictEqual( assert.strictEqual(
consoleErrorSpy.calledWith( consoleErrorSpy.calledWith(
chalk.bold.red('Error:') + chalk.bold.red("Error:") +
' This programming language is not supported yet.' " This programming language is not supported yet.",
), ),
true true,
) )
}) })
await t.test('fails without options', async () => { await t.test("fails without options", async () => {
sinon.stub(console, 'error').value(() => {}) sinon.stub(console, "error").value(() => {})
const consoleErrorSpy = sinon.spy(console, 'error') const consoleErrorSpy = sinon.spy(console, "error")
const stream = new PassThrough() const stream = new PassThrough()
const exitCode = await cli.run(input, { const exitCode = await cli.run(input, {
stdin: process.stdin, stdin: process.stdin,
stdout: stream, stdout: stream,
stderr: stream stderr: stream,
}) })
stream.end() stream.end()
assert.strictEqual(exitCode, 1) assert.strictEqual(exitCode, 1)
assert.strictEqual( assert.strictEqual(
consoleErrorSpy.calledWith( consoleErrorSpy.calledWith(
`${chalk.bold.red( `${chalk.bold.red(
'Error:' "Error:",
)} You must specify all the options (\`--challenge\`, \`--solution\`, \`--language\`).` )} You must specify all the options (\`--challenge\`, \`--solution\`, \`--language\`).`,
), ),
true true,
) )
}) })
}) })

View File

@ -1,48 +1,48 @@
import path from 'node:path' import path from "node:path"
import fs from 'node:fs' import fs from "node:fs"
import { Command, Option } from 'clipanion' import { Command, Option } from "clipanion"
import * as typanion from 'typanion' import * as typanion from "typanion"
import chalk from 'chalk' import chalk from "chalk"
import { isExistingPath } from '../../utils/isExistingPath.js' import { isExistingPath } from "../../utils/isExistingPath.js"
import { template } from '../../services/Template.js' import { template } from "../../services/Template.js"
import { Solution } from '../../services/Solution.js' import { Solution } from "../../services/Solution.js"
import { TemporaryFolder } from '../../services/TemporaryFolder.js' import { TemporaryFolder } from "../../services/TemporaryFolder.js"
export class RunSolutionCommand extends Command { export class RunSolutionCommand extends Command {
public static override paths = [['run', 'solution']] public static override paths = [["run", "solution"]]
public static override usage = { public static override usage = {
description: 'Run the solution with the given `input.txt` file.' description: "Run the solution with the given `input.txt` file.",
} }
public programmingLanguage = Option.String('--language', { public programmingLanguage = Option.String("--language", {
description: 'The programming language used to solve the challenge.', description: "The programming language used to solve the challenge.",
required: true, required: true,
validator: typanion.isString() validator: typanion.isString(),
}) })
public challenge = Option.String('--challenge', { public challenge = Option.String("--challenge", {
description: 'The challenge name where you want to run your solution.', description: "The challenge name where you want to run your solution.",
required: true, required: true,
validator: typanion.isString() validator: typanion.isString(),
}) })
public solutionName = Option.String('--solution', { public solutionName = Option.String("--solution", {
description: 'The solution name to run.', description: "The solution name to run.",
required: true, required: true,
validator: typanion.isString() validator: typanion.isString(),
}) })
public inputPathUser = Option.String('--input-path', { public inputPathUser = Option.String("--input-path", {
description: 'The input file path to use.', description: "The input file path to use.",
required: true, required: true,
validator: typanion.isString() validator: typanion.isString(),
}) })
public output = Option.Boolean('--output', false, { public output = Option.Boolean("--output", false, {
description: 'Display the output of the solution.' description: "Display the output of the solution.",
}) })
public async execute(): Promise<number> { public async execute(): Promise<number> {
@ -50,24 +50,24 @@ export class RunSolutionCommand extends Command {
try { try {
await TemporaryFolder.cleanAll() await TemporaryFolder.cleanAll()
await template.verifySupportedProgrammingLanguage( await template.verifySupportedProgrammingLanguage(
this.programmingLanguage this.programmingLanguage,
) )
const solution = await Solution.get({ const solution = await Solution.get({
name: this.solutionName, name: this.solutionName,
challengeName: this.challenge, challengeName: this.challenge,
programmingLanguageName: this.programmingLanguage programmingLanguageName: this.programmingLanguage,
}) })
const inputPath = path.resolve(process.cwd(), this.inputPathUser) const inputPath = path.resolve(process.cwd(), this.inputPathUser)
if (!(await isExistingPath(inputPath))) { if (!(await isExistingPath(inputPath))) {
throw new Error(`The \`input-path\` doesn't exist: ${inputPath}.`) throw new Error(`The \`input-path\` doesn't exist: ${inputPath}.`)
} }
const input = await fs.promises.readFile(inputPath, { encoding: 'utf-8' }) const input = await fs.promises.readFile(inputPath, { encoding: "utf-8" })
await solution.run(input, this.output) await solution.run(input, this.output)
await TemporaryFolder.cleanAll() await TemporaryFolder.cleanAll()
return 0 return 0
} catch (error) { } catch (error) {
if (error instanceof Error) { if (error instanceof Error) {
console.error(`${chalk.bold.red('Error:')} ${error.message}`) console.error(`${chalk.bold.red("Error:")} ${error.message}`)
} }
await TemporaryFolder.cleanAll() await TemporaryFolder.cleanAll()
return 1 return 1

View File

@ -1,47 +1,47 @@
import { Command, Option } from 'clipanion' import { Command, Option } from "clipanion"
import * as typanion from 'typanion' import * as typanion from "typanion"
import chalk from 'chalk' import chalk from "chalk"
import { Solution } from '../../services/Solution.js' import { Solution } from "../../services/Solution.js"
import { GitAffected } from '../../services/GitAffected.js' import { GitAffected } from "../../services/GitAffected.js"
import { template } from '../../services/Template.js' import { template } from "../../services/Template.js"
import { Test } from '../../services/Test.js' import { Test } from "../../services/Test.js"
import { SolutionTestsResult } from '../../services/SolutionTestsResult.js' import { SolutionTestsResult } from "../../services/SolutionTestsResult.js"
import { TemporaryFolder } from '../../services/TemporaryFolder.js' import { TemporaryFolder } from "../../services/TemporaryFolder.js"
export class RunTestCommand extends Command { export class RunTestCommand extends Command {
public static override paths = [['run', 'test']] public static override paths = [["run", "test"]]
public static override usage = { public static override usage = {
description: description:
'Test if the solution is correct and display where it succeeds and fails.' "Test if the solution is correct and display where it succeeds and fails.",
} }
public programmingLanguage = Option.String('--language', { public programmingLanguage = Option.String("--language", {
description: 'The programming language used to solve the challenge.', description: "The programming language used to solve the challenge.",
validator: typanion.isString() validator: typanion.isString(),
}) })
public challenge = Option.String('--challenge', { public challenge = Option.String("--challenge", {
description: 'The challenge name where you want to test your solution.', description: "The challenge name where you want to test your solution.",
validator: typanion.isString() validator: typanion.isString(),
}) })
public solutionName = Option.String('--solution', { public solutionName = Option.String("--solution", {
description: 'The solution name to run.', description: "The solution name to run.",
validator: typanion.isString() validator: typanion.isString(),
}) })
public affected = Option.Boolean('--affected', false, { public affected = Option.Boolean("--affected", false, {
description: 'Only run the tests for the affected files in `git`.' description: "Only run the tests for the affected files in `git`.",
}) })
public all = Option.Boolean('--all', false, { public all = Option.Boolean("--all", false, {
description: 'Run the tests for all the solutions.' description: "Run the tests for all the solutions.",
}) })
public base = Option.String('--base', { public base = Option.String("--base", {
description: 'Base of the current branch (usually master).' description: "Base of the current branch (usually master).",
}) })
public async execute(): Promise<number> { public async execute(): Promise<number> {
@ -50,18 +50,20 @@ export class RunTestCommand extends Command {
await TemporaryFolder.cleanAll() await TemporaryFolder.cleanAll()
if (this.programmingLanguage != null) { if (this.programmingLanguage != null) {
await template.verifySupportedProgrammingLanguage( await template.verifySupportedProgrammingLanguage(
this.programmingLanguage this.programmingLanguage,
) )
} }
if (this.all) { if (this.all) {
const solutions = await Solution.getManyByProgrammingLanguages( const solutions = await Solution.getManyByProgrammingLanguages(
this.programmingLanguage != null ? [this.programmingLanguage] : undefined this.programmingLanguage != null
? [this.programmingLanguage]
: undefined,
) )
return await Test.runManyWithSolutions(solutions) return await Test.runManyWithSolutions(solutions)
} }
if (this.affected) { if (this.affected) {
const gitAffected = new GitAffected({ const gitAffected = new GitAffected({
base: this.base base: this.base,
}) })
const solutions = await gitAffected.getAffectedSolutionsFromGit() const solutions = await gitAffected.getAffectedSolutionsFromGit()
return await Test.runManyWithSolutions(solutions) return await Test.runManyWithSolutions(solutions)
@ -72,28 +74,28 @@ export class RunTestCommand extends Command {
this.programmingLanguage == null this.programmingLanguage == null
) { ) {
throw new Error( throw new Error(
'You must specify all the options (`--challenge`, `--solution`, `--language`).' "You must specify all the options (`--challenge`, `--solution`, `--language`).",
) )
} }
const solution = await Solution.get({ const solution = await Solution.get({
name: this.solutionName, name: this.solutionName,
challengeName: this.challenge, challengeName: this.challenge,
programmingLanguageName: this.programmingLanguage programmingLanguageName: this.programmingLanguage,
}) })
const result = await solution.test() const result = await solution.test()
result.print({ result.print({
shouldPrintBenchmark: true, shouldPrintBenchmark: true,
shouldPrintTableResult: true shouldPrintTableResult: true,
}) })
await TemporaryFolder.cleanAll() await TemporaryFolder.cleanAll()
if (result.isSuccess) { if (result.isSuccess) {
console.log(SolutionTestsResult.SUCCESS_MESSAGE) console.log(SolutionTestsResult.SUCCESS_MESSAGE)
return 0 return 0
} }
return 1 return 1
} catch (error) { } catch (error) {
if (error instanceof Error) { if (error instanceof Error) {
console.error(`${chalk.bold.red('Error:')} ${error.message}`) console.error(`${chalk.bold.red("Error:")} ${error.message}`)
} }
await TemporaryFolder.cleanAll() await TemporaryFolder.cleanAll()
return 1 return 1

View File

@ -1,40 +1,40 @@
import path from 'node:path' import path from "node:path"
import fs from 'node:fs' import fs from "node:fs"
import { Command, Option } from 'clipanion' import { Command, Option } from "clipanion"
import * as typanion from 'typanion' import * as typanion from "typanion"
import chalk from 'chalk' import chalk from "chalk"
import { template } from '../../services/Template.js' import { template } from "../../services/Template.js"
import { Challenge } from '../../services/Challenge.js' import { Challenge } from "../../services/Challenge.js"
export class SearchCommand extends Command { export class SearchCommand extends Command {
public static override paths = [['search']] public static override paths = [["search"]]
public static override usage = { public static override usage = {
description: 'Search challenges in the programming language specified.' description: "Search challenges in the programming language specified.",
} }
public solved = Option.Boolean('--solved', false, { public solved = Option.Boolean("--solved", false, {
description: description:
'Challenges which have already been solved (at least with one solution).' "Challenges which have already been solved (at least with one solution).",
}) })
public programmingLanguage = Option.String('--language', { public programmingLanguage = Option.String("--language", {
description: 'The programming language used to solve the challenge.', description: "The programming language used to solve the challenge.",
required: true, required: true,
validator: typanion.isString() validator: typanion.isString(),
}) })
public async execute(): Promise<number> { public async execute(): Promise<number> {
try { try {
await template.verifySupportedProgrammingLanguage( await template.verifySupportedProgrammingLanguage(
this.programmingLanguage this.programmingLanguage,
) )
const challenges = await Challenge.getChallenges() const challenges = await Challenge.getChallenges()
const challengesResult: Challenge[] = [] const challengesResult: Challenge[] = []
for (const challenge of challenges) { for (const challenge of challenges) {
const solutionsPath = path.join(challenge.path, 'solutions') const solutionsPath = path.join(challenge.path, "solutions")
const solutions = await fs.promises.readdir(solutionsPath) const solutions = await fs.promises.readdir(solutionsPath)
if ( if (
(!this.solved && !solutions.includes(this.programmingLanguage)) || (!this.solved && !solutions.includes(this.programmingLanguage)) ||
@ -44,8 +44,8 @@ export class SearchCommand extends Command {
} }
} }
const message = this.solved const message = this.solved
? 'Challenges already solved' ? "Challenges already solved"
: 'Challenges not yet solved' : "Challenges not yet solved"
console.log(`${message} in ${chalk.bold(this.programmingLanguage)}:`) console.log(`${message} in ${chalk.bold(this.programmingLanguage)}:`)
for (const challenge of challengesResult) { for (const challenge of challengesResult) {
console.log(` - ${challenge.name}`) console.log(` - ${challenge.name}`)
@ -53,7 +53,7 @@ export class SearchCommand extends Command {
return 0 return 0
} catch (error) { } catch (error) {
if (error instanceof Error) { if (error instanceof Error) {
console.error(`${chalk.bold.red('Error:')} ${error.message}`) console.error(`${chalk.bold.red("Error:")} ${error.message}`)
} }
return 1 return 1
} }

View File

@ -1,7 +1,7 @@
#!/usr/bin/env node #!/usr/bin/env node
import { Cli } from 'clipanion' import { Cli } from "clipanion"
import { cli } from './cli.js' import { cli } from "./cli.js"
const [, , ...arguments_] = process.argv const [, , ...arguments_] = process.argv

View File

@ -1,10 +1,10 @@
import fs from 'node:fs' import fs from "node:fs"
import { fileURLToPath } from 'node:url' 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 { isExistingPath } from "../utils/isExistingPath.js"
import { template } from './Template.js' import { template } from "./Template.js"
export interface ChallengeOptions { export interface ChallengeOptions {
name: string name: string
@ -15,7 +15,7 @@ export interface GenerateChallengeOptions extends ChallengeOptions {
} }
export class Challenge implements 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 name: string
public path: string public path: string
@ -33,7 +33,7 @@ export class Challenge implements ChallengeOptions {
} }
public static async generate( public static async generate(
options: GenerateChallengeOptions options: GenerateChallengeOptions,
): Promise<Challenge> { ): Promise<Challenge> {
const { name, githubUser } = options const { name, githubUser } = options
const challenge = new Challenge({ name }) const challenge = new Challenge({ name })
@ -42,13 +42,13 @@ export class Challenge implements ChallengeOptions {
} }
const isValidName = validateProjectName(name).validForNewPackages const isValidName = validateProjectName(name).validForNewPackages
if (!isValidName) { if (!isValidName) {
throw new Error('Invalid challenge name.') throw new Error("Invalid challenge name.")
} }
await fs.promises.mkdir(challenge.path) await fs.promises.mkdir(challenge.path)
await template.challenge({ await template.challenge({
destination: challenge.path, destination: challenge.path,
githubUser, githubUser,
name name,
}) })
return challenge return challenge
} }

View File

@ -1,18 +1,18 @@
import { execaCommand } from 'execa' import { execaCommand } from "execa"
import ms from 'ms' import ms from "ms"
import { parseCommandOutput } from '../utils/parseCommandOutput.js' import { parseCommandOutput } from "../utils/parseCommandOutput.js"
export interface DockerRunResult { export interface DockerRunResult {
stdout: string stdout: string
} }
export class Docker { 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 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( public static readonly MAXIMUM_TIMEOUT_MILLISECONDS = ms(
Docker.MAXIMUM_TIMEOUT Docker.MAXIMUM_TIMEOUT,
) )
public getContainerTag(id: string): string { public getContainerTag(id: string): string {
@ -23,7 +23,7 @@ export class Docker {
try { try {
const { stdout } = await execaCommand( const { stdout } = await execaCommand(
`docker images -q --filter=reference="${Docker.CONTAINER_BASE_TAG}:*"`, `docker images -q --filter=reference="${Docker.CONTAINER_BASE_TAG}:*"`,
{ shell: true } { shell: true },
) )
return parseCommandOutput(stdout) return parseCommandOutput(stdout)
} catch { } catch {
@ -37,8 +37,8 @@ export class Docker {
if (images.length === 0) { if (images.length === 0) {
return return
} }
await execaCommand(`docker rmi -f ${images.join(' ')}`, { await execaCommand(`docker rmi -f ${images.join(" ")}`, {
shell: true shell: true,
}) })
} catch {} } catch {}
} }
@ -55,8 +55,8 @@ export class Docker {
const subprocess = execaCommand( const subprocess = execaCommand(
`docker run --interactive --rm ${this.getContainerTag(id)}`, `docker run --interactive --rm ${this.getContainerTag(id)}`,
{ {
input input,
} },
) )
try { try {
const { stdout, stderr } = await subprocess const { stdout, stderr } = await subprocess
@ -64,12 +64,12 @@ export class Docker {
throw new Error(stderr) throw new Error(stderr)
} }
return { return {
stdout stdout,
} }
} catch (error: any) { } catch (error: any) {
if (error.exitCode === Docker.SIGSEGV_EXIT_CODE) { if (error.exitCode === Docker.SIGSEGV_EXIT_CODE) {
throw new Error( 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}`) throw new Error(`Docker run failed.\n${error.message as string}`)

View File

@ -1,8 +1,8 @@
import { execaCommand } from 'execa' import { execaCommand } from "execa"
import { Challenge } from './Challenge.js' import { Challenge } from "./Challenge.js"
import { Solution } from './Solution.js' import { Solution } from "./Solution.js"
import { parseCommandOutput } from '../utils/parseCommandOutput.js' import { parseCommandOutput } from "../utils/parseCommandOutput.js"
const solutionsRegex = 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)/ /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( public async getFilesUsingBaseAndHead(
base: string, base: string,
head: string head: string,
): Promise<string[]> { ): Promise<string[]> {
try { try {
const { stdout } = await execaCommand( const { stdout } = await execaCommand(
`git diff --name-only --relative ${base} ${head}` `git diff --name-only --relative ${base} ${head}`,
) )
return parseCommandOutput(stdout) return parseCommandOutput(stdout)
} catch { } catch {
@ -39,13 +39,13 @@ export class GitAffected implements GitAffectedOptions {
} }
public async getUncommittedFiles(): Promise<string[]> { public async getUncommittedFiles(): Promise<string[]> {
return await this.getFilesUsingBaseAndHead('HEAD', '.') return await this.getFilesUsingBaseAndHead("HEAD", ".")
} }
public async getLatestPushedCommit(): Promise<string> { public async getLatestPushedCommit(): Promise<string> {
const latestCommit = this.base != null ? '~1' : '' const latestCommit = this.base != null ? "~1" : ""
const { stdout } = await execaCommand( const { stdout } = await execaCommand(
`git rev-parse origin/master${latestCommit}` `git rev-parse origin/master${latestCommit}`,
) )
return stdout return stdout
} }
@ -53,12 +53,12 @@ export class GitAffected implements GitAffectedOptions {
public async getUnpushedFiles(): Promise<string[]> { public async getUnpushedFiles(): Promise<string[]> {
return await this.getFilesUsingBaseAndHead( return await this.getFilesUsingBaseAndHead(
await this.getLatestPushedCommit(), await this.getLatestPushedCommit(),
'.' ".",
) )
} }
public async getAffectedSolutionsFromFiles( public async getAffectedSolutionsFromFiles(
files: string[] files: string[],
): Promise<Solution[]> { ): Promise<Solution[]> {
const affectedSolutionsPaths = files.filter((filePath) => { const affectedSolutionsPaths = files.filter((filePath) => {
return solutionsRegex.test(filePath) return solutionsRegex.test(filePath)
@ -68,10 +68,10 @@ export class GitAffected implements GitAffectedOptions {
}) })
const affectedLanguages = affectedDockerPaths.map((filePath) => { const affectedLanguages = affectedDockerPaths.map((filePath) => {
const [, , programmingLanguageName] = filePath const [, , programmingLanguageName] = filePath
.replaceAll('\\', '/') .replaceAll("\\", "/")
.split('/') .split("/")
if (programmingLanguageName == null) { if (programmingLanguageName == null) {
throw new Error('programmingLanguageName is null') throw new Error("programmingLanguageName is null")
} }
return programmingLanguageName return programmingLanguageName
}) })
@ -80,19 +80,18 @@ export class GitAffected implements GitAffectedOptions {
}) })
const affectedChallengesFromInputOutput = affectedInputOutput.map( const affectedChallengesFromInputOutput = affectedInputOutput.map(
(filePath) => { (filePath) => {
const [, challengeName] = filePath.replaceAll('\\', '/').split('/') const [, challengeName] = filePath.replaceAll("\\", "/").split("/")
if (challengeName == null) { if (challengeName == null) {
throw new Error('challengeName is null') throw new Error("challengeName is null")
} }
return new Challenge({ name: challengeName }) return new Challenge({ name: challengeName })
} },
) )
const solutionsChallenges = await Solution.getManyByPaths( const solutionsChallenges = await Solution.getManyByPaths(
affectedSolutionsPaths affectedSolutionsPaths,
)
const solutionsDocker = await Solution.getManyByProgrammingLanguages(
affectedLanguages
) )
const solutionsDocker =
await Solution.getManyByProgrammingLanguages(affectedLanguages)
const solutions: Solution[] = [...solutionsDocker, ...solutionsChallenges] const solutions: Solution[] = [...solutionsDocker, ...solutionsChallenges]
for (const challenge of affectedChallengesFromInputOutput) { for (const challenge of affectedChallengesFromInputOutput) {
const solutionsByChallenge = await Solution.getManyByChallenge(challenge) const solutionsByChallenge = await Solution.getManyByChallenge(challenge)
@ -113,10 +112,10 @@ export class GitAffected implements GitAffectedOptions {
public async getAffectedSolutionsFromGit(): Promise<Solution[]> { public async getAffectedSolutionsFromGit(): Promise<Solution[]> {
let files = [ let files = [
...(await this.getUnpushedFiles()), ...(await this.getUnpushedFiles()),
...(await this.getUncommittedFiles()) ...(await this.getUncommittedFiles()),
] ]
if (this.base != null) { if (this.base != null) {
files.push(...(await this.getFilesUsingBaseAndHead(this.base, '.'))) files.push(...(await this.getFilesUsingBaseAndHead(this.base, ".")))
} }
files = Array.from(new Set(files)) files = Array.from(new Set(files))
return await this.getAffectedSolutionsFromFiles(files) return await this.getAffectedSolutionsFromFiles(files)

View File

@ -1,19 +1,19 @@
import { fileURLToPath } from 'node:url' import { fileURLToPath } from "node:url"
import path from 'node:path' import path from "node:path"
import fs from 'node:fs' import fs from "node:fs"
import { performance } from 'node:perf_hooks' import { performance } from "node:perf_hooks"
import chalk from 'chalk' import chalk from "chalk"
import ora from 'ora' import ora from "ora"
import { isExistingPath } from '../utils/isExistingPath.js' import { isExistingPath } from "../utils/isExistingPath.js"
import { Challenge } from './Challenge.js' import { Challenge } from "./Challenge.js"
import { copyDirectory } from '../utils/copyDirectory.js' import { copyDirectory } from "../utils/copyDirectory.js"
import { template } from './Template.js' import { template } from "./Template.js"
import { docker } from './Docker.js' import { docker } from "./Docker.js"
import { Test } from './Test.js' import { Test } from "./Test.js"
import { SolutionTestsResult } from './SolutionTestsResult.js' import { SolutionTestsResult } from "./SolutionTestsResult.js"
import { TemporaryFolder } from './TemporaryFolder.js' import { TemporaryFolder } from "./TemporaryFolder.js"
export interface GetSolutionOptions { export interface GetSolutionOptions {
programmingLanguageName: string programmingLanguageName: string
@ -45,9 +45,9 @@ export class Solution implements SolutionOptions {
this.name = name this.name = name
this.path = path.join( this.path = path.join(
challenge.path, challenge.path,
'solutions', "solutions",
programmingLanguageName, programmingLanguageName,
name name,
) )
this.temporaryFolder = new TemporaryFolder() this.temporaryFolder = new TemporaryFolder()
} }
@ -57,7 +57,7 @@ export class Solution implements SolutionOptions {
await copyDirectory(this.path, this.temporaryFolder.path) await copyDirectory(this.path, this.temporaryFolder.path)
await template.docker({ await template.docker({
programmingLanguage: this.programmingLanguageName, programmingLanguage: this.programmingLanguageName,
destination: this.temporaryFolder.path destination: this.temporaryFolder.path,
}) })
process.chdir(this.temporaryFolder.path) process.chdir(this.temporaryFolder.path)
try { try {
@ -74,16 +74,16 @@ export class Solution implements SolutionOptions {
public async run(input: string, output: boolean = false): Promise<void> { public async run(input: string, output: boolean = false): Promise<void> {
await this.setup() await this.setup()
const loader = ora('Running...').start() const loader = ora("Running...").start()
try { try {
const start = performance.now() const start = performance.now()
const { stdout } = await docker.run(input, this.temporaryFolder.id) const { stdout } = await docker.run(input, this.temporaryFolder.id)
const end = performance.now() const end = performance.now()
const elapsedTimeMilliseconds = end - start const elapsedTimeMilliseconds = end - start
loader.succeed(chalk.bold.green('Success!')) loader.succeed(chalk.bold.green("Success!"))
SolutionTestsResult.printBenchmark(elapsedTimeMilliseconds) SolutionTestsResult.printBenchmark(elapsedTimeMilliseconds)
if (output) { if (output) {
console.log(`${chalk.bold('Output:')}`) console.log(`${chalk.bold("Output:")}`)
console.log(stdout) console.log(stdout)
} }
} catch (error: any) { } catch (error: any) {
@ -101,7 +101,7 @@ export class Solution implements SolutionOptions {
const solution = new Solution({ const solution = new Solution({
name, name,
challenge, challenge,
programmingLanguageName programmingLanguageName,
}) })
if (await isExistingPath(solution.path)) { if (await isExistingPath(solution.path)) {
throw new Error(`The solution already exists: ${name}.`) throw new Error(`The solution already exists: ${name}.`)
@ -111,7 +111,7 @@ export class Solution implements SolutionOptions {
destination: solution.path, destination: solution.path,
githubUser, githubUser,
programmingLanguageName: solution.programmingLanguageName, programmingLanguageName: solution.programmingLanguageName,
name: solution.name name: solution.name,
}) })
return solution return solution
} }
@ -119,25 +119,25 @@ export class Solution implements SolutionOptions {
static async get(options: GetSolutionOptions): Promise<Solution> { static async get(options: GetSolutionOptions): Promise<Solution> {
const { name, challengeName, programmingLanguageName } = options const { name, challengeName, programmingLanguageName } = options
const challenge = new Challenge({ const challenge = new Challenge({
name: challengeName name: challengeName,
}) })
const solution = new Solution({ const solution = new Solution({
name, name,
challenge, challenge,
programmingLanguageName programmingLanguageName,
}) })
if (!(await isExistingPath(solution.path))) { if (!(await isExistingPath(solution.path))) {
throw new Error('The solution was not found.') throw new Error("The solution was not found.")
} }
return solution return solution
} }
static async getManyByChallenge(challenge: Challenge): Promise<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( const languagesSolution = (await fs.promises.readdir(solutionsPath)).filter(
(name) => { (name) => {
return name !== '.gitkeep' return name !== ".gitkeep"
} },
) )
const paths: string[] = [] const paths: string[] = []
for (const language of languagesSolution) { for (const language of languagesSolution) {
@ -152,21 +152,21 @@ export class Solution implements SolutionOptions {
} }
static async getManyByProgrammingLanguages( static async getManyByProgrammingLanguages(
programmingLanguages?: string[] programmingLanguages?: string[],
): Promise<Solution[]> { ): Promise<Solution[]> {
const languages = const languages =
programmingLanguages ?? (await template.getProgrammingLanguages()) programmingLanguages ?? (await template.getProgrammingLanguages())
const challengesPath = fileURLToPath( const challengesPath = fileURLToPath(
new URL('../../challenges', import.meta.url) new URL("../../challenges", import.meta.url),
) )
const challenges = await fs.promises.readdir(challengesPath) const challenges = await fs.promises.readdir(challengesPath)
const paths: string[] = [] const paths: string[] = []
for (const challenge of challenges) { for (const challenge of challenges) {
const solutionsPath = path.join(challengesPath, challenge, 'solutions') const solutionsPath = path.join(challengesPath, challenge, "solutions")
const languagesSolution = ( const languagesSolution = (
await fs.promises.readdir(solutionsPath) await fs.promises.readdir(solutionsPath)
).filter((name) => { ).filter((name) => {
return name !== '.gitkeep' && languages.includes(name) return name !== ".gitkeep" && languages.includes(name)
}) })
for (const language of languagesSolution) { for (const language of languagesSolution) {
const solutionPath = ( const solutionPath = (
@ -194,7 +194,7 @@ export class Solution implements SolutionOptions {
} }
return solutions.map((solution) => { return solutions.map((solution) => {
const [, challengeName, , programmingLanguageName, solutionName] = const [, challengeName, , programmingLanguageName, solutionName] =
solution.replaceAll('\\', '/').split('/') solution.replaceAll("\\", "/").split("/")
if ( if (
challengeName == null || challengeName == null ||
@ -206,10 +206,10 @@ export class Solution implements SolutionOptions {
return new Solution({ return new Solution({
challenge: new Challenge({ challenge: new Challenge({
name: challengeName name: challengeName,
}), }),
name: solutionName, name: solutionName,
programmingLanguageName programmingLanguageName,
}) })
}) })
} }

View File

@ -1,9 +1,9 @@
import logSymbols from 'log-symbols' import logSymbols from "log-symbols"
import chalk from 'chalk' import chalk from "chalk"
import { table } from 'table' import { table } from "table"
import type { Solution } from './Solution.js' import type { Solution } from "./Solution.js"
import type { Test } from './Test.js' import type { Test } from "./Test.js"
export interface SolutionTestsResultOptions { export interface SolutionTestsResultOptions {
tests: Test[] tests: Test[]
@ -22,7 +22,7 @@ export class SolutionTestsResult implements SolutionTestsResultOptions {
public isSuccess: boolean public isSuccess: boolean
public elapsedTimeMilliseconds: number public elapsedTimeMilliseconds: number
public static readonly SUCCESS_MESSAGE = `${chalk.bold.green( public static readonly SUCCESS_MESSAGE = `${chalk.bold.green(
'Success:' "Success:",
)} Tests passed! 🎉` )} Tests passed! 🎉`
constructor(options: SolutionTestsResultOptions) { constructor(options: SolutionTestsResultOptions) {
@ -40,14 +40,14 @@ export class SolutionTestsResult implements SolutionTestsResultOptions {
const { shouldPrintBenchmark = false, shouldPrintTableResult = false } = const { shouldPrintBenchmark = false, shouldPrintTableResult = false } =
options options
const name = `${this.solution.challenge.name}/${this.solution.programmingLanguageName}/${this.solution.name}` 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 = [ const tableResult = [
[ [
chalk.bold('N°'), chalk.bold("N°"),
chalk.bold('Input'), chalk.bold("Input"),
chalk.bold('Expected'), chalk.bold("Expected"),
chalk.bold('Received') chalk.bold("Received"),
] ],
] ]
let totalFailedTests = 0 let totalFailedTests = 0
let totalCorrectTests = 0 let totalCorrectTests = 0
@ -58,13 +58,13 @@ export class SolutionTestsResult implements SolutionTestsResultOptions {
totalCorrectTests += 1 totalCorrectTests += 1
} else { } else {
console.log(logSymbols.error, testLabel) console.log(logSymbols.error, testLabel)
const expected = test.output.split('\n').join('\n') const expected = test.output.split("\n").join("\n")
const received = test.stdout.split('\n').join('\n') const received = test.stdout.split("\n").join("\n")
tableResult.push([ tableResult.push([
test.testNumber.toString(), test.testNumber.toString(),
`"${test.input}"`, `"${test.input}"`,
`"${expected}"`, `"${expected}"`,
`"${received}"` `"${received}"`,
]) ])
totalFailedTests += 1 totalFailedTests += 1
} }
@ -78,19 +78,19 @@ export class SolutionTestsResult implements SolutionTestsResultOptions {
? chalk.bold.green(`${totalCorrectTests} passed`) ? chalk.bold.green(`${totalCorrectTests} passed`)
: chalk.bold.red(`${totalFailedTests} failed`) : chalk.bold.red(`${totalFailedTests} failed`)
console.log( console.log(
`${chalk.bold('Tests:')} ${testsResult}, ${this.tests.length} total` `${chalk.bold("Tests:")} ${testsResult}, ${this.tests.length} total`,
) )
if (shouldPrintBenchmark) { if (shouldPrintBenchmark) {
SolutionTestsResult.printBenchmark(this.elapsedTimeMilliseconds) SolutionTestsResult.printBenchmark(this.elapsedTimeMilliseconds)
} }
if (!isSuccess) { 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 { public static printBenchmark(elapsedTimeMilliseconds: number): void {
const elapsedTime = elapsedTimeMilliseconds / 1000 const elapsedTime = elapsedTimeMilliseconds / 1000
console.log(`${chalk.bold('Benchmark:')} ${elapsedTime} seconds`) console.log(`${chalk.bold("Benchmark:")} ${elapsedTime} seconds`)
} }
} }

View File

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

View File

@ -1,8 +1,8 @@
import { fileURLToPath } from 'node:url' import { fileURLToPath } from "node:url"
import fs from 'node:fs' import fs from "node:fs"
import crypto from 'node:crypto' import crypto from "node:crypto"
import { docker } from './Docker.js' import { docker } from "./Docker.js"
export class TemporaryFolder { export class TemporaryFolder {
public readonly id: string public readonly id: string
@ -24,7 +24,7 @@ export class TemporaryFolder {
public static async cleanAll(): Promise<void> { public static async cleanAll(): Promise<void> {
try { try {
const temporaryPath = fileURLToPath( 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 fs.promises.rm(temporaryPath, { recursive: true, force: true })
await docker.removeImages() await docker.removeImages()

View File

@ -1,11 +1,11 @@
import fs from 'node:fs' import fs from "node:fs"
import path from 'node:path' import path from "node:path"
import { performance } from 'node:perf_hooks' import { performance } from "node:perf_hooks"
import type { Solution } from './Solution.js' import type { Solution } from "./Solution.js"
import { docker } from './Docker.js' import { docker } from "./Docker.js"
import { SolutionTestsResult } from './SolutionTestsResult.js' import { SolutionTestsResult } from "./SolutionTestsResult.js"
import { TemporaryFolder } from './TemporaryFolder.js' import { TemporaryFolder } from "./TemporaryFolder.js"
export interface InputOutput { export interface InputOutput {
input: string input: string
@ -45,7 +45,7 @@ export class Test implements TestOptions {
} }
static async runAll(solution: Solution): Promise<SolutionTestsResult> { 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 testsFolders = await fs.promises.readdir(testsPath)
const testsNumbers = testsFolders.map((test) => { const testsNumbers = testsFolders.map((test) => {
return Number(test) return Number(test)
@ -63,11 +63,11 @@ export class Test implements TestOptions {
} }
static async getInputOutput(testPath: string): Promise<InputOutput> { static async getInputOutput(testPath: string): Promise<InputOutput> {
const inputPath = path.join(testPath, 'input.txt') const inputPath = path.join(testPath, "input.txt")
const outputPath = path.join(testPath, 'output.txt') const outputPath = path.join(testPath, "output.txt")
const input = await fs.promises.readFile(inputPath, { encoding: 'utf-8' }) const input = await fs.promises.readFile(inputPath, { encoding: "utf-8" })
const output = await fs.promises.readFile(outputPath, { const output = await fs.promises.readFile(outputPath, {
encoding: 'utf-8' encoding: "utf-8",
}) })
return { input, output } return { input, output }
} }
@ -101,7 +101,7 @@ export class Test implements TestOptions {
try { try {
const { stdout } = await docker.run( const { stdout } = await docker.run(
input, input,
options.solution.temporaryFolder.id options.solution.temporaryFolder.id,
) )
const test = new Test({ const test = new Test({
path: options.path, path: options.path,
@ -109,12 +109,12 @@ export class Test implements TestOptions {
input, input,
output, output,
stdout, stdout,
isSuccess: stdout === output isSuccess: stdout === output,
}) })
return test return test
} catch (error: any) { } catch (error: any) {
throw new Error( throw new Error(
`solution: ${options.solution.path}\n${error.message as string}\n` `solution: ${options.solution.path}\n${error.message as string}\n`,
) )
} }
} }

View File

@ -1,143 +1,143 @@
import test from 'node:test' import test from "node:test"
import assert from 'node:assert/strict' import assert from "node:assert/strict"
import crypto from 'node:crypto' import crypto from "node:crypto"
import sinon from 'sinon' import sinon from "sinon"
import { Challenge } from '../Challenge.js' import { Challenge } from "../Challenge.js"
import { GitAffected } from '../GitAffected.js' import { GitAffected } from "../GitAffected.js"
import { Solution } from '../Solution.js' import { Solution } from "../Solution.js"
import { parseCommandOutput } from '../../utils/parseCommandOutput.js' import { parseCommandOutput } from "../../utils/parseCommandOutput.js"
const gitAffected = new GitAffected() const gitAffected = new GitAffected()
await test('services/GitAffected', async (t) => { await test("services/GitAffected", async (t) => {
t.afterEach(() => { t.afterEach(() => {
sinon.restore() sinon.restore()
}) })
t.beforeEach(() => { t.beforeEach(() => {
sinon.stub(crypto, 'randomUUID').value(() => { sinon.stub(crypto, "randomUUID").value(() => {
return 'uuid' return "uuid"
}) })
}) })
await t.test('parseCommandOutput', async (t) => { await t.test("parseCommandOutput", async (t) => {
await t.test('returns the right output array', async () => { await t.test("returns the right output array", async () => {
assert.deepStrictEqual(parseCommandOutput('1.txt\n 2.txt '), [ assert.deepStrictEqual(parseCommandOutput("1.txt\n 2.txt "), [
'1.txt', "1.txt",
'2.txt' "2.txt",
]) ])
}) })
}) })
await t.test('getAffectedSolutionsFromFiles', async (t) => { await t.test("getAffectedSolutionsFromFiles", async (t) => {
await t.test('returns the affected solutions', async () => { await t.test("returns the affected solutions", async () => {
const files = [ const files = [
'challenges/hello-world/solutions/javascript/function/solution.js', "challenges/hello-world/solutions/javascript/function/solution.js",
'challenges/is-palindrome/solutions/c/function/input.c' "challenges/is-palindrome/solutions/c/function/input.c",
] ]
const solutions = await gitAffected.getAffectedSolutionsFromFiles(files) const solutions = await gitAffected.getAffectedSolutionsFromFiles(files)
assert.deepStrictEqual(solutions, [ assert.deepStrictEqual(solutions, [
new Solution({ new Solution({
challenge: new Challenge({ name: 'hello-world' }), challenge: new Challenge({ name: "hello-world" }),
name: 'function', name: "function",
programmingLanguageName: 'javascript' programmingLanguageName: "javascript",
}), }),
new Solution({ new Solution({
challenge: new Challenge({ name: 'is-palindrome' }), challenge: new Challenge({ name: "is-palindrome" }),
name: 'function', name: "function",
programmingLanguageName: 'c' programmingLanguageName: "c",
}) }),
]) ])
}) })
await t.test( await t.test(
'returns the affected solutions from Dockerfile changes', "returns the affected solutions from Dockerfile changes",
async () => { async () => {
const files = ['templates/docker/javascript/Dockerfile'] const files = ["templates/docker/javascript/Dockerfile"]
const solutions = await gitAffected.getAffectedSolutionsFromFiles(files) const solutions = await gitAffected.getAffectedSolutionsFromFiles(files)
assert.deepStrictEqual( assert.deepStrictEqual(
solutions[0], solutions[0],
new Solution({ new Solution({
challenge: new Challenge({ name: 'camel-case' }), challenge: new Challenge({ name: "camel-case" }),
name: 'function', name: "function",
programmingLanguageName: 'javascript' programmingLanguageName: "javascript",
}) }),
) )
assert.deepStrictEqual( assert.deepStrictEqual(
solutions[1], solutions[1],
new Solution({ new Solution({
challenge: new Challenge({ name: 'first-non-repeating-character' }), challenge: new Challenge({ name: "first-non-repeating-character" }),
name: 'function', name: "function",
programmingLanguageName: 'javascript' programmingLanguageName: "javascript",
}) }),
) )
} },
) )
await t.test( await t.test(
'returns the affected solutions from Docker template changes', "returns the affected solutions from Docker template changes",
async () => { async () => {
const files = ['templates/docker/javascript/package.json'] const files = ["templates/docker/javascript/package.json"]
const solutions = await gitAffected.getAffectedSolutionsFromFiles(files) const solutions = await gitAffected.getAffectedSolutionsFromFiles(files)
assert.deepStrictEqual( assert.deepStrictEqual(
solutions[0], solutions[0],
new Solution({ new Solution({
challenge: new Challenge({ name: 'camel-case' }), challenge: new Challenge({ name: "camel-case" }),
name: 'function', name: "function",
programmingLanguageName: 'javascript' programmingLanguageName: "javascript",
}) }),
) )
assert.deepStrictEqual( assert.deepStrictEqual(
solutions[1], solutions[1],
new Solution({ new Solution({
challenge: new Challenge({ name: 'first-non-repeating-character' }), challenge: new Challenge({ name: "first-non-repeating-character" }),
name: 'function', name: "function",
programmingLanguageName: 'javascript' programmingLanguageName: "javascript",
}) }),
) )
} },
) )
await t.test( await t.test(
'returns the affected solutions from input/output files', "returns the affected solutions from input/output files",
async () => { 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) const solutions = await gitAffected.getAffectedSolutionsFromFiles(files)
assert.deepStrictEqual( assert.deepStrictEqual(
solutions[0], solutions[0],
new Solution({ new Solution({
challenge: new Challenge({ name: 'hello-world' }), challenge: new Challenge({ name: "hello-world" }),
name: 'function', name: "function",
programmingLanguageName: 'c' programmingLanguageName: "c",
}) }),
) )
assert.deepStrictEqual( assert.deepStrictEqual(
solutions[1], solutions[1],
new Solution({ new Solution({
challenge: new Challenge({ name: 'hello-world' }), challenge: new Challenge({ name: "hello-world" }),
name: 'function', name: "function",
programmingLanguageName: 'cpp' programmingLanguageName: "cpp",
}) }),
) )
assert.deepStrictEqual( assert.deepStrictEqual(
solutions[2], solutions[2],
new Solution({ new Solution({
challenge: new Challenge({ name: 'hello-world' }), challenge: new Challenge({ name: "hello-world" }),
name: 'function', name: "function",
programmingLanguageName: 'cs' programmingLanguageName: "cs",
}) }),
) )
assert.deepStrictEqual( assert.deepStrictEqual(
solutions[3], solutions[3],
new Solution({ new Solution({
challenge: new Challenge({ name: 'hello-world' }), challenge: new Challenge({ name: "hello-world" }),
name: 'function', name: "function",
programmingLanguageName: 'dart' programmingLanguageName: "dart",
}) }),
) )
} },
) )
}) })
}) })

View File

@ -1,92 +1,92 @@
import test from 'node:test' import test from "node:test"
import assert from 'node:assert/strict' import assert from "node:assert/strict"
import fs from 'node:fs' import fs from "node:fs"
import fsMock from 'mock-fs' import fsMock from "mock-fs"
import { copyDirectory } from '../copyDirectory.js' import { copyDirectory } from "../copyDirectory.js"
await test('utils/copyDirectory', async (t) => { await test("utils/copyDirectory", async (t) => {
t.afterEach(() => { t.afterEach(() => {
fsMock.restore() fsMock.restore()
}) })
await t.test('copy the files', async () => { await t.test("copy the files", async () => {
fsMock({ fsMock({
'/source': { "/source": {
'default.png': '', "default.png": "",
'index.ts': '' "index.ts": "",
}, },
'/destination': {} "/destination": {},
}) })
let destinationDirectoryContent = await fs.promises.readdir('/destination') let destinationDirectoryContent = await fs.promises.readdir("/destination")
let sourceDirectoryContent = await fs.promises.readdir('/source') let sourceDirectoryContent = await fs.promises.readdir("/source")
assert.strictEqual(destinationDirectoryContent.length, 0) assert.strictEqual(destinationDirectoryContent.length, 0)
assert.strictEqual(sourceDirectoryContent.length, 2) assert.strictEqual(sourceDirectoryContent.length, 2)
await copyDirectory('/source', '/destination') await copyDirectory("/source", "/destination")
destinationDirectoryContent = await fs.promises.readdir('/destination') destinationDirectoryContent = await fs.promises.readdir("/destination")
sourceDirectoryContent = await fs.promises.readdir('/source') sourceDirectoryContent = await fs.promises.readdir("/source")
assert.strictEqual(destinationDirectoryContent.length, 2) assert.strictEqual(destinationDirectoryContent.length, 2)
assert.strictEqual(sourceDirectoryContent.length, 2) assert.strictEqual(sourceDirectoryContent.length, 2)
assert.deepStrictEqual(destinationDirectoryContent, [ assert.deepStrictEqual(destinationDirectoryContent, [
'default.png', "default.png",
'index.ts' "index.ts",
]) ])
assert.deepStrictEqual(sourceDirectoryContent, ['default.png', 'index.ts']) assert.deepStrictEqual(sourceDirectoryContent, ["default.png", "index.ts"])
}) })
await t.test('copy the files and folders recursively', async () => { await t.test("copy the files and folders recursively", async () => {
fsMock({ fsMock({
'/source': { "/source": {
'random-folder': { "random-folder": {
'default.png': '', "default.png": "",
'second-random-folder': { "second-random-folder": {
'mycode.ts': '' "mycode.ts": "",
} },
}, },
'index.ts': '' "index.ts": "",
}, },
'/destination': {} "/destination": {},
}) })
let destinationDirectoryContent = await fs.promises.readdir('/destination') let destinationDirectoryContent = await fs.promises.readdir("/destination")
let sourceDirectoryContent = await fs.promises.readdir('/source') let sourceDirectoryContent = await fs.promises.readdir("/source")
let randomFolderContent = await fs.promises.readdir('/source/random-folder') let randomFolderContent = await fs.promises.readdir("/source/random-folder")
let secondRandomFolderContent = await fs.promises.readdir( let secondRandomFolderContent = await fs.promises.readdir(
'/source/random-folder/second-random-folder' "/source/random-folder/second-random-folder",
) )
assert.strictEqual(randomFolderContent.length, 2) assert.strictEqual(randomFolderContent.length, 2)
assert.strictEqual(secondRandomFolderContent.length, 1) assert.strictEqual(secondRandomFolderContent.length, 1)
assert.strictEqual(destinationDirectoryContent.length, 0) assert.strictEqual(destinationDirectoryContent.length, 0)
assert.strictEqual(sourceDirectoryContent.length, 2) assert.strictEqual(sourceDirectoryContent.length, 2)
await copyDirectory('/source', '/destination') await copyDirectory("/source", "/destination")
destinationDirectoryContent = await fs.promises.readdir('/destination') destinationDirectoryContent = await fs.promises.readdir("/destination")
sourceDirectoryContent = await fs.promises.readdir('/source') sourceDirectoryContent = await fs.promises.readdir("/source")
randomFolderContent = await fs.promises.readdir( randomFolderContent = await fs.promises.readdir(
'/destination/random-folder' "/destination/random-folder",
) )
secondRandomFolderContent = await fs.promises.readdir( secondRandomFolderContent = await fs.promises.readdir(
'/destination/random-folder/second-random-folder' "/destination/random-folder/second-random-folder",
) )
assert.strictEqual(destinationDirectoryContent.length, 2) assert.strictEqual(destinationDirectoryContent.length, 2)
assert.strictEqual(sourceDirectoryContent.length, 2) assert.strictEqual(sourceDirectoryContent.length, 2)
assert.deepStrictEqual(destinationDirectoryContent, [ assert.deepStrictEqual(destinationDirectoryContent, [
'index.ts', "index.ts",
'random-folder' "random-folder",
]) ])
assert.deepStrictEqual(sourceDirectoryContent, [ assert.deepStrictEqual(sourceDirectoryContent, [
'index.ts', "index.ts",
'random-folder' "random-folder",
]) ])
assert.strictEqual(randomFolderContent.length, 2) assert.strictEqual(randomFolderContent.length, 2)
assert.strictEqual(secondRandomFolderContent.length, 1) assert.strictEqual(secondRandomFolderContent.length, 1)
assert.deepStrictEqual(randomFolderContent, [ assert.deepStrictEqual(randomFolderContent, [
'default.png', "default.png",
'second-random-folder' "second-random-folder",
]) ])
assert.deepStrictEqual(secondRandomFolderContent, ['mycode.ts']) assert.deepStrictEqual(secondRandomFolderContent, ["mycode.ts"])
}) })
}) })

View File

@ -1,26 +1,26 @@
import test from 'node:test' import test from "node:test"
import assert from 'node:assert/strict' import assert from "node:assert/strict"
import fsMock from 'mock-fs' import fsMock from "mock-fs"
import { isExistingPath } from '../isExistingPath.js' import { isExistingPath } from "../isExistingPath.js"
await test('utils/isExistingPath', async (t) => { await test("utils/isExistingPath", async (t) => {
t.afterEach(() => { t.afterEach(() => {
fsMock.restore() fsMock.restore()
}) })
await t.test('should return true if the file exists', async () => { await t.test("should return true if the file exists", async () => {
fsMock({ fsMock({
'/file.txt': '' "/file.txt": "",
}) })
assert.strictEqual(await isExistingPath('/file.txt'), true) assert.strictEqual(await isExistingPath("/file.txt"), true)
}) })
await t.test("should return false if the file doesn't exists", async () => { await t.test("should return false if the file doesn't exists", async () => {
fsMock({ fsMock({
'/file.txt': '' "/file.txt": "",
}) })
assert.strictEqual(await isExistingPath('/randomfile.txt'), false) assert.strictEqual(await isExistingPath("/randomfile.txt"), false)
}) })
}) })

View File

@ -1,9 +1,9 @@
import fs from 'node:fs' import fs from "node:fs"
import path from 'node:path' import path from "node:path"
export const copyDirectory = async ( export const copyDirectory = async (
source: string, source: string,
destination: string destination: string,
): Promise<void> => { ): Promise<void> => {
const filesToCreate = await fs.promises.readdir(source) const filesToCreate = await fs.promises.readdir(source)
for (const file of filesToCreate) { for (const file of filesToCreate) {

View File

@ -1,4 +1,4 @@
import fs from 'node:fs' import fs from "node:fs"
export const isExistingPath = async (path: string): Promise<boolean> => { export const isExistingPath = async (path: string): Promise<boolean> => {
try { try {

View File

@ -1,6 +1,6 @@
export const parseCommandOutput = (output: string): string[] => { export const parseCommandOutput = (output: string): string[] => {
return output return output
.split('\n') .split("\n")
.map((line) => { .map((line) => {
return line.trim() return line.trim()
}) })

1184
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -11,7 +11,7 @@
}, },
"engines": { "engines": {
"node": ">=20.0.0", "node": ">=20.0.0",
"npm": ">=9.0.0" "npm": ">=10.0.0"
}, },
"main": "build/index.js", "main": "build/index.js",
"bin": "build/index.js", "bin": "build/index.js",
@ -29,9 +29,9 @@
"dependencies": { "dependencies": {
"chalk": "5.3.0", "chalk": "5.3.0",
"clipanion": "3.2.1", "clipanion": "3.2.1",
"date-and-time": "3.0.2", "date-and-time": "3.0.3",
"execa": "8.0.1", "execa": "8.0.1",
"log-symbols": "5.1.0", "log-symbols": "6.0.0",
"ora": "7.0.1", "ora": "7.0.1",
"replace-in-file": "7.0.1", "replace-in-file": "7.0.1",
"table": "6.8.1", "table": "6.8.1",
@ -39,33 +39,32 @@
"validate-npm-package-name": "5.0.0" "validate-npm-package-name": "5.0.0"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "17.7.1", "@commitlint/cli": "18.0.0",
"@commitlint/config-conventional": "17.7.0", "@commitlint/config-conventional": "18.0.0",
"@swc/cli": "0.1.62", "@swc/cli": "0.1.62",
"@swc/core": "1.3.85", "@swc/core": "1.3.94",
"@tsconfig/strictest": "2.0.2", "@tsconfig/strictest": "2.0.2",
"@types/date-and-time": "0.13.0", "@types/mock-fs": "4.13.3",
"@types/mock-fs": "4.13.1", "@types/ms": "0.7.33",
"@types/ms": "0.7.31", "@types/node": "20.8.7",
"@types/node": "20.6.2", "@types/sinon": "10.0.20",
"@types/sinon": "10.0.16", "@types/validate-npm-package-name": "4.0.1",
"@types/validate-npm-package-name": "4.0.0", "@typescript-eslint/eslint-plugin": "6.9.0",
"@typescript-eslint/eslint-plugin": "6.7.0", "@typescript-eslint/parser": "6.9.0",
"@typescript-eslint/parser": "6.7.0",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"editorconfig-checker": "5.1.1", "editorconfig-checker": "5.1.1",
"eslint": "8.49.0", "eslint": "8.52.0",
"eslint-config-conventions": "11.0.1", "eslint-config-conventions": "12.0.0",
"eslint-plugin-import": "2.28.1", "eslint-plugin-import": "2.29.0",
"eslint-plugin-promise": "6.1.1", "eslint-plugin-promise": "6.1.1",
"eslint-plugin-unicorn": "48.0.1", "eslint-plugin-unicorn": "48.0.1",
"get-stream": "8.0.1", "get-stream": "8.0.1",
"markdownlint-cli2": "0.9.2", "markdownlint-cli2": "0.10.0",
"markdownlint-rule-relative-links": "2.1.0", "markdownlint-rule-relative-links": "2.1.0",
"mock-fs": "5.2.0", "mock-fs": "5.2.0",
"ms": "2.1.3", "ms": "2.1.3",
"rimraf": "5.0.1", "rimraf": "5.0.5",
"sinon": "16.0.0", "sinon": "17.0.0",
"typescript": "5.2.2" "typescript": "5.2.2"
} }
} }

View File

@ -1,4 +1,4 @@
FROM dart:3.1.2 AS builder FROM dart:3.1.4 AS builder
WORKDIR /usr/src/application WORKDIR /usr/src/application
COPY ./ ./ COPY ./ ./
RUN dart compile exe solution.dart -o solution RUN dart compile exe solution.dart -o solution

View File

@ -1,4 +1,4 @@
FROM gcr.io/distroless/nodejs20-debian11:latest AS runner FROM gcr.io/distroless/nodejs20-debian12:latest AS runner
WORKDIR /usr/src/application WORKDIR /usr/src/application
COPY ./ ./ COPY ./ ./
CMD ["./solution.js"] CMD ["./solution.js"]

View File

@ -1,4 +1,4 @@
FROM rust:1.72.0 AS builder FROM rust:1.73.0 AS builder
WORKDIR /usr/src/rust_application WORKDIR /usr/src/rust_application
# Cache dependencies # Cache dependencies

View File

@ -1,15 +1,15 @@
FROM node:20.6.1 AS builder-dependencies FROM node:20.8.1 AS builder-dependencies
WORKDIR /usr/src/application WORKDIR /usr/src/application
COPY ./package*.json ./ COPY ./package*.json ./
RUN npm install RUN npm install
FROM node:20.6.1 AS builder FROM node:20.8.1 AS builder
WORKDIR /usr/src/application WORKDIR /usr/src/application
COPY --from=builder-dependencies /usr/src/application/node_modules ./node_modules COPY --from=builder-dependencies /usr/src/application/node_modules ./node_modules
COPY ./ ./ COPY ./ ./
RUN npm run build RUN npm run build
FROM gcr.io/distroless/nodejs20-debian11:latest AS runner FROM gcr.io/distroless/nodejs20-debian12:latest AS runner
WORKDIR /usr/src/application WORKDIR /usr/src/application
ENV NODE_ENV=production ENV NODE_ENV=production
COPY --from=builder /usr/src/application/package.json ./package.json COPY --from=builder /usr/src/application/package.json ./package.json

View File

@ -1,14 +1,14 @@
import readline from 'node:readline' import readline from "node:readline"
const input = [] const input = []
const readlineInterface = readline.createInterface({ const readlineInterface = readline.createInterface({
input: process.stdin, input: process.stdin,
output: process.stdout output: process.stdout,
}) })
readlineInterface.on('line', (value) => { readlineInterface.on("line", (value) => {
input.push(value) input.push(value)
}) })
readlineInterface.on('close', solution) readlineInterface.on("close", solution)
function solution() { function solution() {
console.log(`Hello, ${input[0]}!`) console.log(`Hello, ${input[0]}!`)

View File

@ -1,14 +1,14 @@
import readline from 'node:readline' import readline from "node:readline"
const input: string[] = [] const input: string[] = []
const readlineInterface = readline.createInterface({ const readlineInterface = readline.createInterface({
input: process.stdin, input: process.stdin,
output: process.stdout output: process.stdout,
}) })
readlineInterface.on('line', (value) => { readlineInterface.on("line", (value) => {
input.push(value) input.push(value)
}) })
readlineInterface.on('close', solution) readlineInterface.on("close", solution)
function solution(): void { function solution(): void {
console.log(`Hello, ${input[0]}!`) console.log(`Hello, ${input[0]}!`)