mirror of
https://github.com/type-challenges/type-challenges.git
synced 2025-12-08 19:06:13 +00:00
181 lines
5.2 KiB
TypeScript
181 lines
5.2 KiB
TypeScript
import path from 'node:path'
|
|
import process from 'node:process'
|
|
import crypto from 'node:crypto'
|
|
import fs from 'fs-extra'
|
|
import c from 'ansis'
|
|
import prompts from 'prompts'
|
|
import { formatToCode } from './actions/utils/formatToCode'
|
|
import { loadQuizzes, resolveInfo } from './loader'
|
|
import { supportedLocales } from './locales'
|
|
import { getQuestionFullName } from './actions/issue-pr'
|
|
import type { QuizMetaInfo } from './types'
|
|
|
|
type Snapshot = Record<string, string>
|
|
|
|
function calculateFileHash(filePathFull: string): Promise<string> {
|
|
return new Promise((resolve, reject) => {
|
|
const hash = crypto.createHash('sha1')
|
|
const fileStream = fs.createReadStream(filePathFull)
|
|
|
|
fileStream.on('data', (data) => {
|
|
hash.update(data)
|
|
})
|
|
|
|
fileStream.on('end', () => {
|
|
hash.update(filePathFull)
|
|
resolve(hash.digest('hex'))
|
|
})
|
|
|
|
fileStream.on('error', (err) => {
|
|
reject(err)
|
|
})
|
|
})
|
|
}
|
|
|
|
async function takeSnapshot(quizzesPath: string) {
|
|
let snapshot: Snapshot = {}
|
|
|
|
const files = fs.readdirSync(quizzesPath)
|
|
|
|
for (const file of files) {
|
|
// Might be a file, or a folder
|
|
const fPath = path.join(quizzesPath, file)
|
|
const fStats = fs.statSync(fPath)
|
|
|
|
if (fStats.isDirectory()) {
|
|
snapshot = {
|
|
...snapshot,
|
|
...(await takeSnapshot(fPath)),
|
|
}
|
|
}
|
|
else {
|
|
snapshot[file] = await calculateFileHash(fPath)
|
|
}
|
|
}
|
|
|
|
return snapshot
|
|
}
|
|
|
|
function readPlaygroundCache(playgroundCachePath: string): Snapshot {
|
|
if (!fs.existsSync(playgroundCachePath))
|
|
return {}
|
|
|
|
try {
|
|
const rawCacheContent = fs.readFileSync(playgroundCachePath)
|
|
return JSON.parse(rawCacheContent.toString())
|
|
}
|
|
catch (err) {
|
|
console.log(c.red('Playground cache corrupted. '
|
|
+ 'Cannot generate playground without keeping your changes intact'))
|
|
console.log(c.cyan('Please ensure you have run this: "pnpm generate"'))
|
|
process.exit(1)
|
|
}
|
|
}
|
|
|
|
function calculateOverridableFiles(cache: Snapshot, snapshot: Snapshot) {
|
|
const result: Snapshot = {}
|
|
|
|
for (const quizName in snapshot) {
|
|
if (snapshot[quizName] === cache[quizName])
|
|
result[quizName] = snapshot[quizName]
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
function isQuizWritable(quizFileName: string, overridableFiles: Snapshot, playgroundSnapshot: Snapshot): boolean {
|
|
return !!(
|
|
overridableFiles[quizFileName]
|
|
|| (!overridableFiles[quizFileName] && !playgroundSnapshot[quizFileName])
|
|
)
|
|
}
|
|
|
|
async function generatePlayground() {
|
|
const playgroundPath = path.join(__dirname, '../playground')
|
|
const playgroundCachePath = path.join(__dirname, '../.playgroundcache')
|
|
|
|
let locale = supportedLocales.find(locale => locale === process.argv[2])!
|
|
|
|
console.log(c.bold.cyan('Generating local playground...\n'))
|
|
|
|
let overridableFiles: Snapshot
|
|
let keepChanges = false
|
|
const currentPlaygroundCache = readPlaygroundCache(playgroundCachePath)
|
|
let playgroundSnapshot: Snapshot
|
|
|
|
if (process.argv.length === 3 && (process.argv[2] === '--keep-changes' || process.argv[2] === '-K')) {
|
|
console.log(c.bold.cyan('We will keep your changes while generating.\n'))
|
|
keepChanges = true
|
|
|
|
playgroundSnapshot = await takeSnapshot(playgroundPath)
|
|
|
|
overridableFiles = calculateOverridableFiles(currentPlaygroundCache, playgroundSnapshot)
|
|
}
|
|
else if (fs.existsSync(playgroundPath)) {
|
|
const result = await prompts([{
|
|
name: 'confirm',
|
|
type: 'confirm',
|
|
initial: false,
|
|
message: 'The playground directory already exists, it may contains the answers you did. Do you want to override it?',
|
|
}])
|
|
if (!result?.confirm)
|
|
return console.log(c.yellow('Skipped.'))
|
|
}
|
|
|
|
if (!locale) {
|
|
const result = await prompts([{
|
|
name: 'locale',
|
|
type: 'select',
|
|
message: 'Select language:',
|
|
choices: supportedLocales.map(i => ({
|
|
title: i,
|
|
value: i,
|
|
})),
|
|
}])
|
|
if (!result)
|
|
return console.log(c.yellow('Skipped.'))
|
|
locale = result.locale
|
|
}
|
|
|
|
if (!keepChanges) {
|
|
await fs.remove(playgroundPath)
|
|
await fs.ensureDir(playgroundPath)
|
|
}
|
|
|
|
const quizzes = await loadQuizzes()
|
|
const incomingQuizzesCache: Snapshot = {}
|
|
|
|
for (const quiz of quizzes) {
|
|
const { difficulty, title } = resolveInfo(quiz, locale) as QuizMetaInfo & { difficulty: string }
|
|
const code = formatToCode(quiz, locale)
|
|
|
|
if (difficulty === undefined || title === undefined) {
|
|
console.log(c.yellow`${quiz.no} has no ${locale.toUpperCase()} version. Skipping`)
|
|
continue
|
|
}
|
|
|
|
const quizzesPathByDifficulty = path.join(playgroundPath, difficulty)
|
|
|
|
const quizFileName = `${getQuestionFullName(quiz.no, difficulty, title)}.ts`
|
|
const quizPathFull = path.join(quizzesPathByDifficulty, quizFileName)
|
|
|
|
if (!keepChanges || (keepChanges && isQuizWritable(quizFileName, overridableFiles!, playgroundSnapshot!))) {
|
|
if (!fs.existsSync(quizzesPathByDifficulty))
|
|
fs.mkdirSync(quizzesPathByDifficulty)
|
|
await fs.writeFile(quizPathFull, code, 'utf-8')
|
|
incomingQuizzesCache[quizFileName] = await calculateFileHash(quizPathFull)
|
|
}
|
|
}
|
|
|
|
fs.writeFile(playgroundCachePath, JSON.stringify({
|
|
...currentPlaygroundCache,
|
|
...incomingQuizzesCache,
|
|
}))
|
|
|
|
console.log()
|
|
console.log(c.bold.green('Local playground generated at: ') + c.dim(playgroundPath))
|
|
console.log()
|
|
}
|
|
|
|
generatePlayground()
|