mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-28 18:26:54 +00:00
refactor: move client specific scripts inside client (#51123)
This commit is contained in:
committed by
GitHub
parent
91977bbf2c
commit
8d0c3557dd
@@ -1,57 +0,0 @@
|
||||
import { writeFileSync } from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import fetch from 'node-fetch';
|
||||
import yaml from 'js-yaml';
|
||||
import { config } from 'dotenv';
|
||||
|
||||
import { trendingSchemaValidator } from './schema/trending-schema';
|
||||
|
||||
config({ path: path.resolve(__dirname, '../../../.env') });
|
||||
|
||||
const createCdnUrl = (lang: string) =>
|
||||
`https://cdn.freecodecamp.org/universal/trending/${lang}.yaml`;
|
||||
|
||||
const download = async (clientLocale: string) => {
|
||||
const url = createCdnUrl(clientLocale);
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) {
|
||||
throw new Error(
|
||||
`
|
||||
----------------------------------------------------
|
||||
Error: The CDN is missing the trending YAML file.
|
||||
----------------------------------------------------
|
||||
Unable to fetch the ${clientLocale} footer: ${res.statusText}
|
||||
`
|
||||
);
|
||||
}
|
||||
|
||||
const data = await res.text();
|
||||
const trendingJSON = JSON.stringify(yaml.load(data));
|
||||
const trendingLocation = `./client/i18n/locales/${clientLocale}/trending.json`;
|
||||
writeFileSync(trendingLocation, trendingJSON);
|
||||
|
||||
const trendingObject = JSON.parse(trendingJSON) as Record<string, string>;
|
||||
const validationError =
|
||||
(trendingSchemaValidator(trendingObject).error as Error) || null;
|
||||
|
||||
if (validationError) {
|
||||
throw new Error(
|
||||
`
|
||||
----------------------------------------------------
|
||||
Error: The trending JSON is invalid.
|
||||
----------------------------------------------------
|
||||
Unable to validate the ${clientLocale} trending JSON schema: ${validationError.message}
|
||||
`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const locale = process.env.CLIENT_LOCALE;
|
||||
|
||||
if (!locale) throw Error('CLIENT_LOCALE must be set to a valid locale');
|
||||
|
||||
void download(locale);
|
||||
// TODO: remove the need to fallback to english once we're confident it's
|
||||
// unnecessary (client/i18n/config.js will need all references to 'en' removing)
|
||||
if (locale !== 'english') void download('english');
|
||||
@@ -1,146 +0,0 @@
|
||||
import { spawn } from 'child_process';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
import { availableLangs, Languages } from '../../../config/i18n';
|
||||
import env from '../../../config/read-env';
|
||||
|
||||
const globalConfigPath = path.resolve(__dirname, '../../../config');
|
||||
|
||||
const { FREECODECAMP_NODE_ENV } = process.env;
|
||||
|
||||
function checkClientLocale() {
|
||||
if (!process.env.CLIENT_LOCALE) throw Error('CLIENT_LOCALE is not set');
|
||||
if (!availableLangs.client.includes(process.env.CLIENT_LOCALE as Languages)) {
|
||||
throw Error(`
|
||||
|
||||
CLIENT_LOCALE, ${process.env.CLIENT_LOCALE}, is not an available language in config/i18n.ts
|
||||
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
function checkCurriculumLocale() {
|
||||
if (!process.env.CURRICULUM_LOCALE)
|
||||
throw Error('CURRICULUM_LOCALE is not set');
|
||||
if (
|
||||
!availableLangs.curriculum.includes(
|
||||
process.env.CURRICULUM_LOCALE as Languages
|
||||
)
|
||||
) {
|
||||
throw Error(`
|
||||
|
||||
CURRICULUM_LOCALE, ${process.env.CURRICULUM_LOCALE}, is not an available language in config/i18n.ts
|
||||
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
if (FREECODECAMP_NODE_ENV !== 'development') {
|
||||
const locationKeys = [
|
||||
'homeLocation',
|
||||
'apiLocation',
|
||||
'forumLocation',
|
||||
'newsLocation',
|
||||
'radioLocation'
|
||||
];
|
||||
const deploymentKeys = [
|
||||
'clientLocale',
|
||||
'curriculumLocale',
|
||||
'showLocaleDropdownMenu',
|
||||
'deploymentEnv',
|
||||
'environment',
|
||||
'showUpcomingChanges',
|
||||
'showNewCurriculum'
|
||||
];
|
||||
const searchKeys = ['algoliaAppId', 'algoliaAPIKey'];
|
||||
const donationKeys = ['stripePublicKey', 'paypalClientId', 'patreonClientId'];
|
||||
const loggingKeys = ['sentryClientDSN'];
|
||||
const abTestingKeys = ['growthbookUri'];
|
||||
|
||||
const expectedVariables = locationKeys.concat(
|
||||
deploymentKeys,
|
||||
searchKeys,
|
||||
donationKeys,
|
||||
loggingKeys,
|
||||
abTestingKeys
|
||||
);
|
||||
const actualVariables = Object.keys(env as Record<string, unknown>);
|
||||
if (expectedVariables.length !== actualVariables.length) {
|
||||
const extraVariables = actualVariables
|
||||
.filter(x => !expectedVariables.includes(x))
|
||||
.toString();
|
||||
const missingVariables = expectedVariables
|
||||
.filter(x => !actualVariables.includes(x))
|
||||
.toString();
|
||||
|
||||
throw Error(
|
||||
`
|
||||
|
||||
Env. variable validation failed. Make sure only expected variables are used and configured.
|
||||
|
||||
` +
|
||||
(extraVariables ? `Extra variables: ${extraVariables}\n` : '') +
|
||||
(missingVariables ? `Missing variables: ${missingVariables}` : '')
|
||||
);
|
||||
}
|
||||
|
||||
for (const key of expectedVariables) {
|
||||
// Since we may need to disable the sentry DSN (if we're getting too many
|
||||
// errors), this is the one key we don't check is set.
|
||||
if (key === 'sentryClientDSN') continue;
|
||||
const envVal = env[key as keyof typeof env];
|
||||
if (typeof envVal === 'undefined' || envVal === null) {
|
||||
throw Error(`
|
||||
|
||||
Env. variable ${key} is missing, build cannot continue
|
||||
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
if (env['environment'] !== 'production')
|
||||
throw Error(`
|
||||
|
||||
Production environment should be 'production'
|
||||
|
||||
`);
|
||||
|
||||
if (env['showUpcomingChanges'])
|
||||
throw Error(`
|
||||
|
||||
SHOW_UPCOMING_CHANGES should never be 'true' in production
|
||||
|
||||
`);
|
||||
|
||||
checkClientLocale();
|
||||
checkCurriculumLocale();
|
||||
} else {
|
||||
checkClientLocale();
|
||||
checkCurriculumLocale();
|
||||
if (fs.existsSync(`${globalConfigPath}/env.json`)) {
|
||||
/* eslint-disable @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-assignment */
|
||||
const {
|
||||
showNewCurriculum,
|
||||
showUpcomingChanges
|
||||
} = require(`${globalConfigPath}/env.json`);
|
||||
/* eslint-enable @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-assignment */
|
||||
if (
|
||||
env['showUpcomingChanges'] !== showUpcomingChanges ||
|
||||
env['showNewCurriculum'] !== showNewCurriculum
|
||||
) {
|
||||
/* eslint-enable @typescript-eslint/no-unsafe-member-access */
|
||||
console.log('Feature flags have been changed, cleaning client cache.');
|
||||
const child = spawn('pnpm', ['run', 'clean:client']);
|
||||
child.stdout.setEncoding('utf8');
|
||||
child.stdout.on('data', function (data) {
|
||||
console.log(data);
|
||||
});
|
||||
child.on('error', err => {
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fs.writeFileSync(`${globalConfigPath}/env.json`, JSON.stringify(env));
|
||||
@@ -19,14 +19,8 @@
|
||||
"author": "freeCodeCamp <team@freecodecamp.org>",
|
||||
"main": "none",
|
||||
"devDependencies": {
|
||||
"@types/js-yaml": "4.0.5",
|
||||
"@types/node-fetch": "2.6.4",
|
||||
"chai": "4.3.7",
|
||||
"debug": "4.3.4",
|
||||
"dotenv": "16.3.1",
|
||||
"joi": "17.9.2",
|
||||
"js-yaml": "4.1.0",
|
||||
"node-fetch": "2.6.12",
|
||||
"readdirp": "3.6.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
import Joi from 'joi';
|
||||
|
||||
const schema = Joi.object().keys({
|
||||
article0title: Joi.string().required(),
|
||||
article0link: Joi.string().uri({ scheme: 'https' }).required(),
|
||||
article1title: Joi.string().required(),
|
||||
article1link: Joi.string().uri({ scheme: 'https' }).required(),
|
||||
article2title: Joi.string().required(),
|
||||
article2link: Joi.string().uri({ scheme: 'https' }).required(),
|
||||
article3title: Joi.string().required(),
|
||||
article3link: Joi.string().uri({ scheme: 'https' }).required(),
|
||||
article4title: Joi.string().required(),
|
||||
article4link: Joi.string().uri({ scheme: 'https' }).required(),
|
||||
article5title: Joi.string().required(),
|
||||
article5link: Joi.string().uri({ scheme: 'https' }).required(),
|
||||
article6title: Joi.string().required(),
|
||||
article6link: Joi.string().uri({ scheme: 'https' }).required(),
|
||||
article7title: Joi.string().required(),
|
||||
article7link: Joi.string().uri({ scheme: 'https' }).required(),
|
||||
article8title: Joi.string().required(),
|
||||
article8link: Joi.string().uri({ scheme: 'https' }).required(),
|
||||
article9title: Joi.string().required(),
|
||||
article9link: Joi.string().uri({ scheme: 'https' }).required(),
|
||||
article10title: Joi.string().required(),
|
||||
article10link: Joi.string().uri({ scheme: 'https' }).required(),
|
||||
article11title: Joi.string().required(),
|
||||
article11link: Joi.string().uri({ scheme: 'https' }).required(),
|
||||
article12title: Joi.string().required(),
|
||||
article12link: Joi.string().uri({ scheme: 'https' }).required(),
|
||||
article13title: Joi.string().required(),
|
||||
article13link: Joi.string().uri({ scheme: 'https' }).required(),
|
||||
article14title: Joi.string().required(),
|
||||
article14link: Joi.string().uri({ scheme: 'https' }).required(),
|
||||
article15title: Joi.string().required(),
|
||||
article15link: Joi.string().uri({ scheme: 'https' }).required(),
|
||||
article16title: Joi.string().required(),
|
||||
article16link: Joi.string().uri({ scheme: 'https' }).required(),
|
||||
article17title: Joi.string().required(),
|
||||
article17link: Joi.string().uri({ scheme: 'https' }).required(),
|
||||
article18title: Joi.string().required(),
|
||||
article18link: Joi.string().uri({ scheme: 'https' }).required(),
|
||||
article19title: Joi.string().required(),
|
||||
article19link: Joi.string().uri({ scheme: 'https' }).required(),
|
||||
article20title: Joi.string().required(),
|
||||
article20link: Joi.string().uri({ scheme: 'https' }).required(),
|
||||
article21title: Joi.string().required(),
|
||||
article21link: Joi.string().uri({ scheme: 'https' }).required(),
|
||||
article22title: Joi.string().required(),
|
||||
article22link: Joi.string().uri({ scheme: 'https' }).required(),
|
||||
article23title: Joi.string().required(),
|
||||
article23link: Joi.string().uri({ scheme: 'https' }).required(),
|
||||
article24title: Joi.string().required(),
|
||||
article24link: Joi.string().uri({ scheme: 'https' }).required(),
|
||||
article25title: Joi.string().required(),
|
||||
article25link: Joi.string().uri({ scheme: 'https' }).required(),
|
||||
article26title: Joi.string().required(),
|
||||
article26link: Joi.string().uri({ scheme: 'https' }).required(),
|
||||
article27title: Joi.string().required(),
|
||||
article27link: Joi.string().uri({ scheme: 'https' }).required(),
|
||||
article28title: Joi.string().required(),
|
||||
article28link: Joi.string().uri({ scheme: 'https' }).required(),
|
||||
article29title: Joi.string().required(),
|
||||
article29link: Joi.string().uri({ scheme: 'https' }).required()
|
||||
});
|
||||
|
||||
export const trendingSchemaValidator = (
|
||||
trendingObj: Record<string, string>
|
||||
): Joi.ValidationResult => {
|
||||
return schema.validate(trendingObj);
|
||||
};
|
||||
Reference in New Issue
Block a user