Skip to content

Commit

Permalink
feat: generate-play can structurize playground and preserve changes (t…
Browse files Browse the repository at this point in the history
  • Loading branch information
jazelly authored May 21, 2023
1 parent b4a78ba commit f389d3e
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 9 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ dist
*.log
*.local
playground/
.playgroundcache
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,16 @@ pnpm generate

It will prompt you to select the desired language, then you can find the generated challenges in the `./playground` folder.

Later if you want to update playground while keeping your changes:

```bash
pnpm generate --keep-changes
```
OR
```bash
pnpm generate -K
```

## Thanks

This project was born from solving real-world types problem with [@hardfist](https://github.com/hardfist) and [@MeCKodo](https://github.com/MeCKodo). And great thanks to [@sinoon](https://github.com/sinoon) who contributed a lot while giving early feedback on this project.
Expand Down
124 changes: 115 additions & 9 deletions scripts/generate-play.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,109 @@
import path from 'path'
import { argv } from 'process'
import crypto from 'crypto'
import fs from 'fs-extra'
import c from 'picocolors'
import prompts from 'prompts'
import { formatToCode } from './actions/utils/formatToCode'
import { loadQuizes, resolveInfo } from './loader'
import { defaultLocale, supportedLocales } from './locales'
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(quizesPath: string) {
let snapshot: Snapshot = {}

const files = fs.readdirSync(quizesPath)

for (const file of files) {
// Might be a file, or a folder
const fPath = path.join(quizesPath, 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
}

async function generatePlayground() {
const quizesPath = path.join(__dirname, '../playground')
const playgroundPath = path.join(__dirname, '../playground')
const playgroundCachePath = path.join(__dirname, '../.playgroundcache')

let locale = supportedLocales.find(locale => locale === argv[2])!

console.log(c.bold(c.cyan('Generateing local playground...\n')))
console.log(c.bold(c.cyan('Generating local playground...\n')))

let overridableFiles: Snapshot = {}
let keepChanges = false
const currentPlaygroundCache = readPlaygroundCache(playgroundCachePath)

if (fs.existsSync(quizesPath)) {
if (argv.length === 3 && (argv[2] === '--keep-changes' || argv[2] === '-K')) {
console.log(c.bold(c.cyan('We will keep your chanegs while generating.\n')))
keepChanges = true

const playgroundSnapshot = await takeSnapshot(playgroundPath)

overridableFiles = calculateOverridableFiles(currentPlaygroundCache, playgroundSnapshot)
}
else if (fs.existsSync(playgroundPath)) {
const result = await prompts([{
name: 'confirm',
type: 'confirm',
Expand All @@ -41,20 +129,38 @@ async function generatePlayground() {
locale = result.locale
}

await fs.remove(quizesPath)
await fs.ensureDir(quizesPath)
if (!keepChanges) {
await fs.remove(playgroundPath)
await fs.ensureDir(playgroundPath)
}

const quizes = await loadQuizes()
const incomingQuizesCache: Snapshot = {}

for (const quiz of quizes) {
const { difficulty, title } = resolveInfo(quiz, locale) as QuizMetaInfo & { difficulty: string }
const code = formatToCode(quiz, locale)
const filepath = path.join(quizesPath, `${getQuestionFullName(quiz.no, difficulty, title)}.ts`)

await fs.writeFile(filepath, code, 'utf-8')
const quizesPathByDifficulty = path.join(playgroundPath, difficulty)

const quizFileName = `${getQuestionFullName(quiz.no, difficulty, title)}.ts`
const quizPathFull = path.join(quizesPathByDifficulty, quizFileName)

if (!keepChanges || overridableFiles[quizFileName]) {
if (!fs.existsSync(quizesPathByDifficulty))
fs.mkdirSync(quizesPathByDifficulty)
await fs.writeFile(quizPathFull, code, 'utf-8')
incomingQuizesCache[quizFileName] = await calculateFileHash(quizPathFull)
}
}

fs.writeFile(playgroundCachePath, JSON.stringify({
...currentPlaygroundCache,
...incomingQuizesCache,
}))

console.log()
console.log(c.bold(c.green('Local playground generated at: ')) + c.dim(quizesPath))
console.log(c.bold(c.green('Local playground generated at: ')) + c.dim(playgroundPath))
console.log()
}

Expand Down

0 comments on commit f389d3e

Please sign in to comment.