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'
about: 'Report an unexpected problem or unintended behavior.'
title: '[Bug]'
labels: 'bug'
name: "🐛 Bug Report"
about: "Report an unexpected problem or unintended behavior."
title: "[Bug]"
labels: "bug"
---
<!--

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -49,7 +49,7 @@ Gitpod will automatically setup an environment for you.
#### Prerequisites
- [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/)
#### Installation

View File

@ -17,8 +17,8 @@ make?
- **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:
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.
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.
### Output

View File

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

View File

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

View File

@ -7,7 +7,7 @@ export const getMaximumFrequencyDeviation = (string) => {
/** @type {string[]} */
const subStrings = []
for (let index = 0; index < string.length; index++) {
let subString = ''
let subString = ""
for (let subIndex = index; subIndex < string.length; subIndex++) {
subString += string[subIndex]
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 readlineInterface = readline.createInterface({
input: process.stdin,
output: process.stdout
output: process.stdout,
})
readlineInterface.on('line', (value) => {
readlineInterface.on("line", (value) => {
input.push(value)
})
@ -15,4 +15,4 @@ const solution = () => {
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 readlineInterface = readline.createInterface({
input: process.stdin,
output: process.stdout
output: process.stdout,
})
readlineInterface.on('line', (value) => {
readlineInterface.on("line", (value) => {
input.push(value)
})
readlineInterface.on('close', solution)
readlineInterface.on("close", solution)
function solution() {
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 readlineInterface = readline.createInterface({
input: process.stdin,
output: process.stdout
output: process.stdout,
})
readlineInterface.on('line', (value) => {
readlineInterface.on("line", (value) => {
input.push(value)
})
readlineInterface.on('close', solution)
readlineInterface.on("close", solution)
function solution(): void {
console.log(`Hello, ${input[0]}!`)

View File

@ -1,17 +1,17 @@
import readline from 'node:readline'
import readline from "node:readline"
const input = []
const readlineInterface = readline.createInterface({
input: process.stdin,
output: process.stdout
output: process.stdout,
})
readlineInterface.on('line', (value) => {
readlineInterface.on("line", (value) => {
input.push(value)
})
readlineInterface.on('close', solution)
readlineInterface.on("close", solution)
function solution() {
const string = input[0].replace(/ /g, '').toLowerCase()
const isPalindrome = string.split('').reverse().join('') === string
const string = input[0].replace(/ /g, "").toLowerCase()
const isPalindrome = string.split("").reverse().join("") === string
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) {
return string
}
let pad = ''
let pad = ""
while (resultLength !== 0) {
if (resultLength & 1) {
pad += padString
@ -32,9 +32,9 @@ const solution = () => {
const input = []
const readlineInterface = readline.createInterface({
input: process.stdin,
output: process.stdout
output: process.stdout,
})
readlineInterface.on('line', (value) => {
readlineInterface.on("line", (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 readlineInterface = readline.createInterface({
input: process.stdin,
output: process.stdout
output: process.stdout,
})
readlineInterface.on('line', (value) => {
readlineInterface.on("line", (value) => {
input.push(value)
})
readlineInterface.on('close', solution)
readlineInterface.on("close", solution)
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]))
}
@ -26,7 +26,7 @@ function reversePolishNotation(value) {
return 0
}
const stack = []
const values = value.split(' ')
const values = value.split(" ")
values.forEach((value) => {
const number = Number(value)
if (!isNaN(number)) {

View File

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

View File

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

View File

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

View File

@ -3,5 +3,5 @@
Created by [@theoludwig](https://github.com/theoludwig) on 29 June 2021.
| 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)) |

View File

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

View File

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

View File

@ -3,5 +3,5 @@
Created by [@theoludwig](https://github.com/theoludwig) on 29 June 2021.
| 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)) |

View File

@ -3,5 +3,5 @@
Created by [@theoludwig](https://github.com/theoludwig) on 29 June 2021.
| Algorithm | Best Case | Average Case | Worst Case |
| ----------------------------------------------------------- | ----------- | ------------ | ----------- |
| ----------------------------------------------------- | --------- | ------------ | ---------- |
| [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 readlineInterface = readline.createInterface({
input: process.stdin,
output: process.stdout
output: process.stdout,
})
readlineInterface.on('line', (value) => {
readlineInterface.on("line", (value) => {
numbers.push(Number(value))
})
readlineInterface.on('close', solution)
readlineInterface.on("close", solution)
function solution() {
const sortedNumbers = bubbleSort(numbers.slice(1))
@ -17,7 +17,7 @@ function solution() {
})
}
function bubbleSort (numbersInput) {
function bubbleSort(numbersInput) {
const numbers = [...numbersInput]
for (let index1 = 0; index1 < numbers.length; index1++) {
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 readlineInterface = readline.createInterface({
input: process.stdin,
output: process.stdout
output: process.stdout,
})
readlineInterface.on('line', (value) => {
readlineInterface.on("line", (value) => {
numbers.push(Number(value))
})
readlineInterface.on('close', solution)
readlineInterface.on("close", solution)
function solution() {
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)
}

View File

@ -3,5 +3,5 @@
Created by [@theoludwig](https://github.com/theoludwig) on 29 June 2021.
| Algorithm | Best Case | Average Case | Worst Case |
| ----------------------------------------------------------- | ----------- | ------------ | ----------- |
| ----------------------------------------------------------- | --------- | ------------ | ---------- |
| [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 readlineInterface = readline.createInterface({
input: process.stdin,
output: process.stdout
output: process.stdout,
})
readlineInterface.on('line', (value) => {
readlineInterface.on("line", (value) => {
numbers.push(Number(value))
})
readlineInterface.on('close', solution)
readlineInterface.on("close", solution)
function solution() {
const sortedNumbers = insertionSort(numbers.slice(1))

View File

@ -3,5 +3,5 @@
Created by [@theoludwig](https://github.com/theoludwig) on 29 June 2021.
| 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)) |

View File

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

View File

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

View File

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

View File

@ -3,5 +3,5 @@
Created by [@theoludwig](https://github.com/theoludwig) on 29 June 2021.
| 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)) |

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
#!/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

View File

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

View File

@ -1,18 +1,18 @@
import { execaCommand } from 'execa'
import ms from 'ms'
import { execaCommand } from "execa"
import ms from "ms"
import { parseCommandOutput } from '../utils/parseCommandOutput.js'
import { parseCommandOutput } from "../utils/parseCommandOutput.js"
export interface DockerRunResult {
stdout: string
}
export class Docker {
public static readonly CONTAINER_BASE_TAG = 'programming-challenges'
public static readonly CONTAINER_BASE_TAG = "programming-challenges"
public static readonly SIGSEGV_EXIT_CODE = 139
public static readonly MAXIMUM_TIMEOUT = '1 minute'
public static readonly MAXIMUM_TIMEOUT = "1 minute"
public static readonly MAXIMUM_TIMEOUT_MILLISECONDS = ms(
Docker.MAXIMUM_TIMEOUT
Docker.MAXIMUM_TIMEOUT,
)
public getContainerTag(id: string): string {
@ -23,7 +23,7 @@ export class Docker {
try {
const { stdout } = await execaCommand(
`docker images -q --filter=reference="${Docker.CONTAINER_BASE_TAG}:*"`,
{ shell: true }
{ shell: true },
)
return parseCommandOutput(stdout)
} catch {
@ -37,8 +37,8 @@ export class Docker {
if (images.length === 0) {
return
}
await execaCommand(`docker rmi -f ${images.join(' ')}`, {
shell: true
await execaCommand(`docker rmi -f ${images.join(" ")}`, {
shell: true,
})
} catch {}
}
@ -55,8 +55,8 @@ export class Docker {
const subprocess = execaCommand(
`docker run --interactive --rm ${this.getContainerTag(id)}`,
{
input
}
input,
},
)
try {
const { stdout, stderr } = await subprocess
@ -64,12 +64,12 @@ export class Docker {
throw new Error(stderr)
}
return {
stdout
stdout,
}
} catch (error: any) {
if (error.exitCode === Docker.SIGSEGV_EXIT_CODE) {
throw new Error(
"Docker run failed.\nSIGSEGV indicates a segmentation fault (attempts to access a memory location that it's not allowed to access)."
"Docker run failed.\nSIGSEGV indicates a segmentation fault (attempts to access a memory location that it's not allowed to access).",
)
}
throw new Error(`Docker run failed.\n${error.message as string}`)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,92 +1,92 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import fs from 'node:fs'
import test from "node:test"
import assert from "node:assert/strict"
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(() => {
fsMock.restore()
})
await t.test('copy the files', async () => {
await t.test("copy the files", async () => {
fsMock({
'/source': {
'default.png': '',
'index.ts': ''
"/source": {
"default.png": "",
"index.ts": "",
},
'/destination': {}
"/destination": {},
})
let destinationDirectoryContent = await fs.promises.readdir('/destination')
let sourceDirectoryContent = await fs.promises.readdir('/source')
let destinationDirectoryContent = await fs.promises.readdir("/destination")
let sourceDirectoryContent = await fs.promises.readdir("/source")
assert.strictEqual(destinationDirectoryContent.length, 0)
assert.strictEqual(sourceDirectoryContent.length, 2)
await copyDirectory('/source', '/destination')
destinationDirectoryContent = await fs.promises.readdir('/destination')
sourceDirectoryContent = await fs.promises.readdir('/source')
await copyDirectory("/source", "/destination")
destinationDirectoryContent = await fs.promises.readdir("/destination")
sourceDirectoryContent = await fs.promises.readdir("/source")
assert.strictEqual(destinationDirectoryContent.length, 2)
assert.strictEqual(sourceDirectoryContent.length, 2)
assert.deepStrictEqual(destinationDirectoryContent, [
'default.png',
'index.ts'
"default.png",
"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({
'/source': {
'random-folder': {
'default.png': '',
'second-random-folder': {
'mycode.ts': ''
}
"/source": {
"random-folder": {
"default.png": "",
"second-random-folder": {
"mycode.ts": "",
},
'index.ts': ''
},
'/destination': {}
"index.ts": "",
},
"/destination": {},
})
let destinationDirectoryContent = await fs.promises.readdir('/destination')
let sourceDirectoryContent = await fs.promises.readdir('/source')
let randomFolderContent = await fs.promises.readdir('/source/random-folder')
let destinationDirectoryContent = await fs.promises.readdir("/destination")
let sourceDirectoryContent = await fs.promises.readdir("/source")
let randomFolderContent = await fs.promises.readdir("/source/random-folder")
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(secondRandomFolderContent.length, 1)
assert.strictEqual(destinationDirectoryContent.length, 0)
assert.strictEqual(sourceDirectoryContent.length, 2)
await copyDirectory('/source', '/destination')
destinationDirectoryContent = await fs.promises.readdir('/destination')
sourceDirectoryContent = await fs.promises.readdir('/source')
await copyDirectory("/source", "/destination")
destinationDirectoryContent = await fs.promises.readdir("/destination")
sourceDirectoryContent = await fs.promises.readdir("/source")
randomFolderContent = await fs.promises.readdir(
'/destination/random-folder'
"/destination/random-folder",
)
secondRandomFolderContent = await fs.promises.readdir(
'/destination/random-folder/second-random-folder'
"/destination/random-folder/second-random-folder",
)
assert.strictEqual(destinationDirectoryContent.length, 2)
assert.strictEqual(sourceDirectoryContent.length, 2)
assert.deepStrictEqual(destinationDirectoryContent, [
'index.ts',
'random-folder'
"index.ts",
"random-folder",
])
assert.deepStrictEqual(sourceDirectoryContent, [
'index.ts',
'random-folder'
"index.ts",
"random-folder",
])
assert.strictEqual(randomFolderContent.length, 2)
assert.strictEqual(secondRandomFolderContent.length, 1)
assert.deepStrictEqual(randomFolderContent, [
'default.png',
'second-random-folder'
"default.png",
"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 assert from 'node:assert/strict'
import test from "node:test"
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(() => {
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({
'/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 () => {
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 path from 'node:path'
import fs from "node:fs"
import path from "node:path"
export const copyDirectory = async (
source: string,
destination: string
destination: string,
): Promise<void> => {
const filesToCreate = await fs.promises.readdir(source)
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> => {
try {

View File

@ -1,6 +1,6 @@
export const parseCommandOutput = (output: string): string[] => {
return output
.split('\n')
.split("\n")
.map((line) => {
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": {
"node": ">=20.0.0",
"npm": ">=9.0.0"
"npm": ">=10.0.0"
},
"main": "build/index.js",
"bin": "build/index.js",
@ -29,9 +29,9 @@
"dependencies": {
"chalk": "5.3.0",
"clipanion": "3.2.1",
"date-and-time": "3.0.2",
"date-and-time": "3.0.3",
"execa": "8.0.1",
"log-symbols": "5.1.0",
"log-symbols": "6.0.0",
"ora": "7.0.1",
"replace-in-file": "7.0.1",
"table": "6.8.1",
@ -39,33 +39,32 @@
"validate-npm-package-name": "5.0.0"
},
"devDependencies": {
"@commitlint/cli": "17.7.1",
"@commitlint/config-conventional": "17.7.0",
"@commitlint/cli": "18.0.0",
"@commitlint/config-conventional": "18.0.0",
"@swc/cli": "0.1.62",
"@swc/core": "1.3.85",
"@swc/core": "1.3.94",
"@tsconfig/strictest": "2.0.2",
"@types/date-and-time": "0.13.0",
"@types/mock-fs": "4.13.1",
"@types/ms": "0.7.31",
"@types/node": "20.6.2",
"@types/sinon": "10.0.16",
"@types/validate-npm-package-name": "4.0.0",
"@typescript-eslint/eslint-plugin": "6.7.0",
"@typescript-eslint/parser": "6.7.0",
"@types/mock-fs": "4.13.3",
"@types/ms": "0.7.33",
"@types/node": "20.8.7",
"@types/sinon": "10.0.20",
"@types/validate-npm-package-name": "4.0.1",
"@typescript-eslint/eslint-plugin": "6.9.0",
"@typescript-eslint/parser": "6.9.0",
"cross-env": "7.0.3",
"editorconfig-checker": "5.1.1",
"eslint": "8.49.0",
"eslint-config-conventions": "11.0.1",
"eslint-plugin-import": "2.28.1",
"eslint": "8.52.0",
"eslint-config-conventions": "12.0.0",
"eslint-plugin-import": "2.29.0",
"eslint-plugin-promise": "6.1.1",
"eslint-plugin-unicorn": "48.0.1",
"get-stream": "8.0.1",
"markdownlint-cli2": "0.9.2",
"markdownlint-cli2": "0.10.0",
"markdownlint-rule-relative-links": "2.1.0",
"mock-fs": "5.2.0",
"ms": "2.1.3",
"rimraf": "5.0.1",
"sinon": "16.0.0",
"rimraf": "5.0.5",
"sinon": "17.0.0",
"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
COPY ./ ./
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
COPY ./ ./
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
# 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
COPY ./package*.json ./
RUN npm install
FROM node:20.6.1 AS builder
FROM node:20.8.1 AS builder
WORKDIR /usr/src/application
COPY --from=builder-dependencies /usr/src/application/node_modules ./node_modules
COPY ./ ./
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
ENV NODE_ENV=production
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 readlineInterface = readline.createInterface({
input: process.stdin,
output: process.stdout
output: process.stdout,
})
readlineInterface.on('line', (value) => {
readlineInterface.on("line", (value) => {
input.push(value)
})
readlineInterface.on('close', solution)
readlineInterface.on("close", solution)
function solution() {
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 readlineInterface = readline.createInterface({
input: process.stdin,
output: process.stdout
output: process.stdout,
})
readlineInterface.on('line', (value) => {
readlineInterface.on("line", (value) => {
input.push(value)
})
readlineInterface.on('close', solution)
readlineInterface.on("close", solution)
function solution(): void {
console.log(`Hello, ${input[0]}!`)