Skip to content

Commit f389d3e

Browse files
authored
feat: generate-play can structurize playground and preserve changes (type-challenges#27506)
1 parent b4a78ba commit f389d3e

File tree

3 files changed

+126
-9
lines changed

3 files changed

+126
-9
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ dist
55
*.log
66
*.local
77
playground/
8+
.playgroundcache

README.md

+10
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,16 @@ pnpm generate
119119

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

122+
Later if you want to update playground while keeping your changes:
123+
124+
```bash
125+
pnpm generate --keep-changes
126+
```
127+
OR
128+
```bash
129+
pnpm generate -K
130+
```
131+
122132
## Thanks
123133

124134
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.

scripts/generate-play.ts

+115-9
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,109 @@
11
import path from 'path'
22
import { argv } from 'process'
3+
import crypto from 'crypto'
34
import fs from 'fs-extra'
45
import c from 'picocolors'
56
import prompts from 'prompts'
67
import { formatToCode } from './actions/utils/formatToCode'
78
import { loadQuizes, resolveInfo } from './loader'
8-
import { defaultLocale, supportedLocales } from './locales'
9+
import { supportedLocales } from './locales'
910
import { getQuestionFullName } from './actions/issue-pr'
1011
import type { QuizMetaInfo } from './types'
1112

13+
type Snapshot = Record<string, string>
14+
15+
function calculateFileHash(filePathFull: string): Promise<string> {
16+
return new Promise((resolve, reject) => {
17+
const hash = crypto.createHash('sha1')
18+
const fileStream = fs.createReadStream(filePathFull)
19+
20+
fileStream.on('data', (data) => {
21+
hash.update(data)
22+
})
23+
24+
fileStream.on('end', () => {
25+
hash.update(filePathFull)
26+
resolve(hash.digest('hex'))
27+
})
28+
29+
fileStream.on('error', (err) => {
30+
reject(err)
31+
})
32+
})
33+
}
34+
35+
async function takeSnapshot(quizesPath: string) {
36+
let snapshot: Snapshot = {}
37+
38+
const files = fs.readdirSync(quizesPath)
39+
40+
for (const file of files) {
41+
// Might be a file, or a folder
42+
const fPath = path.join(quizesPath, file)
43+
const fStats = fs.statSync(fPath)
44+
45+
if (fStats.isDirectory()) {
46+
snapshot = {
47+
...snapshot,
48+
...(await takeSnapshot(fPath)),
49+
}
50+
}
51+
else {
52+
snapshot[file] = await calculateFileHash(fPath)
53+
}
54+
}
55+
56+
return snapshot
57+
}
58+
59+
function readPlaygroundCache(playgroundCachePath: string): Snapshot {
60+
if (!fs.existsSync(playgroundCachePath))
61+
return {}
62+
63+
try {
64+
const rawCacheContent = fs.readFileSync(playgroundCachePath)
65+
return JSON.parse(rawCacheContent.toString())
66+
}
67+
catch (err) {
68+
console.log(c.red('Playground cache corrupted. '
69+
+ 'Cannot generate playground without keeping your changes intact'))
70+
console.log(c.cyan('Please ensure you have run this: "pnpm generate"'))
71+
process.exit(1)
72+
}
73+
}
74+
75+
function calculateOverridableFiles(cache: Snapshot, snapshot: Snapshot) {
76+
const result: Snapshot = {}
77+
78+
for (const quizName in snapshot) {
79+
if (snapshot[quizName] === cache[quizName])
80+
result[quizName] = snapshot[quizName]
81+
}
82+
83+
return result
84+
}
85+
1286
async function generatePlayground() {
13-
const quizesPath = path.join(__dirname, '../playground')
87+
const playgroundPath = path.join(__dirname, '../playground')
88+
const playgroundCachePath = path.join(__dirname, '../.playgroundcache')
89+
1490
let locale = supportedLocales.find(locale => locale === argv[2])!
1591

16-
console.log(c.bold(c.cyan('Generateing local playground...\n')))
92+
console.log(c.bold(c.cyan('Generating local playground...\n')))
93+
94+
let overridableFiles: Snapshot = {}
95+
let keepChanges = false
96+
const currentPlaygroundCache = readPlaygroundCache(playgroundCachePath)
1797

18-
if (fs.existsSync(quizesPath)) {
98+
if (argv.length === 3 && (argv[2] === '--keep-changes' || argv[2] === '-K')) {
99+
console.log(c.bold(c.cyan('We will keep your chanegs while generating.\n')))
100+
keepChanges = true
101+
102+
const playgroundSnapshot = await takeSnapshot(playgroundPath)
103+
104+
overridableFiles = calculateOverridableFiles(currentPlaygroundCache, playgroundSnapshot)
105+
}
106+
else if (fs.existsSync(playgroundPath)) {
19107
const result = await prompts([{
20108
name: 'confirm',
21109
type: 'confirm',
@@ -41,20 +129,38 @@ async function generatePlayground() {
41129
locale = result.locale
42130
}
43131

44-
await fs.remove(quizesPath)
45-
await fs.ensureDir(quizesPath)
132+
if (!keepChanges) {
133+
await fs.remove(playgroundPath)
134+
await fs.ensureDir(playgroundPath)
135+
}
46136

47137
const quizes = await loadQuizes()
138+
const incomingQuizesCache: Snapshot = {}
139+
48140
for (const quiz of quizes) {
49141
const { difficulty, title } = resolveInfo(quiz, locale) as QuizMetaInfo & { difficulty: string }
50142
const code = formatToCode(quiz, locale)
51-
const filepath = path.join(quizesPath, `${getQuestionFullName(quiz.no, difficulty, title)}.ts`)
52143

53-
await fs.writeFile(filepath, code, 'utf-8')
144+
const quizesPathByDifficulty = path.join(playgroundPath, difficulty)
145+
146+
const quizFileName = `${getQuestionFullName(quiz.no, difficulty, title)}.ts`
147+
const quizPathFull = path.join(quizesPathByDifficulty, quizFileName)
148+
149+
if (!keepChanges || overridableFiles[quizFileName]) {
150+
if (!fs.existsSync(quizesPathByDifficulty))
151+
fs.mkdirSync(quizesPathByDifficulty)
152+
await fs.writeFile(quizPathFull, code, 'utf-8')
153+
incomingQuizesCache[quizFileName] = await calculateFileHash(quizPathFull)
154+
}
54155
}
55156

157+
fs.writeFile(playgroundCachePath, JSON.stringify({
158+
...currentPlaygroundCache,
159+
...incomingQuizesCache,
160+
}))
161+
56162
console.log()
57-
console.log(c.bold(c.green('Local playground generated at: ')) + c.dim(quizesPath))
163+
console.log(c.bold(c.green('Local playground generated at: ')) + c.dim(playgroundPath))
58164
console.log()
59165
}
60166

0 commit comments

Comments
 (0)