[qns] refactor: tweak generated files structure and add locales directory

This commit is contained in:
Yangshun 2025-05-06 13:13:58 +08:00
parent 18976f321b
commit 443820b74f
13 changed files with 196 additions and 190 deletions

View File

@ -14,11 +14,28 @@ import type {
QuestionUserInterfaceBundle,
} from '~/components/interviews/questions/common/QuestionsTypes';
import { getQuestionOutPathAlgo } from './questions-bundlers/QuestionsBundlerAlgoConfig';
import { getQuestionOutPathJavaScript } from './questions-bundlers/QuestionsBundlerJavaScriptConfig';
import { getQuestionOutPathQuiz } from './questions-bundlers/QuestionsBundlerQuizConfig';
import { getQuestionOutPathSystemDesign } from './questions-bundlers/QuestionsBundlerSystemDesignConfig';
import { getQuestionOutPathUserInterface } from './questions-bundlers/QuestionsBundlerUserInterfaceConfig';
import {
getQuestionOutPathAlgo,
getQuestionOutPathAlgoLocaleContents,
} from './questions-bundlers/QuestionsBundlerAlgoConfig';
import {
getQuestionOutPathJavaScript,
getQuestionOutPathJavaScriptLocaleContents,
} from './questions-bundlers/QuestionsBundlerJavaScriptConfig';
import {
getQuestionOutPathQuiz,
getQuestionOutPathQuizLocaleContents,
} from './questions-bundlers/QuestionsBundlerQuizConfig';
import {
getQuestionOutPathSystemDesign,
getQuestionOutPathSystemDesignLocaleContents,
} from './questions-bundlers/QuestionsBundlerSystemDesignConfig';
import {
getQuestionOutPathUserInterface,
getQuestionOutPathUserInterfaceFrameworkLocaleWriteup,
getQuestionOutPathUserInterfaceFrameworkSetup,
getQuestionOutPathUserInterfaceLocaleInfo,
} from './questions-bundlers/QuestionsBundlerUserInterfaceConfig';
import { fetchQuestionsList } from './QuestionsListReader';
// Add functions which read from the generated content files.
@ -35,14 +52,14 @@ export function readQuestionAlgoContents(
const response = (() => {
try {
return fs.readFileSync(
path.join(getQuestionOutPathAlgo(slug), `${requestedLocale}.json`),
getQuestionOutPathAlgoLocaleContents(slug, requestedLocale),
);
} catch {
loadedLocale = 'en-US';
// Fallback to English.
// Fallback to English
return fs.readFileSync(
path.join(getQuestionOutPathAlgo(slug), `${loadedLocale}.json`),
getQuestionOutPathAlgoLocaleContents(slug, loadedLocale),
);
}
})();
@ -98,17 +115,14 @@ export function readQuestionJavaScriptContents(
const response = (() => {
try {
return fs.readFileSync(
path.join(
getQuestionOutPathJavaScript(slug),
`${requestedLocale}.json`,
),
getQuestionOutPathJavaScriptLocaleContents(slug, requestedLocale),
);
} catch {
loadedLocale = 'en-US';
// Fallback to English.
// Fallback to English
return fs.readFileSync(
path.join(getQuestionOutPathJavaScript(slug), `${loadedLocale}.json`),
getQuestionOutPathJavaScriptLocaleContents(slug, loadedLocale),
);
}
})();
@ -165,7 +179,7 @@ export async function readQuestionQuizContents(
try {
response = fs.readFileSync(
path.join(getQuestionOutPathQuiz(slug), `${requestedLocale}.json`),
getQuestionOutPathQuizLocaleContents(slug, requestedLocale),
);
} catch {
loadedLocale = 'en-US';
@ -173,7 +187,7 @@ export async function readQuestionQuizContents(
try {
// Fall back to English
response = fs.readFileSync(
path.join(getQuestionOutPathQuiz(slug), `${loadedLocale}.json`),
getQuestionOutPathQuizLocaleContents(slug, loadedLocale),
);
} catch {
// Fall back to finding the closest question
@ -235,17 +249,14 @@ export function readQuestionSystemDesignContents(
const response = (() => {
try {
return fs.readFileSync(
path.join(
getQuestionOutPathSystemDesign(slug),
`${requestedLocale}.json`,
),
getQuestionOutPathSystemDesignLocaleContents(slug, requestedLocale),
);
} catch {
loadedLocale = 'en-US';
// Fallback to English.
// Fallback to English
return fs.readFileSync(
path.join(getQuestionOutPathSystemDesign(slug), `${loadedLocale}.json`),
getQuestionOutPathSystemDesignLocaleContents(slug, loadedLocale),
);
}
})();
@ -283,20 +294,14 @@ export async function readQuestionUserInterface(
(() => {
try {
return fs.readFileSync(
path.join(
getQuestionOutPathUserInterface(slug),
`${requestedLocale}.json`,
),
getQuestionOutPathUserInterfaceLocaleInfo(slug, requestedLocale),
);
} catch {
loadedLocale = 'en-US';
// Fallback to English.
// Fallback to English
return fs.readFileSync(
path.join(
getQuestionOutPathUserInterface(slug),
`${loadedLocale}.json`,
),
getQuestionOutPathUserInterfaceLocaleInfo(slug, loadedLocale),
);
}
})(),
@ -312,18 +317,25 @@ export async function readQuestionUserInterface(
const framework = frameworkParam ?? metadata.frameworkDefault ?? 'vanilla';
const wirteupResponse = fs.readFileSync(
path.join(questionOutDir, framework, `writeup.${loadedLocale}.json`),
const writeupFile = fs.readFileSync(
getQuestionOutPathUserInterfaceFrameworkLocaleWriteup(
slug,
framework,
loadedLocale,
),
);
const writeup = JSON.parse(String(wirteupResponse));
const writeup = JSON.parse(String(writeupFile));
const skeletonBundle = (() => {
const response = fs.readFileSync(
path.join(questionOutDir, framework, 'setup', `skeleton.json`),
const skeleton = fs.readFileSync(
getQuestionOutPathUserInterfaceFrameworkSetup(
slug,
framework,
'skeleton',
),
);
const skeletonBundleValue = JSON.parse(String(response));
const skeletonBundleValue = JSON.parse(String(skeleton));
return {
...skeletonBundleValue,
@ -332,16 +344,14 @@ export async function readQuestionUserInterface(
})();
const solutionBundle = (() => {
const response = fs.readFileSync(
path.join(
questionOutDir,
const solution = fs.readFileSync(
getQuestionOutPathUserInterfaceFrameworkSetup(
slug,
framework,
'setup',
`${codeId ?? 'solution'}.json`,
`${codeId ?? 'solution'}`,
),
);
const solutionBundleValue = JSON.parse(String(response));
const solutionBundleValue = JSON.parse(String(solution));
const canAccessSolutionWriteup =
isViewerPremium || metadata.access === 'free';

View File

@ -10,6 +10,7 @@ export const QUESTIONS_SRC_DIR_ALGO = path.join(
'questions',
format,
);
export const QUESTIONS_OUT_DIR_ALGO = path.join(
process.cwd(),
'src',
@ -21,9 +22,18 @@ export const QUESTIONS_OUT_DIR_ALGO = path.join(
export function getQuestionSrcPathAlgo(slug: string) {
return path.join(QUESTIONS_SRC_DIR_ALGO, slug);
}
export function getQuestionOutPathAlgo(slug: string) {
return path.join(QUESTIONS_OUT_DIR_ALGO, slug);
}
export function getQuestionOutPathAlgoLocaleContents(
slug: string,
locale: string,
) {
return path.join(getQuestionOutPathAlgo(slug), 'locales', `${locale}.json`);
}
export function getQuestionsListOutFilenameAlgo(locale: string) {
return path.join(QUESTIONS_OUT_DIR_ALGO, `list.${locale}.json`);
}

View File

@ -10,6 +10,7 @@ export const QUESTIONS_SRC_DIR_JAVASCRIPT = path.join(
'questions',
format,
);
export const QUESTIONS_OUT_DIR_JAVASCRIPT = path.join(
process.cwd(),
'src',
@ -21,9 +22,22 @@ export const QUESTIONS_OUT_DIR_JAVASCRIPT = path.join(
export function getQuestionSrcPathJavaScript(slug: string) {
return path.join(QUESTIONS_SRC_DIR_JAVASCRIPT, slug);
}
export function getQuestionOutPathJavaScript(slug: string) {
return path.join(QUESTIONS_OUT_DIR_JAVASCRIPT, slug);
}
export function getQuestionOutPathJavaScriptLocaleContents(
slug: string,
locale: string,
) {
return path.join(
getQuestionOutPathJavaScript(slug),
'locales',
`${locale}.json`,
);
}
export function getQuestionsListOutFilenameJavaScript(locale: string) {
return path.join(QUESTIONS_OUT_DIR_JAVASCRIPT, `list.${locale}.json`);
}

View File

@ -120,7 +120,7 @@ export async function readQuestionQuiz(
export async function readQuestionListMetadataQuiz(
locale = 'en-US',
): Promise<ReadonlyArray<InterviewsQuestionItemMinimal>> {
// Non-JavaScript.
// Non-JavaScript
const nonJavaScriptQuestionsDirectories = fs
.readdirSync(QuestionsQuizSourceConfigNonJavaScript.questionsListPath, {
withFileTypes: true,
@ -141,7 +141,7 @@ export async function readQuestionListMetadataQuiz(
}),
);
// JavaScript.
// JavaScript
const javaScriptQuestionsDirectories = fs
.readdirSync(QuestionsQuizSourceConfigJavaScript.questionsListPath, {
withFileTypes: true,
@ -162,7 +162,7 @@ export async function readQuestionListMetadataQuiz(
}),
);
// React.
// React
const reactQuestionsDirectories = fs
.readdirSync(QuestionsQuizSourceConfigReact.questionsListPath, {
withFileTypes: true,

View File

@ -87,6 +87,14 @@ export const QuestionsQuizSourceConfigReact: QuestionsQuizSourceConfig = {
export function getQuestionOutPathQuiz(slug: string) {
return path.join(QUESTIONS_OUT_DIR_QUIZ, slug);
}
export function getQuestionOutPathQuizLocaleContents(
slug: string,
locale: string,
) {
return path.join(getQuestionOutPathQuiz(slug), 'locales', `${locale}.json`);
}
export function getQuestionsListOutFilenameQuiz(locale: string) {
return path.join(QUESTIONS_OUT_DIR_QUIZ, `list.${locale}.json`);
}

View File

@ -10,6 +10,7 @@ export const QUESTIONS_SRC_DIR_SYSTEM_DESIGN = path.join(
'questions',
format,
);
export const QUESTIONS_OUT_DIR_SYSTEM_DESIGN = path.join(
process.cwd(),
'src',
@ -21,9 +22,22 @@ export const QUESTIONS_OUT_DIR_SYSTEM_DESIGN = path.join(
export function getQuestionSrcPathSystemDesign(slug: string) {
return path.join(QUESTIONS_SRC_DIR_SYSTEM_DESIGN, slug);
}
export function getQuestionOutPathSystemDesign(slug: string) {
return path.join(QUESTIONS_OUT_DIR_SYSTEM_DESIGN, slug);
}
export function getQuestionOutPathSystemDesignLocaleContents(
slug: string,
locale: string,
) {
return path.join(
getQuestionOutPathSystemDesign(slug),
'locales',
`${locale}.json`,
);
}
export function getQuestionsListOutFilenameSystemDesign(locale: string) {
return path.join(QUESTIONS_OUT_DIR_SYSTEM_DESIGN, `list.${locale}.json`);
}

View File

@ -10,6 +10,7 @@ export const QUESTIONS_SRC_DIR_USER_INTERFACE = path.join(
'questions',
format,
);
export const QUESTIONS_OUT_DIR_USER_INTERFACE = path.join(
process.cwd(),
'src',
@ -17,12 +18,61 @@ export const QUESTIONS_OUT_DIR_USER_INTERFACE = path.join(
'questions',
format,
);
export function getQuestionSrcPathUserInterface(slug: string) {
return path.join(QUESTIONS_SRC_DIR_USER_INTERFACE, slug);
}
export function getQuestionOutPathUserInterface(slug: string) {
return path.join(QUESTIONS_OUT_DIR_USER_INTERFACE, slug);
}
export function getQuestionOutPathUserInterfaceLocaleInfo(
slug: string,
locale: string,
) {
return path.join(
getQuestionOutPathUserInterface(slug),
'info',
`${locale}.json`,
);
}
export function getQuestionOutPathUserInterfaceFramework(
slug: string,
framework: string,
) {
return path.join(
getQuestionOutPathUserInterface(slug),
'frameworks',
framework,
);
}
export function getQuestionOutPathUserInterfaceFrameworkLocaleWriteup(
slug: string,
framework: string,
locale: string,
) {
return path.join(
getQuestionOutPathUserInterfaceFramework(slug, framework),
'writeup',
`${locale}.json`,
);
}
export function getQuestionOutPathUserInterfaceFrameworkSetup(
slug: string,
framework: string,
setupType: string,
) {
return path.join(
getQuestionOutPathUserInterfaceFramework(slug, framework),
'setup',
`${setupType}.json`,
);
}
export function getQuestionsListOutFilenameUserInterface(_locale: string) {
return path.join(
QUESTIONS_OUT_DIR_USER_INTERFACE,

View File

@ -8,6 +8,7 @@ import {
} from '../db/questions-bundlers/QuestionsBundlerAlgo';
import {
getQuestionOutPathAlgo,
getQuestionOutPathAlgoLocaleContents,
getQuestionSrcPathAlgo,
QUESTIONS_SRC_DIR_ALGO,
} from '../db/questions-bundlers/QuestionsBundlerAlgoConfig';
@ -54,9 +55,10 @@ async function generateSetupForQuestion(slug: string) {
await Promise.all(
locales.map(async (locale) => {
const content = await readQuestionAlgo(slug);
const outPath = path.join(outDir, `${locale}.json`);
const contentOutPath = getQuestionOutPathAlgoLocaleContents(slug, locale);
fs.writeFileSync(outPath, JSON.stringify(content, null, 2));
fs.mkdirSync(path.dirname(contentOutPath), { recursive: true });
fs.writeFileSync(contentOutPath, JSON.stringify(content, null, 2));
}),
);
}

View File

@ -8,6 +8,7 @@ import {
} from '../db/questions-bundlers/QuestionsBundlerJavaScript';
import {
getQuestionOutPathJavaScript,
getQuestionOutPathJavaScriptLocaleContents,
getQuestionSrcPathJavaScript,
QUESTIONS_SRC_DIR_JAVASCRIPT,
} from '../db/questions-bundlers/QuestionsBundlerJavaScriptConfig';
@ -54,9 +55,13 @@ async function generateSetupForQuestion(slug: string) {
await Promise.all(
locales.map(async (locale) => {
const content = await readQuestionJavaScript(slug, locale);
const outPath = path.join(outDir, `${locale}.json`);
const contentOutPath = getQuestionOutPathJavaScriptLocaleContents(
slug,
locale,
);
fs.writeFileSync(outPath, JSON.stringify(content, null, 2));
fs.mkdirSync(path.dirname(contentOutPath), { recursive: true });
fs.writeFileSync(contentOutPath, JSON.stringify(content, null, 2));
}),
);
}

View File

@ -7,7 +7,10 @@ import {
readQuestionQuizLocaleAgnostic,
} from '../db/questions-bundlers/QuestionsBundlerQuiz';
import type { QuestionsQuizSourceConfig } from '../db/questions-bundlers/QuestionsBundlerQuizConfig';
import { getQuestionOutPathQuiz } from '../db/questions-bundlers/QuestionsBundlerQuizConfig';
import {
getQuestionOutPathQuiz,
getQuestionOutPathQuizLocaleContents,
} from '../db/questions-bundlers/QuestionsBundlerQuizConfig';
async function generateSetupForQuestion(
quizSourceConfig: QuestionsQuizSourceConfig,
@ -34,9 +37,10 @@ async function generateSetupForQuestion(
await Promise.all(
locales.map(async (locale) => {
const content = await readQuestionQuiz(quizSourceConfig, slug, locale);
const outPath = path.join(outDir, `${locale}.json`);
const contentOutPath = getQuestionOutPathQuizLocaleContents(slug, locale);
fs.writeFileSync(outPath, JSON.stringify(content, null, 2));
fs.mkdirSync(path.dirname(contentOutPath), { recursive: true });
fs.writeFileSync(contentOutPath, JSON.stringify(content, null, 2));
}),
);
}

View File

@ -1,113 +0,0 @@
import fs from 'fs';
import path from 'path';
import slugify from 'slugify';
type FileObj = {
lines: Array<string>;
slug: string | null;
title: string | null;
};
function createFileObj() {
return {
lines: [],
slug: null,
title: null,
};
}
const TITLE_MARKER = '### ';
async function processFile(topic: string, filePath: string, outDir: string) {
const file = String(fs.readFileSync(filePath));
const lines = file.split('\n');
const allFiles: Array<FileObj> = [];
let currentFile: FileObj | null = null;
let stopAdding = false;
lines.forEach((line) => {
if (line.includes('Back to top')) {
return;
}
if (line.includes('###### References')) {
stopAdding = true;
return;
}
if (line.startsWith(TITLE_MARKER)) {
stopAdding = false;
if (currentFile != null) {
allFiles.push(currentFile);
}
currentFile = createFileObj();
currentFile.title = line.replace(new RegExp(`^${TITLE_MARKER}`), '');
currentFile.slug = slugify(line)
.toLocaleLowerCase()
.replace(/(\?|\.|"|:|'|\(|\)|\*)/g, '');
} else if (!stopAdding) {
if (
line !== '' ||
(line === '' &&
currentFile!.lines.length > 0 &&
currentFile!.lines[currentFile!.lines.length - 1] !== '')
) {
currentFile?.lines.push(line);
}
}
});
if (currentFile != null) {
allFiles.push(currentFile);
}
await Promise.all(
allFiles.map(async (fileObj) => {
const fileContents =
`
---
slug: ${fileObj.slug}
title: ${fileObj.title?.includes(':') ? `"${fileObj.title}"` : fileObj.title}
languages: []
companies: []
premium: false
duration: 5
published: true
difficulty: medium
---
${fileObj.lines.join('\n').trim()}
## Source
- [Front End Interview Handbook](https://www.frontendinterviewhandbook.com/${topic}-questions)
`.trim() + '\n';
fs.mkdirSync(outDir, { recursive: true });
return await fs.writeFileSync(
path.join(outDir, `${fileObj.slug}.mdx`),
fileContents,
);
}),
);
}
processFile(
'javascript',
path.join(process.cwd(), 'scripts/data/javascript-questions.md'),
path.join(process.cwd(), 'questions/quiz/javascript'),
);
processFile(
'css',
path.join(process.cwd(), 'scripts/data/css-questions.md'),
path.join(process.cwd(), 'questions/quiz/css'),
);
processFile(
'html',
path.join(process.cwd(), 'scripts/data/html-questions.md'),
path.join(process.cwd(), 'questions/quiz/html'),
);

View File

@ -8,6 +8,7 @@ import {
} from '../db/questions-bundlers/QuestionsBundlerSystemDesign';
import {
getQuestionOutPathSystemDesign,
getQuestionOutPathSystemDesignLocaleContents,
getQuestionSrcPathSystemDesign,
QUESTIONS_SRC_DIR_SYSTEM_DESIGN,
} from '../db/questions-bundlers/QuestionsBundlerSystemDesignConfig';
@ -34,9 +35,13 @@ async function generateSetupForQuestion(slug: string) {
await Promise.all(
locales.map(async (locale) => {
const content = await readQuestionSystemDesign(slug, locale);
const outPath = path.join(outDir, `${locale}.json`);
const contentOutPath = getQuestionOutPathSystemDesignLocaleContents(
slug,
locale,
);
fs.writeFileSync(outPath, JSON.stringify(content, null, 2));
fs.mkdirSync(path.dirname(contentOutPath), { recursive: true });
fs.writeFileSync(contentOutPath, JSON.stringify(content, null, 2));
}),
);
}

View File

@ -18,6 +18,9 @@ import {
} from '../db/questions-bundlers/QuestionsBundlerUserInterface';
import {
getQuestionOutPathUserInterface,
getQuestionOutPathUserInterfaceFrameworkLocaleWriteup,
getQuestionOutPathUserInterfaceFrameworkSetup,
getQuestionOutPathUserInterfaceLocaleInfo,
getQuestionSrcPathUserInterface,
QUESTIONS_SRC_DIR_USER_INTERFACE,
} from '../db/questions-bundlers/QuestionsBundlerUserInterfaceConfig';
@ -184,14 +187,14 @@ async function generateSetupForQuestion(slug: string) {
return { ...acc, ...curr };
}, {});
const outPath = path.join(
getQuestionOutPathUserInterface(slug),
framework,
`writeup.${locale}.json`,
);
const outPath =
getQuestionOutPathUserInterfaceFrameworkLocaleWriteup(
slug,
framework,
locale,
);
fs.mkdirSync(path.dirname(outPath), { recursive: true });
fs.writeFileSync(outPath, JSON.stringify(writeupFilesObj, null, 2));
}),
);
@ -213,11 +216,7 @@ async function generateSetupForQuestion(slug: string) {
})(),
...locales.map(async (locale) => {
const info = await readQuestionInfoUserInterface(slug, locale);
const outPath = path.join(
getQuestionOutPathUserInterface(slug),
`${locale}.json`,
);
const outPath = getQuestionOutPathUserInterfaceLocaleInfo(slug, locale);
fs.mkdirSync(path.dirname(outPath), { recursive: true });
fs.writeFileSync(outPath, JSON.stringify(info, null, 2));
@ -231,15 +230,6 @@ async function generateSetupForQuestion(slug: string) {
}
});
const outPath = path.join(
getQuestionOutPathUserInterface(slug),
framework,
'setup',
`${setupType}.json`,
);
fs.mkdirSync(path.dirname(outPath), { recursive: true });
const author: string | null = (() => {
try {
const pkgJSON = JSON.parse(files['/package.json'].code);
@ -256,6 +246,13 @@ async function generateSetupForQuestion(slug: string) {
workspace,
};
const outPath = getQuestionOutPathUserInterfaceFrameworkSetup(
slug,
framework,
setupType,
);
fs.mkdirSync(path.dirname(outPath), { recursive: true });
fs.writeFileSync(outPath, JSON.stringify(bundle, null, 2));
}),
writeupFile(),