chore: import upstream snapshot with attribution
i18n - Build Validation / Validate i18n Builds (24) (push) Has been cancelled
CI - Node.js / Lint (24) (push) Has been cancelled
CI - Node.js / Build (24) (push) Has been cancelled
CI - Node.js / Test (24) (push) Has been cancelled
CI - Node.js / Test - Upcoming Changes (24) (push) Has been cancelled
CI - Node.js / Test - i18n (italian, 24) (push) Has been cancelled
CI - Node.js / Test - i18n (portuguese, 24) (push) Has been cancelled
CD - Docker - GHCR Images / Build and Push Images (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 11:55:53 +08:00
commit dde272c4b8
19405 changed files with 2730632 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
#!/bin/bash
# Define the source file
source_file="curriculum/challenges/english/22-rosetta-code/rosetta-code-challenges/execute-brain.md"
# Define the destination directory
destination_dir="curriculum/challenges"
# Loop through all directories except _meta and english
for dir in $(find "$destination_dir" -mindepth 1 -maxdepth 1 -type d ! -name '_meta' ! -name 'english'); do
# Copy the file to each directory
cp "./$source_file" "./$dir/22-rosetta-code/rosetta-code-challenges/"
done
+36
View File
@@ -0,0 +1,36 @@
import fs from 'fs';
import path from 'path';
/* This can be used to create NGINX maps for redirects. After running this
script with `npx tsx redirect-gen`, the map should appear in the current
directory.
*/
function createRedirectMap(): void {
const basePath = '../../../curriculum/challenges/english/18-project-euler';
const directories = fs.readdirSync(path.resolve(__dirname, basePath));
let mapObject = '';
for (let i = 0; i < directories.length; i++) {
const files = fs.readdirSync(
path.resolve(__dirname, `${basePath}/${directories[i]}`)
);
for (let j = 0; j < files.length; j++) {
const fileName = path.parse(files[j]).name;
mapObject += `~^/learn/coding-interview-prep/project-euler/${fileName}/?$ /learn/project-euler/${directories[i]}/${fileName}; \n`;
}
}
fs.writeFile('redirectMap.map', mapObject, 'utf8', function (err) {
if (err) {
console.log('An error occurred while writing MAP redirect file', err);
return console.log(err);
}
console.log('Map file has been saved.');
});
}
createRedirectMap();
+33
View File
@@ -0,0 +1,33 @@
## WARNING: Never change any of the ID's or delete anything. Mark things as deprecated instead.
### How to create a new exam:
1. Add the exam file in the `exams` folder
2. Add the exam meta data:
- `_id`: This should match the `id` in the exam challenge markdown file,
- `title`: This should match the `title` in the exam challenge markdown file,
- `numberOfQuestionsInExam`: This is how many questions will be given to campers who take the exam. It must be less than or equal to the length of the `questions` array.
- `passingPercent`: Percent of questions needed to get correct to pass the exam
- `prerequisites`: Array of challenges that are required to complete before being able to take the exam. Each should have an `id` and `title` that match what's in their challenge markdown file
- `questions`: Array of exam questions
3. Add the exam questions to the `questions` array:
- `question`: The exam question
- `wrongAnswers`: Array of answers. There should be at least 4, but 6-7 would be ideal.
- `correctAnswers`: Array of answers. There should be at least 1, but 2 or more would be ideal when possible.
4. `wrongAnswers` and `correctAnswers` arrays:
- `answer`: This is one of the multiple choice options
5. Add the ID's:
- Change the `examPath` variable in the `add-nano-ids.js` file to the name of the new exam file
- Run it with `node add-nano-ids.js`. It will add an `id` to each `question`, and each `answer`.
Add a `deprecated: true` property to any of the `questions`, `wrongAnswers`, or `correctAnswers`. Any that include this will be omitted when generating an exam.
The exam files in this folder are not used in production. Never push real exam questions to GitHub or anywhere public. These exams are for local development and testing. To seed the real exams to staging/production databases, replace the example exams here with the real exams, connect to the desired database, and run the `create-exams.js` script.
+54
View File
@@ -0,0 +1,54 @@
/*
* Script that will add `id` fields to the `questions`, `wrong_answers`, and
* `correct_answers` of an exam file where the `id` doesn't exist.
*/
import { readFileSync, writeFileSync } from 'fs';
import { customAlphabet } from 'nanoid';
import yaml from 'js-yaml';
const newId = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz', 10);
// Change this to the path of the file you want to add id's to
const examPath = './exams/example-certification-exam.yml';
const examFile = readFileSync(examPath, { encoding: 'utf-8' });
const examJson = yaml.load(examFile);
// The strangeness of how the id's are added below is to keep the id at the top
// of each object when rewriting the YAML at the end
const newQuestions = [];
examJson.questions?.forEach(question => {
let newQuestion;
if (!question.id) {
newQuestion = { id: newId(), ...question };
} else {
newQuestion = { ...question };
}
let newWrongAnswers = [];
question.wrongAnswers?.forEach(answer => {
if (!answer.id) {
newWrongAnswers.push({ id: newId(), ...answer });
} else {
newWrongAnswers.push(answer);
}
});
let newCorrectAnswers = [];
question.correctAnswers?.forEach(answer => {
if (!answer.id) {
newCorrectAnswers.push({ id: newId(), ...answer });
} else {
newCorrectAnswers.push(answer);
}
});
newQuestion.wrongAnswers = newWrongAnswers;
newQuestion.correctAnswers = newCorrectAnswers;
newQuestions.push(newQuestion);
});
examJson.questions = newQuestions;
const yamlStr = yaml.dump(examJson);
writeFileSync(examPath, yamlStr, 'utf8');
+80
View File
@@ -0,0 +1,80 @@
import { readFileSync } from 'fs';
import path, { join } from 'path';
import { fileURLToPath } from 'url';
import debug from 'debug';
import dotenv from 'dotenv';
import yaml from 'js-yaml';
import { MongoClient, ObjectId } from 'mongodb';
import { validateExamSchema } from './exam-schema.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const log = debug('fcc:tools:seedExams');
dotenv.config({ path: path.resolve(__dirname, '../../../.env') });
const { MONGOHQ_URL } = process.env;
// Only these will be added to or updated in the database
const examFilenames = [
'foundational-c-sharp-with-microsoft-certification-exam.yml',
'example-certification-exam.yml'
];
const client = new MongoClient(MONGOHQ_URL);
function handleError(err, client) {
if (err) {
console.error('Oh noes!! Error seeding exams.');
console.error(err);
try {
client.close();
} catch {
// no-op
} finally {
process.exit(1);
}
}
}
const seed = async () => {
await client.db('admin').command({ ping: 1 });
log('Connected successfully to mongo');
const db = client.db('freecodecamp');
const exams = db.collection('Exam');
for (const filename of examFilenames) {
try {
const examPath = join(__dirname, 'exams', filename);
const examFile = readFileSync(examPath, { encoding: 'utf-8' });
const examJson = yaml.load(examFile);
const validExam = validateExamSchema(examJson);
if (validExam.error) {
throw new Error(
`Invalid exam schema for '${filename}': ${validExam.error.message}`
);
}
examJson._id = new ObjectId(examJson._id);
// Update existing database object, or create new if it doesn't exist
await exams.updateOne(
{ _id: examJson._id },
{ $set: examJson },
{ upsert: true }
);
log(`'${examJson.title}' added to exams database.`);
} catch (err) {
handleError(err, client);
}
}
log('Finished seeding exams.');
};
seed()
.then(() => client.close())
.catch(err => handleError(err, client));
@@ -0,0 +1,13 @@
import { configTypeChecked } from '@freecodecamp/eslint-config/base';
import globals from 'globals';
export default [
...configTypeChecked,
{
languageOptions: {
globals: {
...globals.node // TODO: migrate to ESM and remove globals
}
}
}
];
+86
View File
@@ -0,0 +1,86 @@
import Joi from 'joi';
import JoiObjectId from 'joi-objectid';
Joi.objectId = JoiObjectId(Joi);
const nanoIdRE = new RegExp('[a-z0-9]{10}');
const PrerequisitesJoi = Joi.object().keys({
id: Joi.objectId().required(),
title: Joi.string()
});
const AnswerJoi = Joi.object().keys({
id: Joi.string().regex(nanoIdRE).required(),
deprecated: Joi.bool(),
answer: Joi.string().required()
});
const QuestionJoi = Joi.object().keys({
id: Joi.string().regex(nanoIdRE).required(),
question: Joi.string().required(),
deprecated: Joi.bool(),
wrongAnswers: Joi.array()
.items(AnswerJoi)
.required()
.custom((value, helpers) => {
const nonDeprecatedCount = value.reduce(
(count, answer) => (answer.deprecated ? count : count + 1),
0
);
const minimumAnswers = 4;
if (nonDeprecatedCount < minimumAnswers) {
return helpers.message(
`'wrongAnswers' must have at least ${minimumAnswers} non-deprecated answers.`
);
}
return value;
}),
correctAnswers: Joi.array()
.items(AnswerJoi)
.required()
.custom((value, helpers) => {
const nonDeprecatedCount = value.reduce(
(count, answer) => (answer.deprecated ? count : count + 1),
0
);
const minimumAnswers = 1;
if (nonDeprecatedCount < minimumAnswers) {
return helpers.message(
`'correctAnswers' must have at least ${minimumAnswers} non-deprecated answer.`
);
}
return value;
})
});
const schema = Joi.object().keys({
// TODO: make sure _id and title match what's in the challenge markdown file
_id: Joi.objectId().required(),
title: Joi.string().required(),
numberOfQuestionsInExam: Joi.number()
.min(1)
.max(
Joi.ref('questions', {
adjust: questions => {
const nonDeprecatedCount = questions.reduce(
(count, question) => (question.deprecated ? count : count + 1),
0
);
return nonDeprecatedCount;
}
})
)
.required(),
passingPercent: Joi.number().min(0).max(100).required(),
prerequisites: Joi.array().items(PrerequisitesJoi),
questions: Joi.array().items(QuestionJoi).min(1).required()
});
export const validateExamSchema = exam => {
return schema.validate(exam);
};
@@ -0,0 +1,166 @@
_id: 645147516c245de4d11eb7ba
title: Example Certification Exam
numberOfQuestionsInExam: 5
passingPercent: 70
questions:
- id: vjxyn2ngzs
question: Which of the following is NOT a JavaScript data type?
wrongAnswers:
- id: h1x269fts9
answer: Integer
- id: 3x3j6ts2sb
answer: Boolean
- id: 9g4ix2tr1z
answer: Symbol
- id: n1rfllrvvy
answer: String
- id: 33oq8q3aio
answer: Bigint
- id: 7w1gaddxup
answer: Undefined
- id: ywgqr2wp6i
answer: Object
correctAnswers:
- id: ydmnlkjviv
answer: Array
- id: sy2trjs7p8
answer: Float
- id: lpmv63p5sv
question: What does the `NaN` keyword represent in JavaScript?
wrongAnswers:
- id: 3cxs9as4bt
answer: Not a Node
- id: axn8omt3zf
answer: Null and None
- id: 7ssyazt4zu
answer: No Assignment Named
- id: do4cm37x7y
answer: Negative Assertion Needed
- id: r2hbfnrodz
answer: Never Allow Null
correctAnswers:
- id: ah5kkj38s1
answer: Not a Number
- id: g2hudvmgoj
question: >-
Which method is used to remove the last element from an array in
JavaScript?
wrongAnswers:
- id: jrxd1hjv63
answer: '`shift()`'
- id: udc3kecrqn
answer: '`unshift()`'
- id: a12aysp2et
answer: '`slice()`'
- id: xljr0lkb0n
answer: '`splice()`'
- id: qe4z3kxgyz
answer: '`concat()`'
correctAnswers:
- id: gwddf01p89
answer: '`pop()`'
- id: 7tfdjebrnq
question: >-
What is the correct way to create a new instance of an object in
JavaScript?
wrongAnswers:
- id: 5bygwpaz4d
answer: '`Object.create(MyObject);`'
- id: h5s3304gbl
answer: '`MyObject.create();`'
- id: mdsjsjpi9t
answer: '`Object.new(MyObject);`'
- id: 3o8otd8j9h
answer: '`MyObject.new();`'
- id: h4ozml27zn
answer: '`MyObject.instance();`'
deprecated: true
correctAnswers:
- id: qbfhqmnedn
answer: '`new MyObject();`'
- id: hx0gxr5yjc
question: What is the purpose of the `querySelectorAll` method in JavaScript?
deprecated: true
wrongAnswers:
- id: 71iry42cy1
answer: To select all elements with a specific ID
- id: jjyk5e9e1l
answer: To select all elements of a specific tag name
- id: k5tpc2o2q8
answer: To select all child elements of a specific parent
- id: s3cm1tq4wt
answer: To select all elements with a specific attribute
- id: l1gtuheh87
answer: To select elements based on their position in the DOM
correctAnswers:
- id: e6xz6zdpxr
answer: To select all elements with a specific class name
- id: eigcj3e0mg
question: Which operator is used to compare two values for equality in JavaScript?
wrongAnswers:
- id: ltgdcoyonb
answer: '`!=`'
- id: f3szfel7x6
answer: '`<`'
- id: b0meyxhvtu
answer: '`>`'
- id: 31dd0pte4i
answer: '`<=`'
- id: 9u8bi7wqa2
answer: '`>=`'
correctAnswers:
- id: fmo9sof6v2
answer: '`==`'
deprecated: true
- id: tl6eo06r4x
answer: '`===`'
- id: l5f2beg2ih
question: >-
Which of the following statements is used to terminate a loop in
JavaScript?
wrongAnswers:
- id: kqi1fg8vtm
answer: stop
- id: a2clf7fmyx
answer: exit
- id: v58wx865e7
answer: return
- id: 5mgeaetnf0
answer: halt
- id: 2u73kyry6o
answer: skip
correctAnswers:
- id: 7j046bkjmf
answer: break
- id: atuq2zwg4h
question: What is the correct syntax to define a function in JavaScript?
wrongAnswers:
- id: dusl68wx2o
answer: '`function myFunction {}`'
- id: dlwjt59a5s
answer: '`def myFunction():`'
- id: 98p8ai27ko
answer: '`function = myFunction {}`'
- id: 7ciq8sood9
answer: '`function myFunction():`'
correctAnswers:
- id: wc3h7uc4zn
answer: '`function myFunction() {}`'
- id: 3ne0ju7yzq
question: >-
Which of the following methods is used to add a new element to the end of
an array in JavaScript?
wrongAnswers:
- id: fytb9yvbug
answer: shift()
- id: wnc31qc1ie
answer: unshift()
- id: 22mi7j64h5
answer: pop()
- id: da96ukvtol
answer: splice()
- id: gc6elnmtzw
answer: concat()
correctAnswers:
- id: pymbxhyn12
answer: push()
@@ -0,0 +1,179 @@
_id: 647e22d18acb466c97ccbef8
title: Foundational C# with Microsoft Certification Exam
numberOfQuestionsInExam: 5
passingPercent: 75
prerequisites:
- id: 647f85d407d29547b3bee1bb
title: Trophy - Write Your First Code Using C#
- id: 647f87dc07d29547b3bee1bf
title: Trophy - Create and Run Simple C# Console Applications
- id: 647f882207d29547b3bee1c0
title: Trophy - Add Logic to C# Console Applications
- id: 647f867a07d29547b3bee1bc
title: Trophy - Work with Variable Data in C# Console Applications
- id: 647f877f07d29547b3bee1be
title: Trophy - Create Methods in C# Console Applications
- id: 647f86ff07d29547b3bee1bd
title: Trophy - Debug C# Console Applications
questions:
- id: vjxyn2ngzs
question: Which of the following is NOT a JavaScript data type?
wrongAnswers:
- id: 5y7xv9ppc1
answer: Integer
- id: 3x3j6ts2sb
answer: Boolean
- id: 9g4ix2tr1z
answer: Symbol
- id: n1rfllrvvy
answer: String
- id: 33oq8q3aio
answer: Bigint
- id: 7w1gaddxup
answer: Undefined
- id: ywgqr2wp6i
answer: Object
correctAnswers:
- id: ydmnlkjviv
answer: Array
- id: sy2trjs7p8
answer: Float
- id: lpmv63p5sv
question: What does the `NaN` keyword represent in JavaScript?
wrongAnswers:
- id: 3cxs9as4bt
answer: Not a Node
- id: axn8omt3zf
answer: Null and None
- id: 7ssyazt4zu
answer: No Assignment Named
- id: do4cm37x7y
answer: Negative Assertion Needed
- id: r2hbfnrodz
answer: Never Allow Null
correctAnswers:
- id: ah5kkj38s1
answer: Not a Number
- id: g2hudvmgoj
question: >-
Which method is used to remove the last element from an array in
JavaScript?
wrongAnswers:
- id: jrxd1hjv63
answer: '`shift()`'
- id: udc3kecrqn
answer: '`unshift()`'
- id: a12aysp2et
answer: '`slice()`'
- id: xljr0lkb0n
answer: '`splice()`'
- id: qe4z3kxgyz
answer: '`concat()`'
correctAnswers:
- id: gwddf01p89
answer: '`pop()`'
- id: 7tfdjebrnq
question: >-
What is the correct way to create a new instance of an object in
JavaScript?
wrongAnswers:
- id: 5bygwpaz4d
answer: '`Object.create(MyObject);`'
- id: h5s3304gbl
answer: '`MyObject.create();`'
- id: mdsjsjpi9t
answer: '`Object.new(MyObject);`'
- id: 3o8otd8j9h
answer: '`MyObject.new();`'
- id: h4ozml27zn
answer: '`MyObject.instance();`'
deprecated: true
correctAnswers:
- id: qbfhqmnedn
answer: '`new MyObject();`'
- id: hx0gxr5yjc
question: What is the purpose of the `querySelectorAll` method in JavaScript?
deprecated: true
wrongAnswers:
- id: 71iry42cy1
answer: To select all elements with a specific ID
- id: jjyk5e9e1l
answer: To select all elements of a specific tag name
- id: k5tpc2o2q8
answer: To select all child elements of a specific parent
- id: s3cm1tq4wt
answer: To select all elements with a specific attribute
- id: l1gtuheh87
answer: To select elements based on their position in the DOM
correctAnswers:
- id: e6xz6zdpxr
answer: To select all elements with a specific class name
- id: eigcj3e0mg
question: Which operator is used to compare two values for equality in JavaScript?
wrongAnswers:
- id: ltgdcoyonb
answer: '`!=`'
- id: f3szfel7x6
answer: '`<`'
- id: b0meyxhvtu
answer: '`>`'
- id: 31dd0pte4i
answer: '`<=`'
- id: 9u8bi7wqa2
answer: '`>=`'
correctAnswers:
- id: fmo9sof6v2
answer: '`==`'
deprecated: true
- id: tl6eo06r4x
answer: '`===`'
- id: l5f2beg2ih
question: >-
Which of the following statements is used to terminate a loop in
JavaScript?
wrongAnswers:
- id: kqi1fg8vtm
answer: stop
- id: a2clf7fmyx
answer: exit
- id: v58wx865e7
answer: return
- id: 5mgeaetnf0
answer: halt
- id: 2u73kyry6o
answer: skip
correctAnswers:
- id: 7j046bkjmf
answer: break
- id: atuq2zwg4h
question: What is the correct syntax to define a function in JavaScript?
wrongAnswers:
- id: dusl68wx2o
answer: '`function myFunction {}`'
- id: dlwjt59a5s
answer: '`def myFunction():`'
- id: 98p8ai27ko
answer: '`function = myFunction {}`'
- id: 7ciq8sood9
answer: '`function myFunction():`'
correctAnswers:
- id: wc3h7uc4zn
answer: '`function myFunction() {}`'
- id: 3ne0ju7yzq
question: >-
Which of the following methods is used to add a new element to the end of
an array in JavaScript?
wrongAnswers:
- id: fytb9yvbug
answer: shift()
- id: wnc31qc1ie
answer: unshift()
- id: 22mi7j64h5
answer: pop()
- id: da96ukvtol
answer: splice()
- id: gc6elnmtzw
answer: concat()
correctAnswers:
- id: pymbxhyn12
answer: push()
+36
View File
@@ -0,0 +1,36 @@
{
"name": "@freecodecamp/seed-exams",
"version": "0.0.1",
"description": "Seed certification exams questions to database",
"license": "BSD-3-Clause",
"private": true,
"engines": {
"node": ">=24",
"pnpm": ">=10"
},
"scripts": {
"lint": "eslint --max-warnings 0"
},
"repository": {
"type": "git",
"url": "git+https://github.com/freeCodeCamp/freeCodeCamp.git"
},
"bugs": {
"url": "https://github.com/freeCodeCamp/freeCodeCamp/issues"
},
"homepage": "https://github.com/freeCodeCamp/freeCodeCamp#readme",
"author": "freeCodeCamp <team@freecodecamp.org>",
"main": "none",
"devDependencies": {
"@freecodecamp/eslint-config": "workspace:*",
"debug": "4.4.3",
"dotenv": "16.6.1",
"eslint": "^9.39.1",
"joi": "17.13.3",
"joi-objectid": "3.0.1",
"js-yaml": "4.1.0",
"mongodb": "6.21.0",
"nanoid": "4.0.2"
},
"type": "module"
}
@@ -0,0 +1,85 @@
{
"super-block-a": {
"blocks": {
"block-one": {
"meta": {},
"challenges": [
{
"title": "Challenge One",
"block": "Block One",
"superBlock": "super-block-a"
},
{
"title": "Challenge Two",
"block": "Block One",
"superBlock": "super-block-a"
},
{
"title": "Challenge Three",
"block": "Block One",
"superBlock": "super-block-a"
},
{
"title": "Challenge Four",
"block": "Block One",
"superBlock": "super-block-a"
}
]
},
"block-two": {
"meta": {},
"challenges": [
{
"title": "Challenge Five",
"block": "Block Two",
"superBlock": "super-block-a"
},
{
"title": "Challenge Six",
"block": "Block Two",
"superBlock": "super-block-a"
},
{
"title": "Challenge Seven",
"block": "Block Two",
"superBlock": "super-block-a"
},
{
"title": "Challenge Eight",
"block": "Block Two",
"superBlock": "super-block-a"
}
]
}
}
},
"super-block-b": {
"blocks": {
"block-one": {
"meta": {},
"challenges": [
{
"title": "Challenge Nine",
"block": "Block One",
"superBlock": "super-block-b"
},
{
"title": "Challenge Ten",
"block": "Block One",
"superBlock": "super-block-b"
},
{
"title": "Challenge Eleven",
"block": "Block One",
"superBlock": "super-block-b"
},
{
"title": "Challenge Twelve",
"block": "Block One",
"superBlock": "super-block-b"
}
]
}
}
}
}
+13
View File
@@ -0,0 +1,13 @@
import { configTypeChecked } from '@freecodecamp/eslint-config/base';
import globals from 'globals';
export default [
...configTypeChecked,
{
languageOptions: {
globals: {
...globals.node // TODO: migrate to ESM and remove globals
}
}
}
];
+31
View File
@@ -0,0 +1,31 @@
{
"name": "@freecodecamp/scripts-seed",
"version": "0.0.1",
"description": "The freeCodeCamp.org open-source codebase and curriculum",
"license": "BSD-3-Clause",
"private": true,
"engines": {
"node": ">=24",
"pnpm": ">=10"
},
"scripts": {
"lint": "eslint --max-warnings 0"
},
"repository": {
"type": "git",
"url": "git+https://github.com/freeCodeCamp/freeCodeCamp.git"
},
"bugs": {
"url": "https://github.com/freeCodeCamp/freeCodeCamp/issues"
},
"homepage": "https://github.com/freeCodeCamp/freeCodeCamp#readme",
"author": "freeCodeCamp <team@freecodecamp.org>",
"main": "none",
"devDependencies": {
"@freecodecamp/eslint-config": "workspace:*",
"debug": "4.4.3",
"dotenv": "16.6.1",
"eslint": "^9.39.1",
"mongodb": "6.21.0"
}
}
+146
View File
@@ -0,0 +1,146 @@
const { parseArgs } = require('node:util');
const path = require('path');
const debug = require('debug');
const { MongoClient } = require('mongodb');
require('dotenv').config({ path: path.resolve(__dirname, '../../../.env') });
const {
demoUser,
blankUser,
publicUser,
fullyCertifiedUser,
userIds,
almostFullyCertifiedUser,
unclaimedUser
} = require('./user-data');
const options = {
'set-true': { type: 'string', multiple: true },
'top-contributor': { type: 'boolean' },
'set-false': { type: 'string', multiple: true },
'seed-trophy-challenges': { type: 'boolean' },
'certified-user': { type: 'boolean' },
'almost-certified-user': { type: 'boolean' },
'unclaimed-user': { type: 'boolean' }
};
const { values: argValues } = parseArgs({ options });
const log = debug('fcc:tools:seedLocalAuthUser');
const { MONGOHQ_URL } = process.env;
function handleError(err, client) {
if (err) {
console.error('Oh noes!! Error seeding local auth user.');
console.error(err);
try {
client.close();
} catch {
// no-op
} finally {
process.exit(1);
}
}
}
const trophyChallenges = [
{
id: '647f85d407d29547b3bee1bb',
solution:
'https://learn.microsoft.com/api/gamestatus/achievements/learn.wwl.get-started-c-sharp-part-1.trophy?username=moT01&locale=en-us',
completedDate: 1695064765244
},
{
id: '647f87dc07d29547b3bee1bf',
solution:
'https://learn.microsoft.com/api/gamestatus/achievements/learn.wwl.get-started-c-sharp-part-2.trophy?username=moT01&locale=en-us',
completedDate: 1695064900926
},
{
id: '647f882207d29547b3bee1c0',
solution:
'https://learn.microsoft.com/api/gamestatus/achievements/learn.wwl.get-started-c-sharp-part-3.trophy?username=moT01&locale=en-us',
completedDate: 1695064949460
},
{
id: '647f867a07d29547b3bee1bc',
solution:
'https://learn.microsoft.com/api/gamestatus/achievements/learn.wwl.get-started-c-sharp-part-4.trophy?username=moT01&locale=en-us',
completedDate: 1695064986634
},
{
id: '647f877f07d29547b3bee1be',
solution:
'https://learn.microsoft.com/api/gamestatus/achievements/learn.wwl.get-started-c-sharp-part-5.trophy?username=moT01&locale=en-us',
completedDate: 1695065026465
},
{
id: '647f86ff07d29547b3bee1bd',
solution:
'https://learn.microsoft.com/api/gamestatus/achievements/learn.wwl.get-started-c-sharp-part-6.trophy?username=moT01&locale=en-us',
completedDate: 1695065060157
}
];
[demoUser, blankUser, fullyCertifiedUser].forEach(user => {
if (argValues['top-contributor']) {
user.yearsTopContributor = ['2017', '2018', '2019'];
}
for (const key of argValues['set-false'] || []) {
user[key] = false;
}
for (const key of argValues['set-true'] || []) {
user[key] = true;
}
if (argValues['--seed-trophy-challenges']) {
user.completedChallenges = trophyChallenges;
}
});
const client = new MongoClient(MONGOHQ_URL);
const db = client.db('freecodecamp');
const user = db.collection('user');
const dropUserTokens = async function () {
await db.collection('UserToken').deleteMany({
userId: {
$in: userIds
}
});
};
const dropUsers = async function () {
await db.collection('user').deleteMany({
_id: {
$in: userIds
}
});
};
const run = async () => {
await client.db('admin').command({ ping: 1 });
log('Connected successfully to mongo');
await dropUserTokens();
await dropUsers();
if (argValues['certified-user']) {
await user.insertOne(fullyCertifiedUser);
} else if (argValues['almost-certified-user']) {
await user.insertOne(almostFullyCertifiedUser);
} else if (argValues['unclaimed-user']) {
await user.insertOne(unclaimedUser);
} else {
await user.insertOne(demoUser);
}
await user.insertOne(blankUser);
await user.insertOne(publicUser);
log('local auth user seed complete');
};
run()
.then(() => client.close())
.catch(err => handleError(err, client));
+73
View File
@@ -0,0 +1,73 @@
const path = require('path');
const debug = require('debug');
const { MongoClient, ObjectId } = require('mongodb');
require('dotenv').config({ path: path.resolve(__dirname, '../../../.env') });
const log = debug('fcc:tools:seedMsUsername');
const { MONGOHQ_URL } = process.env;
const args = process.argv.slice(2);
const allowedArgs = ['--delete-only'];
// Check for invalid arguments
args.forEach(arg => {
if (!allowedArgs.includes(arg))
throw new Error(
`Invalid argument ${arg}. Allowed arguments are ${allowedArgs.join(', ')}`
);
});
function handleError(err, client) {
if (err) {
console.error('Oh noes!! Error seeding MS username.');
console.error(err);
try {
client.close();
} catch {
// no-op
} finally {
process.exit(1);
}
}
}
const msAccountId = new ObjectId('65785b25d4c5bd0565c0184d');
const certifiedUserAccount = {
_id: msAccountId,
userId: new ObjectId('5fa2db00a25c1c1fa49ce067'),
ttl: 77760000000,
msUsername: 'certifieduser'
};
const client = new MongoClient(MONGOHQ_URL);
const run = async () => {
await client.db('admin').command({ ping: 1 });
log('Connected successfully to mongo');
const db = client.db('freecodecamp');
const msUsername = db.collection('MsUsername');
if (args.includes('--delete-only')) {
await msUsername.deleteOne({
_id: { $eq: msAccountId }
});
log('MS username deleted');
return;
}
// Rewrite if the object exists, create new if it doesn't
await msUsername.updateOne(
{ _id: msAccountId },
{ $set: certifiedUserAccount },
{ upsert: true }
);
log('MS username seeded');
};
run()
.then(() => client.close())
.catch(err => handleError(err, client));
+97
View File
@@ -0,0 +1,97 @@
const path = require('path');
const debug = require('debug');
const { MongoClient, ObjectId } = require('mongodb');
const { userIds } = require('./user-data');
require('dotenv').config({ path: path.resolve(__dirname, '../../../.env') });
const args = process.argv.slice(2);
const allowedArgs = ['delete-only'];
// Check for invalid arguments
args.forEach(arg => {
if (!allowedArgs.includes(arg))
throw new Error(
`Invalid argument ${arg}. Allowed arguments are ${allowedArgs.join(', ')}`
);
});
const log = debug('fcc:tools:seedSurveyInfo');
const { MONGOHQ_URL } = process.env;
function handleError(err, client) {
if (err) {
console.error('Oh noes!! Error seeding survey info.');
console.error(err);
try {
client.close();
} catch {
// no-op
} finally {
process.exit(1);
}
}
}
const surveyIds = [
new ObjectId('651c5a2a5f9b639b584028bc'),
new ObjectId('651c5a4c5f9b639b584028bd')
];
const defaultUserSurvey = {
_id: surveyIds[0],
title: 'Foundational C# with Microsoft Survey',
responses: [
{
question: 'Please describe your role:',
response: 'Beginner developer (less than 2 years experience)'
},
{
question:
'Prior to this course, how experienced were you with .NET and C#?',
response: 'Novice (no prior experience)'
}
],
userId: new ObjectId('5bd30e0f1caf6ac3ddddddb5')
};
const certifiedUserSurvey = {
_id: surveyIds[1],
title: 'Foundational C# with Microsoft Survey',
responses: [
{
question: 'Please describe your role:',
response: 'Experienced developer (more than 5 years experience)'
},
{
question:
'Prior to this course, how experienced were you with .NET and C#?',
response: 'Expert'
}
],
userId: new ObjectId('5fa2db00a25c1c1fa49ce067')
};
const client = new MongoClient(MONGOHQ_URL);
const run = async () => {
await client.db('admin').command({ ping: 1 });
log('Connected successfully to mongo');
const db = client.db('freecodecamp');
const survey = db.collection('Survey');
await survey.deleteMany({ userId: { $in: userIds } });
log('Survey info deleted');
if (!args.includes('delete-only')) {
await survey.insertOne(defaultUserSurvey);
await survey.insertOne(certifiedUserSurvey);
log('Survey info seeded');
}
};
run()
.then(() => client.close())
.catch(err => handleError(err, client));
File diff suppressed because it is too large Load Diff
+104
View File
@@ -0,0 +1,104 @@
import { exec } from 'child_process';
import { readFile, readdir, stat, writeFile } from 'fs/promises';
import { join, sep } from 'path';
import { promisify } from 'util';
const asyncExec = promisify(exec);
const loadDirectory = async (path: string): Promise<string[]> => {
const files: string[] = [];
const status = await stat(path);
if (status.isDirectory()) {
const filesInDir = await readdir(path);
for (const file of filesInDir) {
files.push(...(await loadDirectory(join(path, file))));
}
} else {
files.push(path);
}
return files.filter(f => !f.includes('DS_Store'));
};
const syncChallenges = async () => {
const block = process.env.FCC_BLOCK;
const ignore = ['.markdownlint.yaml', '_meta', 'english'];
const basePath = join(process.cwd(), 'curriculum', 'challenges');
const allLangs = await readdir(basePath);
const filtered = allLangs.filter(lang => !ignore.includes(lang));
// these will be paths
const english = await loadDirectory(join(basePath, 'english'));
for (const path of english) {
await Promise.all(
filtered.map(async lang => {
if (block && !path.includes(block)) {
return;
}
const targetPath = path.replace('english', lang);
// we swallow the error here to detect if the file doesn't exist
const status = await stat(targetPath).catch(() => null);
if (!status) {
const targetDir = targetPath.split(sep);
targetDir.pop();
console.log(`Syncing ${path.split('/english/')[1]}`);
await asyncExec(
`mkdir -p ${targetDir.join(sep)} && cp ${path} ${targetPath}`
);
}
const englishContent = await readFile(path, 'utf-8');
if (path.endsWith('LICENSE.txt')) {
await writeFile(targetPath, englishContent, 'utf-8');
return;
}
const langContent = await readFile(targetPath, 'utf-8');
const engLines = englishContent.split('\n');
const engId = engLines.find(l => l.startsWith('id'));
const engSlug = engLines.find(l => l.startsWith('dashedName'));
const langLines = langContent.split('\n');
const langId = langLines.find(l => l.startsWith('id'));
const langSlug = langLines.find(
l => l.startsWith('dashedName') || l.startsWith('certification:')
);
if (!langSlug) {
throw new Error(
`Missing dashedName for ${targetPath}. Please add it so that it matches the English version.`
);
}
if (!langId) {
throw new Error(
`Missing id for ${targetPath}. Please add it so that it matches the English version.`
);
}
if (engId && engId !== langId) {
langLines.splice(langLines.indexOf(langId), 1, engId);
console.log(`Updating ID for ${targetPath}`);
await writeFile(targetPath, langLines.join('\n'), 'utf-8');
}
if (engSlug && engSlug !== langSlug) {
langLines.splice(langLines.indexOf(langSlug), 1, engSlug);
console.log(`Updating dashed name for ${targetPath}`);
await writeFile(targetPath, langLines.join('\n'), 'utf-8');
}
})
);
}
for (const lang of filtered) {
const langPath = join(process.cwd(), 'curriculum', 'challenges', lang);
const langFiles = await loadDirectory(langPath);
for (const path of langFiles) {
if (block && !path.includes(block)) {
continue;
}
const engPath = path.replace(lang, 'english');
// we don't want an error, only need to know that the file exists
const existsEnglish = await stat(engPath).catch(() => null);
if (!existsEnglish) {
console.log(`Unlinking ${path.split(`/${lang}/`)[1]}`);
await asyncExec(`rm ${path}`);
}
}
}
};
void (async () => {
await syncChallenges();
})();
+14
View File
@@ -0,0 +1,14 @@
#!/bin/bash
echo "Enter the ids of the challenges you wish to test (separated by commas) and hit enter."
read -r input
IFS=',' challengeIDS=($input)
for challengeID in "${challengeIDS[@]}";
do
challengeID="$(tr -d ' ' <<< "$challengeID")"
FCC_CHALLENGE_ID=$challengeID pnpm turbo -F=@freecodecamp/curriculum test
done