Skip to content

Commit 70f25f2

Browse files
authored
Merge pull request #146 from geeksblabla/feat/add-yaml-validation
Add YAML validation for survey files
2 parents 2378a66 + 238a235 commit 70f25f2

File tree

9 files changed

+1600
-32
lines changed

9 files changed

+1600
-32
lines changed

package.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,16 @@
55
"scripts": {
66
"dev": "astro dev",
77
"start": "astro dev",
8-
"build": "astro check && astro build",
9-
"check": "rimraf dist && astro check",
8+
"build": "pnpm validate-survey && astro check && astro build",
9+
"check": "rimraf dist && pnpm validate-survey && astro check",
1010
"preview": "astro preview",
1111
"astro": "astro",
1212
"test": "vitest",
1313
"test:ui": "vitest --ui",
1414
"test:ci": "vitest --coverage.enabled true",
1515
"lint": "prettier --write \"**/*.{js,jsx,ts,tsx,md,mdx,astro}\" && eslint --fix \"src/**/*.{js,ts,jsx,tsx,astro}\"",
1616
"lint:ci": "eslint \"src/**/*.{js,ts,jsx,tsx,astro}\"",
17+
"validate-survey": "tsx ./scripts/validate-survey.ts",
1718
"prepare": "husky",
1819
"export-results": " tsx --env-file=.env.local ./scripts/export-results.ts",
1920
"export-results:ci": " tsx ./scripts/export-results.ts"
@@ -52,7 +53,8 @@
5253
"tailwindcss": "^3.4.4",
5354
"tailwindcss-animate": "^1.0.7",
5455
"typescript": "^5.4.5",
55-
"xstate": "^5.24.0"
56+
"xstate": "^5.24.0",
57+
"zod": "^4.1.13"
5658
},
5759
"devDependencies": {
5860
"@rollup/plugin-yaml": "^4.1.2",

pnpm-lock.yaml

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

scripts/generate-questions.ts

Lines changed: 50 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,35 +2,59 @@
22

33
import yaml from "js-yaml";
44
import fs from "fs";
5+
import path from "path";
6+
import {
7+
validateAllSurveyFiles,
8+
formatValidationReport
9+
} from "../src/lib/validators/survey-validator";
10+
import type {
11+
SurveyQuestion,
12+
SurveyQuestionsYamlFile
13+
} from "../src/lib/validators/survey-schema";
14+
import { SURVEY_DIR } from "../src/lib/validators/constants";
515

616
const generate = async () => {
17+
console.log("Starting survey questions generation...\n");
18+
19+
// Phase 1: Validate all survey files
20+
console.log("Phase 1: Validating all survey YAML files...");
21+
const validationReport = validateAllSurveyFiles(SURVEY_DIR);
22+
23+
console.log(formatValidationReport(validationReport));
24+
25+
if (!validationReport.success) {
26+
console.error(
27+
"\n❌ Validation failed! Please fix the errors above before generating questions.json"
28+
);
29+
process.exit(1);
30+
}
31+
32+
console.log("✓ All survey files validated successfully!\n");
33+
34+
// Phase 2: Generate questions.json
35+
console.log("Phase 2: Generating questions.json...");
36+
737
const QS: Record<string, SurveyQuestion> = {};
8-
// Get document, or throw exception on error
38+
939
try {
10-
const profile = (await yaml.load(
11-
fs.readFileSync("./survey/1-profile.yml", "utf8")
12-
)) as SurveyQuestionsYamlFile;
13-
const learning = (await yaml.load(
14-
fs.readFileSync("./survey/2-learning-and-education.yml", "utf8")
15-
)) as SurveyQuestionsYamlFile;
16-
const work = (await yaml.load(
17-
fs.readFileSync("./survey/3-work.yml", "utf8")
18-
)) as SurveyQuestionsYamlFile;
19-
const ai = (await yaml.load(
20-
fs.readFileSync("./survey/4-ai.yml", "utf8")
21-
)) as SurveyQuestionsYamlFile;
22-
const tech = (await yaml.load(
23-
fs.readFileSync("./survey/5-tech.yml", "utf8")
24-
)) as SurveyQuestionsYamlFile;
25-
const community = (await yaml.load(
26-
fs.readFileSync("./survey/6-community.yml", "utf8")
27-
)) as SurveyQuestionsYamlFile;
28-
const data = [profile, learning, work, tech, ai, community];
40+
const files = fs
41+
.readdirSync(SURVEY_DIR)
42+
.filter((file) => file.endsWith(".yml"))
43+
.sort();
44+
45+
const data: SurveyQuestionsYamlFile[] = [];
46+
47+
for (const file of files) {
48+
const filepath = path.join(SURVEY_DIR, file);
49+
const content = fs.readFileSync(filepath, "utf8");
50+
const parsed = yaml.load(content) as SurveyQuestionsYamlFile;
51+
data.push(parsed);
52+
}
2953

3054
data.forEach(({ label, questions }) => {
3155
questions.forEach((element, index: number) => {
3256
const id = `${label}-q-${index}`;
33-
console.log(id);
57+
console.log(` Generated: ${id}`);
3458
QS[id] = {
3559
...element,
3660
multiple: element.multiple ?? false, // default to false
@@ -41,9 +65,12 @@ const generate = async () => {
4165

4266
writeToFile("./scripts/questions.json", QS);
4367

44-
console.log(QS);
68+
console.log(
69+
`\n✓ Successfully generated ${Object.keys(QS).length} questions`
70+
);
4571
} catch (e) {
46-
console.log(e);
72+
console.error("\n❌ Error generating questions:", e);
73+
process.exit(1);
4774
}
4875
};
4976

scripts/validate-survey.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* Standalone script to validate all survey YAML files
3+
* This can be run independently or as part of the build process
4+
*/
5+
6+
import {
7+
validateAllSurveyFiles,
8+
formatValidationReport
9+
} from "../src/lib/validators/survey-validator";
10+
import { SURVEY_DIR } from "../src/lib/validators/constants";
11+
12+
async function main() {
13+
console.log("🔍 Validating survey YAML files...\n");
14+
15+
const validationReport = validateAllSurveyFiles(SURVEY_DIR);
16+
17+
console.log(formatValidationReport(validationReport));
18+
19+
if (validationReport.success) {
20+
console.log("\n✓ All validations passed!");
21+
process.exit(0);
22+
} else {
23+
console.error("\n❌ Validation failed! Please fix the errors above.");
24+
process.exit(1);
25+
}
26+
}
27+
28+
main();

src/components/survey/index.astro

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,30 @@
11
---
2-
import profileQuestions from "@/survey/1-profile.yml";
3-
import learningQuestions from "@/survey/2-learning-and-education.yml";
4-
import workQuestion from "@/survey/3-work.yml";
5-
import aiQuestion from "@/survey/4-ai.yml";
6-
import techQuestion from "@/survey/5-tech.yml";
7-
import communityQuestion from "@/survey/6-community.yml";
2+
import profileQuestionsRaw from "@/survey/1-profile.yml";
3+
import learningQuestionsRaw from "@/survey/2-learning-and-education.yml";
4+
import workQuestionRaw from "@/survey/3-work.yml";
5+
import aiQuestionRaw from "@/survey/4-ai.yml";
6+
import techQuestionRaw from "@/survey/5-tech.yml";
7+
import communityQuestionRaw from "@/survey/6-community.yml";
88
import { SurveyApp } from "./survey-app";
99
import ExitPopup from "./exit-popup.astro";
10+
import { validateSurveyFile } from "@/lib/validators/survey-schema";
11+
12+
// Validate each YAML file to ensure data integrity
13+
const profileQuestions = validateSurveyFile(
14+
profileQuestionsRaw,
15+
"1-profile.yml"
16+
);
17+
const learningQuestions = validateSurveyFile(
18+
learningQuestionsRaw,
19+
"2-learning-and-education.yml"
20+
);
21+
const workQuestion = validateSurveyFile(workQuestionRaw, "3-work.yml");
22+
const aiQuestion = validateSurveyFile(aiQuestionRaw, "4-ai.yml");
23+
const techQuestion = validateSurveyFile(techQuestionRaw, "5-tech.yml");
24+
const communityQuestion = validateSurveyFile(
25+
communityQuestionRaw,
26+
"6-community.yml"
27+
);
1028
1129
const questions = [
1230
profileQuestions,

src/lib/validators/constants.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
export const SURVEY_DIR = "./survey";
2+
3+
export const VALIDATION_THRESHOLDS = {
4+
MIN_LABEL_LENGTH: 3,
5+
MAX_LABEL_LENGTH: 500,
6+
MIN_TITLE_LENGTH: 2,
7+
MAX_CHOICE_LENGTH: 200,
8+
MIN_CHOICES: 2,
9+
MAX_CHOICES_WARNING: 30,
10+
MIN_QUESTIONS_WARNING: 2,
11+
MAX_QUESTIONS_WARNING: 20,
12+
OPTIONAL_RATIO_WARNING: 0.5
13+
} as const;
14+
15+
export enum ValidationSeverity {
16+
ERROR = "error",
17+
WARNING = "warning",
18+
INFO = "info"
19+
}
20+
21+
export interface ValidationError {
22+
severity: ValidationSeverity;
23+
message: string;
24+
path?: string;
25+
value?: unknown;
26+
}

0 commit comments

Comments
 (0)