refactor: one eslint task per workspace (#63835)

This commit is contained in:
Oliver Eyton-Williams
2025-11-21 14:51:46 +01:00
committed by GitHub
parent c9e83c5c6a
commit 1e0805fd72
74 changed files with 1813 additions and 2307 deletions

View File

@@ -68,9 +68,7 @@ jobs:
- name: Lint Source Files - name: Lint Source Files
run: | run: |
echo pnpm version $(pnpm -v) echo pnpm version $(pnpm -v)
pnpm compile:ts pnpm turbo lint
pnpm run build:curriculum
pnpm run lint
# DONT REMOVE THIS JOB. # DONT REMOVE THIS JOB.
# TODO: Refactor and use re-usable workflow and shared artifacts # TODO: Refactor and use re-usable workflow and shared artifacts

View File

@@ -1,28 +0,0 @@
const { ESLint } = require('eslint');
const cli = new ESLint();
// if a lot of files are changed, it's faster to run prettier/eslint on the
// whole project than to run them on each file separately
module.exports = {
'*.(js|ts|tsx)': async files => {
const ignoredIds = await Promise.all(
files.map(file => cli.isPathIgnored(file))
);
const lintableFiles = files.filter((_, i) => !ignoredIds[i]);
return [
'eslint --max-warnings=0 --cache --fix ' + lintableFiles.join(' '),
...files.map(filename => `prettier --write '${filename}'`)
];
},
'*.!(js|ts|tsx|css)': files =>
files.map(filename => `prettier --write --ignore-unknown '${filename}'`),
'./curriculum/challenges/**/*.md': files =>
files.map(filename => `node ./tools/scripts/lint/index.js '${filename}'`),
'*.css': files => [
...files.map(filename => `stylelint --fix '${filename}'`),
...files.map(filename => `prettier --write '${filename}'`)
]
};

7
.lintstagedrc.mjs Normal file
View File

@@ -0,0 +1,7 @@
import { createLintStagedConfig } from '@freecodecamp/eslint-config/lintstaged';
export default {
...createLintStagedConfig(import.meta.dirname),
'./curriculum/challenges/**/*.md': files =>
files.map(filename => `node ./tools/scripts/lint/index.js '${filename}'`)
};

4
api/.lintstagedrc.mjs Normal file
View File

@@ -0,0 +1,4 @@
/* eslint-disable filenames-simple/naming-convention */
import { createLintStagedConfig } from '@freecodecamp/eslint-config/lintstaged';
export default createLintStagedConfig(import.meta.dirname);

View File

@@ -1,3 +1,4 @@
/* eslint-disable jsdoc/require-jsdoc */
import { Static } from '@fastify/type-provider-typebox'; import { Static } from '@fastify/type-provider-typebox';
import { import {
ExamEnvironmentConfig, ExamEnvironmentConfig,

36
api/eslint.config.js Normal file
View File

@@ -0,0 +1,36 @@
import { configTypeChecked, tsFiles } from '@freecodecamp/eslint-config/base';
import jsdoc from 'eslint-plugin-jsdoc';
/**
* A shared ESLint configuration for the repository.
*
* @type {import("eslint").Linter.Config[]}
* */
export default [
...configTypeChecked,
{
...jsdoc.configs['flat/recommended-typescript-error'],
rules: {
'jsdoc/require-jsdoc': [
'error',
{
require: {
ArrowFunctionExpression: true,
ClassDeclaration: true,
ClassExpression: true,
FunctionDeclaration: true,
FunctionExpression: true,
MethodDefinition: true
},
publicOnly: true
}
],
'jsdoc/require-description-complete-sentence': 'error',
'jsdoc/tag-lines': 'off'
},
files: tsFiles
}
];

View File

@@ -40,6 +40,7 @@
}, },
"description": "The freeCodeCamp.org open-source codebase and curriculum", "description": "The freeCodeCamp.org open-source codebase and curriculum",
"devDependencies": { "devDependencies": {
"@freecodecamp/eslint-config": "workspace:*",
"@total-typescript/ts-reset": "0.5.1", "@total-typescript/ts-reset": "0.5.1",
"@types/jsonwebtoken": "9.0.5", "@types/jsonwebtoken": "9.0.5",
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
@@ -48,6 +49,8 @@
"@types/validator": "13.11.2", "@types/validator": "13.11.2",
"@vitest/ui": "^3.2.4", "@vitest/ui": "^3.2.4",
"dotenv-cli": "7.3.0", "dotenv-cli": "7.3.0",
"eslint": "^9.39.1",
"eslint-plugin-jsdoc": "48.2.1",
"jsdom": "^26.1.0", "jsdom": "^26.1.0",
"msw": "^2.7.0", "msw": "^2.7.0",
"prisma": "6.16.2", "prisma": "6.16.2",
@@ -74,6 +77,7 @@
"clean": "rm -rf dist", "clean": "rm -rf dist",
"develop": "tsx watch --clear-screen=false src/server.ts", "develop": "tsx watch --clear-screen=false src/server.ts",
"start": "FREECODECAMP_NODE_ENV=production node dist/server.js", "start": "FREECODECAMP_NODE_ENV=production node dist/server.js",
"lint": "eslint --max-warnings 0",
"test": "vitest run", "test": "vitest run",
"test:watch": "vitest", "test:watch": "vitest",
"test:ui": "vitest --ui", "test:ui": "vitest --ui",

View File

@@ -1,4 +1,3 @@
/* eslint-disable jsdoc/require-returns, jsdoc/require-param */
import { type FastifyPluginCallbackTypebox } from '@fastify/type-provider-typebox'; import { type FastifyPluginCallbackTypebox } from '@fastify/type-provider-typebox';
import { PrismaClientValidationError } from '@prisma/client/runtime/library.js'; import { PrismaClientValidationError } from '@prisma/client/runtime/library.js';
import { type FastifyInstance, type FastifyReply } from 'fastify'; import { type FastifyInstance, type FastifyReply } from 'fastify';

View File

@@ -1,6 +1,5 @@
// TODO: enable this, since strings don't make good errors. // TODO: enable this, since strings don't make good errors.
/* eslint-disable @typescript-eslint/only-throw-error */ /* eslint-disable @typescript-eslint/only-throw-error */
/* eslint-disable jsdoc/require-returns, jsdoc/require-param */
import { import {
ExamEnvironmentAnswer, ExamEnvironmentAnswer,
ExamEnvironmentConfig, ExamEnvironmentConfig,

View File

@@ -577,7 +577,6 @@ export const userRoutes: FastifyPluginCallbackTypebox = (
done(); done();
}; };
// eslint-disable-next-line jsdoc/require-param, jsdoc/require-returns
/** /**
* Generate a new authorization token for the given user, and invalidates any existing tokens. * Generate a new authorization token for the given user, and invalidates any existing tokens.
* *

View File

@@ -1,4 +1,5 @@
{ {
"include": ["**/*.ts"],
"compilerOptions": { "compilerOptions": {
"target": "es2022", "target": "es2022",
"module": "nodenext", "module": "nodenext",

View File

@@ -1,3 +1,4 @@
/* eslint-disable jsdoc/require-jsdoc */
import { beforeAll, afterAll, expect, vi } from 'vitest'; import { beforeAll, afterAll, expect, vi } from 'vitest';
import request from 'supertest'; import request from 'supertest';
@@ -46,13 +47,13 @@ export const getCookies = (setCookies: string[]): string => {
* A wrapper around supertest that handles common setup for requests. Namely * A wrapper around supertest that handles common setup for requests. Namely
* setting the Origin header, cookies and CSRF token. * setting the Origin header, cookies and CSRF token.
* *
* @param resource - The URL of the resource to be requested * @param resource - The URL of the resource to be requested.
* @param config - The configuration for the request * @param config - The configuration for the request.
* @param config.method - The HTTP method to be used * @param config.method - The HTTP method to be used.
* @param config.setCookies - The cookies to be set in the request * @param config.setCookies - The cookies to be set in the request.
* @param options - Additional options for the request * @param options - Additional options for the request.
* @param options.sendCSRFToken - Whether to send the CSRF token in the request (default: true) * @param options.sendCSRFToken - Whether to send the CSRF token in the request (default: true).
* @returns The request object * @returns The request object.
*/ */
export function superRequest( export function superRequest(
resource: string, resource: string,
@@ -83,9 +84,9 @@ export function superRequest(
* request function with the desired method and setCookies baked in. * request function with the desired method and setCookies baked in.
* *
* @param config * @param config
* @param config.method - HTTP method * @param config.method - HTTP method.
* @param config.setCookies - Cookies to be set in the request * @param config.setCookies - Cookies to be set in the request.
* @returns A superRequest function with the desired method and setCookies * @returns A superRequest function with the desired method and setCookies.
*/ */
export function createSuperRequest(config: { export function createSuperRequest(config: {
method: 'GET' | 'POST' | 'PUT' | 'DELETE'; method: 'GET' | 'POST' | 'PUT' | 'DELETE';

View File

@@ -1,3 +1,4 @@
/* eslint-disable filenames-simple/naming-convention */
require('dotenv').config({ path: '../.env' }); require('dotenv').config({ path: '../.env' });
const config = { const config = {
presets: [ presets: [

4
client/.lintstagedrc.mjs Normal file
View File

@@ -0,0 +1,4 @@
/* eslint-disable filenames-simple/naming-convention */
import { createLintStagedConfig } from '@freecodecamp/eslint-config/lintstaged';
export default createLintStagedConfig(import.meta.dirname);

78
client/eslint.config.mjs Normal file
View File

@@ -0,0 +1,78 @@
import {
config,
configTypeChecked,
configReact,
configTestingLibrary,
jsFiles,
tsFiles
} from '@freecodecamp/eslint-config/base';
import globals from 'globals';
import { defineConfig, globalIgnores } from 'eslint/config';
const baseLanguageOptions = {
globals: {
...globals.browser,
...globals.mocha,
...globals.node,
Promise: true,
window: true,
$: true,
ga: true,
jQuery: true,
router: true,
globalThis: true
}
};
const baseConfig = {
settings: {
react: {
version: '16.4.2'
},
'import/resolver': {
typescript: true,
node: true
}
},
rules: {
'import/no-cycle': [
2,
{
maxDepth: 2
}
],
'react/prop-types': 'off',
'react/jsx-no-useless-fragment': 'error'
}
};
// Order matters here; later configs can override settings in earlier ones.
export default defineConfig(
globalIgnores(['static', '.cache', 'public']),
{
files: jsFiles,
extends: [configReact, configTestingLibrary, config],
...baseConfig,
languageOptions: {
...baseLanguageOptions,
parserOptions: {
babelOptions: {
presets: ['@babel/preset-react'],
configFile: './.babelrc.js'
}
}
}
},
{
files: tsFiles,
extends: [configReact, configTestingLibrary, configTypeChecked],
...baseConfig,
languageOptions: {
...baseLanguageOptions
}
}
);

View File

@@ -30,7 +30,7 @@
"create:search-placeholder": "tsx ./tools/generate-search-placeholder", "create:search-placeholder": "tsx ./tools/generate-search-placeholder",
"predevelop": "pnpm run common-setup && pnpm run build:scripts --env development", "predevelop": "pnpm run common-setup && pnpm run build:scripts --env development",
"develop": "NODE_OPTIONS=\"--max-old-space-size=7168 --no-deprecation\" gatsby develop --inspect=9230", "develop": "NODE_OPTIONS=\"--max-old-space-size=7168 --no-deprecation\" gatsby develop --inspect=9230",
"lint": "tsx ./i18n/schema-validation.ts", "lint": "eslint --max-warnings 0",
"serve": "gatsby serve -p 8000", "serve": "gatsby serve -p 8000",
"serve-ci": "serve -l 8000 -c serve.json public", "serve-ci": "serve -l 8000 -c serve.json public",
"prestand-alone": "pnpm run prebuild", "prestand-alone": "pnpm run prebuild",
@@ -139,6 +139,7 @@
}, },
"devDependencies": { "devDependencies": {
"@babel/plugin-syntax-dynamic-import": "7.8.3", "@babel/plugin-syntax-dynamic-import": "7.8.3",
"@freecodecamp/eslint-config": "workspace:*",
"@testing-library/jest-dom": "^6.8.0", "@testing-library/jest-dom": "^6.8.0",
"@testing-library/react": "12.1.5", "@testing-library/react": "12.1.5",
"@testing-library/react-hooks": "^8.0.1", "@testing-library/react-hooks": "^8.0.1",
@@ -169,6 +170,8 @@
"babel-plugin-macros": "3.1.0", "babel-plugin-macros": "3.1.0",
"core-js": "2.6.12", "core-js": "2.6.12",
"dotenv": "16.4.5", "dotenv": "16.4.5",
"eslint": "^9.39.1",
"eslint-plugin-flowtype": "^8.0.3",
"gatsby-plugin-webpack-bundle-analyser-v2": "1.1.32", "gatsby-plugin-webpack-bundle-analyser-v2": "1.1.32",
"i18next-fs-backend": "2.6.0", "i18next-fs-backend": "2.6.0",
"joi": "17.12.2", "joi": "17.12.2",

View File

@@ -1,3 +1,4 @@
/* eslint-disable filenames-simple/naming-convention */
import { Router } from '@gatsbyjs/reach-router'; import { Router } from '@gatsbyjs/reach-router';
import { withPrefix } from 'gatsby'; import { withPrefix } from 'gatsby';
import React from 'react'; import React from 'react';

View File

@@ -0,0 +1,4 @@
/* eslint-disable filenames-simple/naming-convention */
import { createLintStagedConfig } from '@freecodecamp/eslint-config/lintstaged';
export default createLintStagedConfig(import.meta.dirname);

View File

@@ -0,0 +1,26 @@
import { config } from '@freecodecamp/eslint-config/base';
import { defineConfig } from 'eslint/config';
import globals from 'globals';
/**
* A shared ESLint configuration for the repository.
*
* @type {import("eslint").Linter.Config[]}
* */
export default defineConfig({
extends: [config],
languageOptions: {
globals: {
...globals.browser,
...globals.mocha,
...globals.node,
Promise: true,
window: true,
$: true,
ga: true,
jQuery: true,
router: true,
globalThis: true
}
}
});

View File

@@ -19,6 +19,7 @@
"author": "freeCodeCamp <team@freecodecamp.org>", "author": "freeCodeCamp <team@freecodecamp.org>",
"scripts": { "scripts": {
"build": "tsx ./src/generate/build-curriculum", "build": "tsx ./src/generate/build-curriculum",
"compile": "tsc --build --clean && tsc --build",
"create-empty-steps": "CALLING_DIR=$INIT_CWD tsx --tsconfig ../tsconfig.json ../tools/challenge-helper-scripts/create-empty-steps", "create-empty-steps": "CALLING_DIR=$INIT_CWD tsx --tsconfig ../tsconfig.json ../tools/challenge-helper-scripts/create-empty-steps",
"create-next-challenge": "CALLING_DIR=$INIT_CWD tsx --tsconfig ../tsconfig.json ../tools/challenge-helper-scripts/create-next-challenge", "create-next-challenge": "CALLING_DIR=$INIT_CWD tsx --tsconfig ../tsconfig.json ../tools/challenge-helper-scripts/create-next-challenge",
"create-this-challenge": "CALLING_DIR=$INIT_CWD tsx --tsconfig ../tsconfig.json ../tools/challenge-helper-scripts/create-this-challenge", "create-this-challenge": "CALLING_DIR=$INIT_CWD tsx --tsconfig ../tsconfig.json ../tools/challenge-helper-scripts/create-this-challenge",
@@ -31,7 +32,8 @@
"delete-step": "CALLING_DIR=$INIT_CWD tsx --tsconfig ../tsconfig.json ../tools/challenge-helper-scripts/delete-step", "delete-step": "CALLING_DIR=$INIT_CWD tsx --tsconfig ../tsconfig.json ../tools/challenge-helper-scripts/delete-step",
"delete-challenge": "CALLING_DIR=$INIT_CWD tsx --tsconfig ../tsconfig.json ../tools/challenge-helper-scripts/delete-challenge", "delete-challenge": "CALLING_DIR=$INIT_CWD tsx --tsconfig ../tsconfig.json ../tools/challenge-helper-scripts/delete-challenge",
"delete-task": "CALLING_DIR=$INIT_CWD tsx --tsconfig ../tsconfig.json ../tools/challenge-helper-scripts/delete-task", "delete-task": "CALLING_DIR=$INIT_CWD tsx --tsconfig ../tsconfig.json ../tools/challenge-helper-scripts/delete-task",
"lint": "tsx --tsconfig ../tsconfig.json src/lint-localized", "lint": "eslint --max-warnings 0",
"lint-challenges": "tsx --tsconfig ../tsconfig.json src/lint-localized",
"reorder-tasks": "CALLING_DIR=$INIT_CWD tsx --tsconfig ../tsconfig.json ../tools/challenge-helper-scripts/reorder-tasks", "reorder-tasks": "CALLING_DIR=$INIT_CWD tsx --tsconfig ../tsconfig.json ../tools/challenge-helper-scripts/reorder-tasks",
"update-challenge-order": "CALLING_DIR=$INIT_CWD tsx --tsconfig ../tsconfig.json ../tools/challenge-helper-scripts/update-challenge-order", "update-challenge-order": "CALLING_DIR=$INIT_CWD tsx --tsconfig ../tsconfig.json ../tools/challenge-helper-scripts/update-challenge-order",
"update-step-titles": "CALLING_DIR=$INIT_CWD tsx --tsconfig ../tsconfig.json ../tools/challenge-helper-scripts/update-step-titles", "update-step-titles": "CALLING_DIR=$INIT_CWD tsx --tsconfig ../tsconfig.json ../tools/challenge-helper-scripts/update-step-titles",
@@ -41,12 +43,14 @@
"devDependencies": { "devDependencies": {
"@babel/core": "7.23.7", "@babel/core": "7.23.7",
"@babel/register": "7.23.7", "@babel/register": "7.23.7",
"@freecodecamp/eslint-config": "workspace:*",
"@total-typescript/ts-reset": "^0.6.1", "@total-typescript/ts-reset": "^0.6.1",
"@types/debug": "^4.1.12", "@types/debug": "^4.1.12",
"@types/js-yaml": "4.0.5", "@types/js-yaml": "4.0.5",
"@types/polka": "^0.5.7", "@types/polka": "^0.5.7",
"@types/string-similarity": "^4.0.2", "@types/string-similarity": "^4.0.2",
"@vitest/ui": "^3.2.4", "@vitest/ui": "^3.2.4",
"eslint": "^9.39.1",
"glob": "8.1.0", "glob": "8.1.0",
"joi": "17.12.2", "joi": "17.12.2",
"joi-objectid": "3.0.1", "joi-objectid": "3.0.1",

4
e2e/.lintstagedrc.mjs Normal file
View File

@@ -0,0 +1,4 @@
/* eslint-disable filenames-simple/naming-convention */
import { createLintStagedConfig } from '@freecodecamp/eslint-config/lintstaged';
export default createLintStagedConfig(import.meta.dirname);

View File

@@ -16,9 +16,9 @@ test.beforeEach(async ({ page }) => {
test.afterAll( test.afterAll(
async () => async () =>
await Promise.all([ await Promise.all([
await execP('node ./tools/scripts/seed/seed-demo-user --certified-user'), execP('node ./tools/scripts/seed/seed-demo-user --certified-user'),
await execP('node ./tools/scripts/seed/seed-surveys'), execP('node ./tools/scripts/seed/seed-surveys'),
await execP('node ./tools/scripts/seed/seed-ms-username') execP('node ./tools/scripts/seed/seed-ms-username')
]) ])
); );

12
e2e/eslint.config.js Normal file
View File

@@ -0,0 +1,12 @@
import { configTypeChecked } from '@freecodecamp/eslint-config/base';
export default [
...configTypeChecked,
{
rules: {
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off'
}
}
];

14
e2e/package.json Normal file
View File

@@ -0,0 +1,14 @@
{
"name": "@freecodecamp/e2e",
"version": "1.0.0",
"scripts": {
"lint": "eslint --max-warnings 0"
},
"author": "freeCodeCamp <team@freecodecamp.org>",
"license": "BSD-3-Clause",
"packageManager": "pnpm@10.20.0",
"devDependencies": {
"@freecodecamp/eslint-config": "workspace:*",
"eslint": "^9.39.1"
}
}

View File

@@ -1,3 +1,13 @@
import { baseConfig } from '@freecodecamp/eslint-config/base'; import { configTypeChecked } from '@freecodecamp/eslint-config/base';
import { defineConfig } from 'eslint/config';
export default baseConfig; export default defineConfig({
// include all in root dir, but not subdirs:
files: ['*.js', '*.ts', '*.mjs'],
extends: [configTypeChecked],
rules: {
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off'
}
});

View File

@@ -45,15 +45,13 @@
"develop:client": "cd ./client && pnpm run develop", "develop:client": "cd ./client && pnpm run develop",
"develop:api": "cd ./api && pnpm run develop", "develop:api": "cd ./api && pnpm run develop",
"format": "run-s format:eslint format:prettier", "format": "run-s format:eslint format:prettier",
"format:eslint": "eslint . --fix", "format:eslint": "eslint --fix",
"format:prettier": "prettier --write .", "format:prettier": "prettier --write .",
"i18n-sync": "tsx ./tools/scripts/sync-i18n.ts", "i18n-sync": "tsx ./tools/scripts/sync-i18n.ts",
"knip": "npx -y knip@5 --include files", "knip": "npx -y knip@5 --include files",
"knip:all": "npx -y knip@5 ", "knip:all": "npx -y knip@5 ",
"prelint": "pnpm run -F=client predevelop", "lint": "pnpm npm-run-all lint:*",
"lint": "NODE_OPTIONS=\"--max-old-space-size=7168\" npm-run-all compile:ts -p lint:*", "lint:challenges": "cd ./curriculum && pnpm run lint-challenges",
"lint:challenges": "cd ./curriculum && pnpm run lint",
"lint:js": "eslint --cache --max-warnings 0 .",
"lint:ts": "tsc && tsc -p shared && tsc -p api && tsc -p client && tsc -p curriculum", "lint:ts": "tsc && tsc -p shared && tsc -p api && tsc -p client && tsc -p curriculum",
"lint:prettier": "prettier --list-different .", "lint:prettier": "prettier --list-different .",
"lint:css": "stylelint '**/*.css'", "lint:css": "stylelint '**/*.css'",
@@ -84,7 +82,8 @@
"playwright:watch": "playwright test --ui-port=0" "playwright:watch": "playwright test --ui-port=0"
}, },
"dependencies": { "dependencies": {
"dotenv": "16.4.5" "dotenv": "16.4.5",
"eslint": "^9.39.1"
}, },
"devDependencies": { "devDependencies": {
"@freecodecamp/eslint-config": "workspace:*", "@freecodecamp/eslint-config": "workspace:*",
@@ -94,16 +93,12 @@
"@types/lodash": "4.14.202", "@types/lodash": "4.14.202",
"@types/node": "20.12.8", "@types/node": "20.12.8",
"@types/testing-library__jest-dom": "^5.14.5", "@types/testing-library__jest-dom": "^5.14.5",
"@typescript-eslint/eslint-plugin": "8.24.0",
"@typescript-eslint/parser": "8.23.0",
"@vitest/eslint-plugin": "^1.3.12",
"debug": "4.3.4", "debug": "4.3.4",
"eslint": "^9.39.1",
"globals": "^15.14.0", "globals": "^15.14.0",
"husky": "9.0.11", "husky": "9.0.11",
"identity-obj-proxy": "^3.0.0", "identity-obj-proxy": "^3.0.0",
"js-yaml": "3.14.1", "js-yaml": "3.14.1",
"lint-staged": "^13.1.0", "lint-staged": "^16.2.7",
"lodash": "4.17.21", "lodash": "4.17.21",
"markdownlint": "0.33.0", "markdownlint": "0.33.0",
"npm-run-all2": "5.0.2", "npm-run-all2": "5.0.2",

View File

@@ -0,0 +1,35 @@
import { ESLint } from 'eslint';
export const createLintStagedConfig = cwd => {
// cwd has to be passed to ESLint or it defaults to process.cwd(), i.e. the root
// of the monorepo.
const cli = new ESLint({ cwd });
return {
'*.(mjs|js|ts|tsx)': async files => {
const ignoredIds = await Promise.all(
files.map(file => cli.isPathIgnored(file))
);
const lintableFiles = files.filter((_, i) => !ignoredIds[i]);
const prettierCommand = [
...files.map(filename => `prettier --write '${filename}'`)
];
// There should be at least one lintable file if we reach here, but if not,
// just run prettier.
return lintableFiles.length === 0
? prettierCommand
: [
'eslint --max-warnings=0 --cache --fix ' + lintableFiles.join(' '),
...prettierCommand
];
},
'*.!(mjs|js|ts|tsx|css)': files =>
files.map(filename => `prettier --write --ignore-unknown '${filename}'`),
'*.css': files => [
...files.map(filename => `stylelint --fix '${filename}'`),
...files.map(filename => `prettier --write '${filename}'`)
]
};
};

View File

@@ -1,147 +1,85 @@
import { fileURLToPath } from 'node:url'; import js from '@eslint/js';
import path from 'node:path'; import eslintConfigPrettier from 'eslint-config-prettier';
import tseslint from 'typescript-eslint';
import { fixupConfigRules, fixupPluginRules } from '@eslint/compat'; import vitest from '@vitest/eslint-plugin';
import { defineConfig, globalIgnores } from 'eslint/config';
import noOnlyTests from 'eslint-plugin-no-only-tests'; import noOnlyTests from 'eslint-plugin-no-only-tests';
import filenamesSimple from 'eslint-plugin-filenames-simple'; import filenamesSimple from 'eslint-plugin-filenames-simple';
import globals from 'globals'; import { fixupConfigRules, fixupPluginRules } from '@eslint/compat';
import babelParser from '@babel/eslint-parser';
import importPlugin from 'eslint-plugin-import';
import jsxAllyPlugin from 'eslint-plugin-jsx-a11y';
import prettierConfig from 'eslint-config-prettier';
import reactPlugin from 'eslint-plugin-react'; import reactPlugin from 'eslint-plugin-react';
import jsxAllyPlugin from 'eslint-plugin-jsx-a11y';
import importPlugin from 'eslint-plugin-import';
import testingLibraryPlugin from 'eslint-plugin-testing-library'; import testingLibraryPlugin from 'eslint-plugin-testing-library';
import vitest from '@vitest/eslint-plugin'; import babelParser from '@babel/eslint-parser'; // TODO: can we get away from using babel?
import tsParser from '@typescript-eslint/parser';
import tseslint from 'typescript-eslint';
import jsdoc from 'eslint-plugin-jsdoc';
import js from '@eslint/js';
import { FlatCompat } from '@eslint/eslintrc'; import { FlatCompat } from '@eslint/eslintrc';
const __filename = fileURLToPath(import.meta.url); const __dirname = import.meta.dirname;
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({ const compat = new FlatCompat({
baseDirectory: __dirname, baseDirectory: __dirname
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all
}); });
export const baseConfig = tseslint.config( export const jsFiles = ['**/*.js', '**/*.jsx', '**/*.mjs', '**/*.cjs'];
{ export const tsFiles = ['**/*.ts', '**/*.tsx'];
ignores: [ const testFiles = [
'client/static/**/*', '**/*.test.js',
'client/.cache/**/*', '**/*.test.jsx',
'client/public/**/*', '**/*.test.mjs',
'shared/**/*.js', '**/*.test.cjs',
'shared/**/*.d.ts', '**/*.test.ts',
'docs/**/*.md', '**/*.test.tsx'
'**/playwright*.config.ts', ];
'playwright/**/*',
'shared-dist/**/*', const base = defineConfig(
'**/dist/**/*' globalIgnores(['dist', '.turbo']),
]
},
js.configs.recommended, js.configs.recommended,
reactPlugin.configs['flat'].recommended, eslintConfigPrettier,
jsxAllyPlugin.flatConfigs.recommended, {
...fixupConfigRules( ...vitest.configs.recommended,
compat.extends( files: testFiles
'plugin:react-hooks/recommended' // Note: at time of testing, upgrading to v5 creates false positives },
) {
), extends: [importPlugin.flatConfigs.recommended],
importPlugin.flatConfigs.recommended, settings: { 'import/resolver': { node: true, typescript: true } },
// TODO: consider adding eslint-plugin-n (): rules: {
// ...nodePlugin.configs["flat/mixed-esm-and-cjs"], // TODO: fix the errors, allow the rules.
prettierConfig, 'import/no-named-as-default': 'off',
'import/no-named-as-default-member': 'off'
}
},
{ {
plugins: { plugins: {
'no-only-tests': noOnlyTests, 'no-only-tests': noOnlyTests,
'filenames-simple': fixupPluginRules(filenamesSimple) 'filenames-simple': fixupPluginRules(filenamesSimple)
}, },
languageOptions: {
globals: {
...globals.browser,
...globals.mocha,
...globals.node,
Promise: true,
window: true,
$: true,
ga: true,
jQuery: true,
router: true,
globalThis: true
},
parser: babelParser,
parserOptions: {
babelOptions: {
presets: ['@babel/preset-react']
}
}
},
settings: {
react: {
version: '16.4.2'
},
'import/resolver': {
typescript: true,
node: true
}
},
// TODO: audit these rules and remove as many as possible, ideally we want
// to rely on recommended configs.
rules: { rules: {
'import/no-unresolved': [ 'filenames-simple/naming-convention': ['error'],
2, 'no-only-tests/no-only-tests': 'error'
{
commonjs: true
}
],
'import/named': 'error',
'import/no-named-as-default': 'off',
'import/no-named-as-default-member': 'off',
'import/order': 'error',
'import/no-cycle': [
2,
{
maxDepth: 2
}
],
'react/prop-types': 'off',
'react/jsx-no-useless-fragment': 'error',
'no-only-tests/no-only-tests': 'error',
'no-unused-expressions': 'error', // This is so the js rules are more in line with the ts rules
'filenames-simple/naming-convention': ['warn']
} }
}, },
{ {
files: ['**/*.ts?(x)'], files: jsFiles,
extends: [ rules: {
tseslint.configs.recommended, 'no-unused-expressions': 'error', // This is so the js rules are more in line with the ts rules
// TODO: turn on type-aware rules 'import/no-unresolved': [2, { commonjs: true }] // commonjs is necessary while we still use require()
// tseslint.configs.recommendedTypeChecked, },
importPlugin.flatConfigs['typescript'] languageOptions: {
], parser: babelParser,
parserOptions: {
languageOptions: { requireConfigFile: false
parser: tsParser, }
}
parserOptions: { },
projectService: true {
} files: tsFiles,
}, extends: [importPlugin.flatConfigs['typescript']],
rules: {
'import/no-unresolved': 'off' // handled by TS
}
},
{
files: tsFiles,
rules: { rules: {
'import/no-unresolved': 'off',
'@typescript-eslint/naming-convention': 'off',
'@typescript-eslint/no-unused-vars': [ '@typescript-eslint/no-unused-vars': [
'warn', 'warn',
{ {
@@ -151,71 +89,50 @@ export const baseConfig = tseslint.config(
} }
] ]
} }
}, }
);
/**
* A shared ESLint configuration for the repository.
*
* @type {import("eslint").Linter.Config[]}
* */
export const config = defineConfig(...base, {
files: tsFiles,
extends: [tseslint.configs.recommended]
});
/**
* A shared ESLint configuration with type aware linting
*
* @type {import("eslint").Linter.Config[]}
* */
export const configTypeChecked = defineConfig(
...base,
{ {
files: [ files: tsFiles,
'client/**/*.ts?(x)',
'api/**/*.ts',
'shared/**/*.ts',
'tools/client-plugins/**/*.ts',
'tools/scripts/**/*.ts',
'tools/challenge-helper-scripts/**/*.ts',
'tools/challenge-auditor/**/*.ts',
'e2e/**/*.ts'
],
extends: [tseslint.configs.recommendedTypeChecked] extends: [tseslint.configs.recommendedTypeChecked]
}, },
{ {
files: ['client/**/*.test.[jt]s?(x)'], languageOptions: {
parserOptions: {
extends: [testingLibraryPlugin.configs['flat/react']] projectService: true
}, }
{
files: ['**/*.test.[jt]s?(x)'],
plugins: { vitest },
extends: [vitest.configs.recommended]
},
{
files: ['e2e/*.ts'],
rules: {
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off'
}
},
{
files: ['.lintstagedrc.js', '**/.babelrc.js', '**/404.tsx'],
rules: {
'filenames-simple/naming-convention': 'off'
}
},
{
extends: [
jsdoc.configs['flat/recommended-typescript-error'],
tseslint.configs.recommendedTypeChecked
],
files: ['**/api/src/**/*.ts'],
rules: {
'jsdoc/require-jsdoc': [
'error',
{
require: {
ArrowFunctionExpression: true,
ClassDeclaration: true,
ClassExpression: true,
FunctionDeclaration: true,
FunctionExpression: true,
MethodDefinition: true
},
publicOnly: true
}
],
'jsdoc/require-description-complete-sentence': 'error',
'jsdoc/tag-lines': 'off'
} }
} }
); );
export const configReact = [
reactPlugin.configs['flat'].recommended,
jsxAllyPlugin.flatConfigs.recommended,
...fixupConfigRules(
compat.extends(
'plugin:react-hooks/recommended' // Note: at time of testing, upgrading to v5 creates false positives
)
)
];
export const configTestingLibrary = defineConfig({
files: testFiles,
extends: [testingLibraryPlugin.configs['flat/react']]
});

View File

@@ -3,7 +3,8 @@
"version": "1.0.0", "version": "1.0.0",
"type": "module", "type": "module",
"exports": { "exports": {
"./base": "./base.js" "./base": "./base.js",
"./lintstaged": "./.lintstagedrc.js"
}, },
"author": "freeCodeCamp <team@freecodecamp.org>", "author": "freeCodeCamp <team@freecodecamp.org>",
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
@@ -14,6 +15,7 @@
"@eslint/compat": "^1.2.6", "@eslint/compat": "^1.2.6",
"@eslint/eslintrc": "^3.2.0", "@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.19.0", "@eslint/js": "^9.19.0",
"@vitest/eslint-plugin": "^1.4.3",
"eslint": "^9.39.1", "eslint": "^9.39.1",
"eslint-config-prettier": "10.0.1", "eslint-config-prettier": "10.0.1",
"eslint-import-resolver-typescript": "^3.5.5", "eslint-import-resolver-typescript": "^3.5.5",
@@ -26,6 +28,6 @@
"eslint-plugin-react-hooks": "4.6.0", "eslint-plugin-react-hooks": "4.6.0",
"eslint-plugin-testing-library": "7.1.1", "eslint-plugin-testing-library": "7.1.1",
"typescript": "5.7.3", "typescript": "5.7.3",
"typescript-eslint": "^8.23.0" "typescript-eslint": "^8.47.0"
} }
} }

3204
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,7 @@ packages:
- 'api' - 'api'
- 'client' - 'client'
- 'curriculum' - 'curriculum'
- 'e2e'
- 'shared' - 'shared'
- 'tools/challenge-editor/api' - 'tools/challenge-editor/api'
- 'tools/challenge-editor/client' - 'tools/challenge-editor/client'
@@ -19,3 +20,6 @@ packageExtensions:
'@testing-library/jest-dom': '@testing-library/jest-dom':
peerDependencies: peerDependencies:
vitest: '*' vitest: '*'
hoistPattern:
- '!*eslint*'

4
shared/.lintstagedrc.mjs Normal file
View File

@@ -0,0 +1,4 @@
/* eslint-disable filenames-simple/naming-convention */
import { createLintStagedConfig } from '@freecodecamp/eslint-config/lintstaged';
export default createLintStagedConfig(import.meta.dirname);

3
shared/eslint.config.js Normal file
View File

@@ -0,0 +1,3 @@
import { configTypeChecked } from '@freecodecamp/eslint-config/base';
export default configTypeChecked;

View File

@@ -10,7 +10,9 @@
"pnpm": ">=10" "pnpm": ">=10"
}, },
"scripts": { "scripts": {
"test": "vitest" "test": "vitest",
"compile": "tsc",
"lint": "eslint --max-warnings 0"
}, },
"type": "module", "type": "module",
"repository": { "repository": {
@@ -22,7 +24,9 @@
}, },
"homepage": "https://github.com/freeCodeCamp/freeCodeCamp#readme", "homepage": "https://github.com/freeCodeCamp/freeCodeCamp#readme",
"devDependencies": { "devDependencies": {
"vitest": "^3.2.4", "@freecodecamp/eslint-config": "workspace:*",
"@vitest/ui": "^3.2.4" "@vitest/ui": "^3.2.4",
"eslint": "^9.39.1",
"vitest": "^3.2.4"
} }
} }

View File

@@ -0,0 +1,4 @@
/* eslint-disable filenames-simple/naming-convention */
import { createLintStagedConfig } from '@freecodecamp/eslint-config/lintstaged';
export default createLintStagedConfig(import.meta.dirname);

View File

@@ -0,0 +1,3 @@
import { configTypeChecked } from '@freecodecamp/eslint-config/base';
export default configTypeChecked;

View File

@@ -1,11 +1,12 @@
{ {
"name": "@freecodecamp/challenge-editor", "name": "@freecodecamp/challenge-editor-api",
"version": "1.0.0", "version": "1.0.0",
"private": true, "private": true,
"description": "Editor to help with new challenge structure", "description": "Editor to help with new challenge structure",
"scripts": { "scripts": {
"start": "tsx server.ts", "start": "tsx server.ts",
"postinstall": "shx cp ./sample.env ./.env" "postinstall": "shx cp ./sample.env ./.env",
"lint": "eslint --max-warnings 0"
}, },
"author": "freeCodeCamp", "author": "freeCodeCamp",
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
@@ -16,9 +17,11 @@
}, },
"devDependencies": { "devDependencies": {
"@total-typescript/ts-reset": "^0.6.1", "@total-typescript/ts-reset": "^0.6.1",
"@freecodecamp/eslint-config": "workspace:*",
"@types/cors": "^2.8.13", "@types/cors": "^2.8.13",
"@types/express": "4.17.21", "@types/express": "4.17.21",
"dotenv": "16.4.5", "dotenv": "16.4.5",
"eslint": "^9.39.1",
"shx": "0.3.4", "shx": "0.3.4",
"typescript": "5.2.2" "typescript": "5.2.2"
} }

View File

@@ -0,0 +1,3 @@
{
"extends": "../../../tsconfig-base.json"
}

View File

@@ -58,15 +58,13 @@ export const getModules = async (
let modules: Module[] = []; let modules: Module[] = [];
modules = await Promise.all( modules = chapter.modules.map(module => {
chapter.modules!.map(module => { const modules = Object.entries(introData[superBlock]['modules']!);
const modules = Object.entries(introData[superBlock]['modules']!); const moduleTrueName = modules.filter(
const moduleTrueName = modules.filter( x => x[0] === module.dashedName
x => x[0] === module.dashedName )[0][1];
)[0][1]; return { name: moduleTrueName, path: 'modules/' + module.dashedName };
return { name: moduleTrueName, path: 'modules/' + module.dashedName }; });
})
);
return { return {
modules: modules, modules: modules,

View File

@@ -0,0 +1,4 @@
/* eslint-disable filenames-simple/naming-convention */
import { createLintStagedConfig } from '@freecodecamp/eslint-config/lintstaged';
export default createLintStagedConfig(import.meta.dirname);

View File

@@ -0,0 +1,10 @@
import {
configTypeChecked,
configReact
} from '@freecodecamp/eslint-config/base';
export default [
...configTypeChecked,
...configReact,
{ settings: { react: { version: '17.0.2' } } }
];

View File

@@ -14,14 +14,17 @@
"scripts": { "scripts": {
"start": "PORT=3300 vite", "start": "PORT=3300 vite",
"build": "tsc && vite build", "build": "tsc && vite build",
"lint": "eslint --max-warnings 0",
"test": "echo \"no tests here yet\"", "test": "echo \"no tests here yet\"",
"postinstall": "shx cp ./sample.env ./.env" "postinstall": "shx cp ./sample.env ./.env"
}, },
"devDependencies": { "devDependencies": {
"@freecodecamp/eslint-config": "workspace:*",
"@types/codemirror": "5.60.15", "@types/codemirror": "5.60.15",
"@types/react": "17.0.83", "@types/react": "17.0.83",
"@types/react-dom": "17.0.19", "@types/react-dom": "17.0.19",
"@uiw/react-codemirror": "3.2.10", "@uiw/react-codemirror": "3.2.10",
"eslint": "^9.39.1",
"shx": "0.3.4" "shx": "0.3.4"
} }
} }

View File

@@ -0,0 +1,4 @@
/* eslint-disable filenames-simple/naming-convention */
import { createLintStagedConfig } from '@freecodecamp/eslint-config/lintstaged';
export default createLintStagedConfig(import.meta.dirname);

View File

@@ -0,0 +1,3 @@
import { configTypeChecked } from '@freecodecamp/eslint-config/base';
export default configTypeChecked;

View File

@@ -23,13 +23,16 @@
"create-project": "tsx create-project", "create-project": "tsx create-project",
"create-language-block": "tsx create-language-block", "create-language-block": "tsx create-language-block",
"create-quiz": "tsx create-quiz", "create-quiz": "tsx create-quiz",
"lint": "eslint --max-warnings 0",
"test": "vitest" "test": "vitest"
}, },
"devDependencies": { "devDependencies": {
"@freecodecamp/eslint-config": "workspace:*",
"@types/glob": "^8.0.1", "@types/glob": "^8.0.1",
"@types/inquirer": "^8.2.5", "@types/inquirer": "^8.2.5",
"@vitest/ui": "^3.2.4", "@vitest/ui": "^3.2.4",
"bson-objectid": "2.0.4", "bson-objectid": "2.0.4",
"eslint": "^9.39.1",
"glob": "^8.1.0", "glob": "^8.1.0",
"gray-matter": "4.0.3", "gray-matter": "4.0.3",
"inquirer": "8.2.6", "inquirer": "8.2.6",

View File

@@ -0,0 +1,3 @@
{
"extends": "../../tsconfig-base.json"
}

View File

@@ -0,0 +1,4 @@
/* eslint-disable filenames-simple/naming-convention */
import { createLintStagedConfig } from '@freecodecamp/eslint-config/lintstaged';
export default createLintStagedConfig(import.meta.dirname);

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
}
}
}
];

View File

@@ -19,6 +19,7 @@
"author": "freeCodeCamp <team@freecodecamp.org>", "author": "freeCodeCamp <team@freecodecamp.org>",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"lint": "eslint --max-warnings 0",
"test": "vitest" "test": "vitest"
}, },
"dependencies": { "dependencies": {
@@ -54,6 +55,8 @@
"unist-util-visit-children": "1.1.4" "unist-util-visit-children": "1.1.4"
}, },
"devDependencies": { "devDependencies": {
"@freecodecamp/eslint-config": "workspace:*",
"eslint": "^9.39.1",
"unist-util-select": "3.0.4", "unist-util-select": "3.0.4",
"vitest": "^3.2.4" "vitest": "^3.2.4"
} }

View File

@@ -0,0 +1,3 @@
{
"extends": "../../tsconfig-base.json"
}

View File

@@ -0,0 +1,4 @@
/* eslint-disable filenames-simple/naming-convention */
import { createLintStagedConfig } from '@freecodecamp/eslint-config/lintstaged';
export default createLintStagedConfig(import.meta.dirname);

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
}
}
}
];

View File

@@ -20,6 +20,7 @@
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
"lint": "eslint --max-warnings 0",
"build": "NODE_OPTIONS=\"--max-old-space-size=7168\" webpack -c webpack.config.cjs" "build": "NODE_OPTIONS=\"--max-old-space-size=7168\" webpack -c webpack.config.cjs"
}, },
"type": "module", "type": "module",
@@ -29,10 +30,12 @@
"@babel/plugin-transform-runtime": "7.23.7", "@babel/plugin-transform-runtime": "7.23.7",
"@babel/preset-env": "7.23.7", "@babel/preset-env": "7.23.7",
"@babel/preset-typescript": "7.23.3", "@babel/preset-typescript": "7.23.3",
"@freecodecamp/eslint-config": "workspace:*",
"@types/copy-webpack-plugin": "^8.0.1", "@types/copy-webpack-plugin": "^8.0.1",
"@typescript/vfs": "^1.6.0", "@typescript/vfs": "^1.6.0",
"babel-loader": "8.3.0", "babel-loader": "8.3.0",
"copy-webpack-plugin": "9.1.0", "copy-webpack-plugin": "9.1.0",
"eslint": "^9.39.1",
"process": "0.11.10", "process": "0.11.10",
"pyodide": "^0.23.3", "pyodide": "^0.23.3",
"sass.js": "0.11.1", "sass.js": "0.11.1",

View File

@@ -0,0 +1,4 @@
/* eslint-disable filenames-simple/naming-convention */
import { createLintStagedConfig } from '@freecodecamp/eslint-config/lintstaged';
export default createLintStagedConfig(import.meta.dirname);

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
}
}
}
];

View File

@@ -8,6 +8,9 @@
"node": ">=16", "node": ">=16",
"pnpm": ">=10" "pnpm": ">=10"
}, },
"scripts": {
"lint": "eslint --max-warnings 0"
},
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/freeCodeCamp/freeCodeCamp.git" "url": "git+https://github.com/freeCodeCamp/freeCodeCamp.git"
@@ -17,5 +20,9 @@
}, },
"homepage": "https://github.com/freeCodeCamp/freeCodeCamp#readme", "homepage": "https://github.com/freeCodeCamp/freeCodeCamp#readme",
"author": "freeCodeCamp <team@freecodecamp.org>", "author": "freeCodeCamp <team@freecodecamp.org>",
"main": "gatsby-node.js" "main": "gatsby-node.js",
"devDependencies": {
"@freecodecamp/eslint-config": "workspace:*",
"eslint": "^9.39.1"
}
} }

View File

@@ -0,0 +1,4 @@
/* eslint-disable filenames-simple/naming-convention */
import { createLintStagedConfig } from '@freecodecamp/eslint-config/lintstaged';
export default createLintStagedConfig(import.meta.dirname);

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
}
}
}
];

View File

@@ -8,6 +8,9 @@
"node": ">=16", "node": ">=16",
"pnpm": ">=10" "pnpm": ">=10"
}, },
"scripts": {
"lint": "eslint --max-warnings 0"
},
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/freeCodeCamp/freeCodeCamp.git" "url": "git+https://github.com/freeCodeCamp/freeCodeCamp.git"
@@ -19,6 +22,8 @@
"author": "freeCodeCamp <team@freecodecamp.org>", "author": "freeCodeCamp <team@freecodecamp.org>",
"main": "gatsby-node.js", "main": "gatsby-node.js",
"devDependencies": { "devDependencies": {
"chokidar": "3.6.0" "@freecodecamp/eslint-config": "workspace:*",
"chokidar": "3.6.0",
"eslint": "^9.39.1"
} }
} }

View File

@@ -0,0 +1,4 @@
/* eslint-disable filenames-simple/naming-convention */
import { createLintStagedConfig } from '@freecodecamp/eslint-config/lintstaged';
export default createLintStagedConfig(import.meta.dirname);

View File

@@ -0,0 +1,3 @@
import { configTypeChecked } from '@freecodecamp/eslint-config/base';
export default configTypeChecked;

View File

@@ -126,12 +126,12 @@ export function combineChallenges({
return challengeData; return challengeData;
} }
export function handleError(err: unknown, client: MongoClient) { export async function handleError(err: unknown, client: MongoClient) {
if (err) { if (err) {
console.error('Oh noes!! Error seeding Daily Challenges.'); console.error('Oh noes!! Error seeding Daily Challenges.');
console.error(err); console.error(err);
try { try {
client.close(); await client.close();
} catch { } catch {
// no-op // no-op
} finally { } finally {

View File

@@ -3,13 +3,16 @@
"version": "1.0.0", "version": "1.0.0",
"main": "seed-daily-challenges.js", "main": "seed-daily-challenges.js",
"scripts": { "scripts": {
"seed-daily-challenges": "tsx seed-daily-challenges.ts" "seed-daily-challenges": "tsx seed-daily-challenges.ts",
"lint": "eslint --max-warnings 0"
}, },
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"description": "", "description": "",
"devDependencies": { "devDependencies": {
"@freecodecamp/eslint-config": "workspace:*",
"dotenv": "16.4.5", "dotenv": "16.4.5",
"eslint": "^9.39.1",
"mongodb": "6.10.0", "mongodb": "6.10.0",
"tsx": "4.19.1", "tsx": "4.19.1",
"typescript": "5.8.2" "typescript": "5.8.2"

View File

@@ -0,0 +1,3 @@
{
"extends": "../../tsconfig-base.json"
}

View File

@@ -0,0 +1,4 @@
/* eslint-disable filenames-simple/naming-convention */
import { createLintStagedConfig } from '@freecodecamp/eslint-config/lintstaged';
export default createLintStagedConfig(import.meta.dirname);

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
}
}
}
];

View File

@@ -15,10 +15,13 @@
"homepage": "https://github.com/freeCodeCamp/freeCodeCamp#readme", "homepage": "https://github.com/freeCodeCamp/freeCodeCamp#readme",
"author": "freeCodeCamp <team@freecodecamp.org>", "author": "freeCodeCamp <team@freecodecamp.org>",
"scripts": { "scripts": {
"lint": "eslint --max-warnings 0",
"test": "vitest" "test": "vitest"
}, },
"devDependencies": { "devDependencies": {
"@freecodecamp/eslint-config": "workspace:*",
"@vitest/ui": "^3.2.4", "@vitest/ui": "^3.2.4",
"eslint": "^9.39.1",
"vitest": "^3.2.4" "vitest": "^3.2.4"
} }
} }

View File

@@ -0,0 +1,3 @@
{
"extends": "../../../tsconfig-base.json"
}

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
}
}
}
];

View File

@@ -8,6 +8,9 @@
"node": ">=16", "node": ">=16",
"pnpm": ">=10" "pnpm": ">=10"
}, },
"scripts": {
"lint": "eslint --max-warnings 0"
},
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/freeCodeCamp/freeCodeCamp.git" "url": "git+https://github.com/freeCodeCamp/freeCodeCamp.git"
@@ -19,8 +22,10 @@
"author": "freeCodeCamp <team@freecodecamp.org>", "author": "freeCodeCamp <team@freecodecamp.org>",
"main": "none", "main": "none",
"devDependencies": { "devDependencies": {
"@freecodecamp/eslint-config": "workspace:*",
"debug": "4.3.4", "debug": "4.3.4",
"dotenv": "16.4.5", "dotenv": "16.4.5",
"eslint": "^9.39.1",
"joi": "17.12.2", "joi": "17.12.2",
"joi-objectid": "3.0.1", "joi-objectid": "3.0.1",
"js-yaml": "4.1.0", "js-yaml": "4.1.0",

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
}
}
}
];

View File

@@ -8,6 +8,9 @@
"node": ">=16", "node": ">=16",
"pnpm": ">=10" "pnpm": ">=10"
}, },
"scripts": {
"lint": "eslint --max-warnings 0"
},
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/freeCodeCamp/freeCodeCamp.git" "url": "git+https://github.com/freeCodeCamp/freeCodeCamp.git"
@@ -19,8 +22,10 @@
"author": "freeCodeCamp <team@freecodecamp.org>", "author": "freeCodeCamp <team@freecodecamp.org>",
"main": "none", "main": "none",
"devDependencies": { "devDependencies": {
"@freecodecamp/eslint-config": "workspace:*",
"debug": "4.3.4", "debug": "4.3.4",
"dotenv": "16.4.5", "dotenv": "16.4.5",
"eslint": "^9.39.1",
"mongodb": "6.10.0" "mongodb": "6.10.0"
} }
} }

View File

@@ -1,5 +1,6 @@
{ {
"include": [ "include": [
"*.ts",
"curriculum/*.test.ts", "curriculum/*.test.ts",
"e2e/**/*.ts", "e2e/**/*.ts",
"tools/challenge-auditor/index.ts", "tools/challenge-auditor/index.ts",

View File

@@ -1,9 +1,34 @@
{ {
"$schema": "https://turborepo.com/schema.json", "$schema": "https://turborepo.com/schema.json",
"tasks": { "tasks": {
"lint": { "lint": {},
"dependsOn": ["^lint"] "@freecodecamp/client#lint": {
"dependsOn": [
"@freecodecamp/curriculum#compile",
"create:env",
"build:scripts"
]
}, },
"//#lint": {} "@freecodecamp/gatsby-source-challenges#lint": {
"dependsOn": ["@freecodecamp/curriculum#compile"]
},
"@freecodecamp/scripts-lint#lint": {
"dependsOn": ["@freecodecamp/client#create:trending"],
"inputs": [
"$TURBO_DEFAULT$",
"$TURBO_ROOT$/client/i18n/locales/english/*"
]
},
"//#lint": {
"dependsOn": [
"@freecodecamp/shared#compile",
"@freecodecamp/curriculum#build"
]
},
"compile": {},
"create:trending": { "cache": false },
"create:env": { "dependsOn": ["@freecodecamp/curriculum#compile"] },
"build": { "dependsOn": ["compile"] },
"build:scripts": {}
} }
} }