| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131 |
- import assert from 'node:assert/strict';
- import { readFileSync } from 'node:fs';
- import { resolve } from 'node:path';
- const modules = [
- 'api',
- 'database',
- 'api-database',
- 'tasks',
- 'redis',
- 'docker',
- 'linux',
- 'troubleshooting',
- 'distributed',
- ];
- let lessonsChecked = 0;
- let stepsChecked = 0;
- for (const slug of modules) {
- const lessons = JSON.parse(
- readFileSync(
- resolve(
- slug === 'api' ? 'app/lessons.json' : `public/content/${slug}.json`,
- ),
- 'utf8',
- ).replace(/^\uFEFF/, ''),
- );
- const markdown = readFileSync(
- resolve(`public/downloads/${slug}-lessons.md`),
- 'utf8',
- );
- for (const [index, lesson] of lessons.entries()) {
- const label = `${slug}/${lesson.id}`;
- const marker = `## ${lesson.id} ${lesson.title}\n`;
- const start = markdown.indexOf(marker);
- assert(start >= 0, `${label}: missing downloaded lesson`);
- const next = lessons[index + 1];
- const end = next
- ? markdown.indexOf(`## ${next.id} ${next.title}\n`, start + marker.length)
- : markdown.length;
- assert(end > start, `${label}: lesson order changed`);
- const lessonText = markdown.slice(start, end);
- assert(
- !lessonText.includes('### 必要概念'),
- `${label}: concepts still front-loaded`,
- );
- const theory = lesson.kind === 'theory';
- const positions = [
- '### 从一个问题开始',
- '### 这节课要完成什么',
- `### ${theory ? '一步步推演场景' : '一步步完成案例'}`,
- `### ${theory ? '检查推演结果' : '检查学习结果'}`,
- `### ${theory ? '自己设计一次' : '自己试一次'}`,
- '### 核心概念回顾',
- `### ${theory ? '延伸阅读' : '技术文档'}`,
- ].map((heading) => {
- const position = lessonText.indexOf(heading);
- assert(position >= 0, `${label}: missing section ${heading}`);
- return position;
- });
- assert(
- positions.every((position, i) => !i || position > positions[i - 1]),
- `${label}: self-study reading order is incorrect`,
- );
- for (const content of [
- lesson.intro.scenario,
- lesson.intro.outcome,
- ...lesson.intro.startingPoint,
- ]) {
- assert(
- lessonText.slice(0, positions[2]).includes(content),
- `${label}: preparation missing from download`,
- );
- }
- let cursor = positions[2];
- for (const [stepIndex, step] of lesson.steps.entries()) {
- const heading = `#### ${stepIndex + 1}. ${step.title}\n`;
- const at = lessonText.indexOf(heading, cursor);
- assert(
- at >= cursor && at < positions[3],
- `${label}: step ${stepIndex + 1} missing or misplaced`,
- );
- const nextStep = lesson.steps[stepIndex + 1];
- const until = nextStep
- ? lessonText.indexOf(
- `#### ${stepIndex + 2}. ${nextStep.title}\n`,
- at + heading.length,
- )
- : positions[3];
- assert(until > at, `${label}: step boundaries are incorrect`);
- const stepText = lessonText.slice(at, until);
- const actionAt = stepText.indexOf(step.body);
- const expectedAt = stepText.indexOf(
- `**${theory ? '对照你的推演' : '你应该看到什么'}**`,
- );
- const explainAt = stepText.indexOf(
- `**${theory ? '从这里理解原因' : '刚才发生了什么'}**`,
- );
- assert(
- actionAt >= 0 && expectedAt > actionAt && explainAt > expectedAt,
- `${label}/step-${stepIndex + 1}: action → observation → explanation broken`,
- );
- assert(
- stepText.slice(expectedAt, explainAt).includes(step.expected),
- `${label}: expected result lost`,
- );
- assert(
- stepText.slice(explainAt).includes(step.explanation),
- `${label}: explanation lost`,
- );
- if (step.code) {
- const codeAt = stepText.indexOf(step.code, actionAt + step.body.length);
- assert(
- codeAt > actionAt && codeAt < expectedAt,
- `${label}: code must precede observation`,
- );
- }
- cursor = until;
- stepsChecked++;
- }
- for (const concept of lesson.concepts) {
- assert(
- lessonText.slice(positions[5], positions[6]).includes(concept.body),
- `${label}: concept recap lost`,
- );
- }
- lessonsChecked++;
- }
- }
- console.log(
- `Self-study downloads: ${lessonsChecked} lessons and ${stepsChecked} steps preserve preparation, action, observation, explanation and final recap.`,
- );
|