diff --git a/curriculum/.lintstagedrc.mjs b/curriculum/.lintstagedrc.mjs index 1fc03a0d70f..a6ac4700245 100644 --- a/curriculum/.lintstagedrc.mjs +++ b/curriculum/.lintstagedrc.mjs @@ -1,8 +1,17 @@ /* eslint-disable filenames-simple/naming-convention */ +import path from 'node:path'; import { createLintStagedConfig } from '@freecodecamp/eslint-config/lintstaged'; +const linterConfigPath = path.resolve( + import.meta.dirname, + './challenges/.markdownlint.yaml' +); + export default { ...createLintStagedConfig(import.meta.dirname), './challenges/**/*.md': files => - files.map(filename => `node ../tools/scripts/lint/index.js '${filename}'`) + files.map( + filename => + `pnpm challenge-linter --config=${linterConfigPath} '${filename}'` + ) }; diff --git a/curriculum/package.json b/curriculum/package.json index 821676b7026..472c086f615 100644 --- a/curriculum/package.json +++ b/curriculum/package.json @@ -41,7 +41,7 @@ "delete-step": "tsx --tsconfig ../tools/challenge-helper-scripts/tsconfig.json ../tools/challenge-helper-scripts/delete-step", "delete-challenge": "tsx --tsconfig ../tools/challenge-helper-scripts/tsconfig.json ../tools/challenge-helper-scripts/delete-challenge", "delete-task": "tsx --tsconfig ../tools/challenge-helper-scripts/tsconfig.json ../tools/challenge-helper-scripts/delete-task", - "lint": "eslint --max-warnings 0", + "lint": "eslint --max-warnings 0 && pnpm lint-challenges", "lint-challenges": "tsx src/lint-localized", "reorder-tasks": "tsx --tsconfig ../tools/challenge-helper-scripts/tsconfig.json ../tools/challenge-helper-scripts/reorder-tasks", "update-challenge-order": "tsx --tsconfig ../tools/challenge-helper-scripts/tsconfig.json ../tools/challenge-helper-scripts/update-challenge-order", @@ -57,6 +57,7 @@ "@babel/register": "7.23.7", "@freecodecamp/browser-scripts": "workspace:*", "@freecodecamp/challenge-builder": "workspace:*", + "@freecodecamp/challenge-linter": "workspace:*", "@freecodecamp/eslint-config": "workspace:*", "@freecodecamp/shared": "workspace:*", "@total-typescript/ts-reset": "^0.6.1", @@ -67,7 +68,6 @@ "@typescript/vfs-1.6.1": "npm:@typescript/vfs@1.6.1", "@vitest/ui": "^4.0.15", "eslint": "^9.39.1", - "glob": "8.1.0", "joi": "17.12.2", "joi-objectid": "3.0.1", "js-yaml": "4.0.0", diff --git a/curriculum/src/lint-localized.js b/curriculum/src/lint-localized.js deleted file mode 100644 index 7642bb8a140..00000000000 --- a/curriculum/src/lint-localized.js +++ /dev/null @@ -1,8 +0,0 @@ -var glob = require('glob'); -const lint = require('../../tools/scripts/lint'); -const { testedLang } = require('./config'); - -glob(`challenges/${testedLang()}/**/*.md`, (err, files) => { - if (!files.length) throw Error('No files found'); - files.forEach(file => lint({ path: file })); -}); diff --git a/curriculum/src/lint-localized.ts b/curriculum/src/lint-localized.ts new file mode 100644 index 00000000000..4cd1299afdc --- /dev/null +++ b/curriculum/src/lint-localized.ts @@ -0,0 +1,8 @@ +import path from 'node:path'; +import { configure } from '@freecodecamp/challenge-linter'; +import { testedLang } from './config'; + +const CONFIG_PATH = path.resolve(__dirname, '../challenges/.markdownlint.yaml'); +const { lintAll } = configure(CONFIG_PATH); + +lintAll(`challenges/${testedLang()}/**/*.md`); diff --git a/docker/api/Dockerfile b/docker/api/Dockerfile index 778ddd744c0..61beabfbdf6 100644 --- a/docker/api/Dockerfile +++ b/docker/api/Dockerfile @@ -9,7 +9,7 @@ WORKDIR /home/node/build COPY --chown=node:node *.* . COPY --chown=node:node api/ api/ -COPY --chown=node:node packages/shared/ packages/shared/ +COPY --chown=node:node packages/ packages/ COPY --chown=node:node tools/ tools/ COPY --chown=node:node curriculum/ curriculum/ # TODO: AFAIK it's just the intro translations. Those should be folded into the @@ -36,7 +36,7 @@ WORKDIR /home/node/build COPY --chown=node:node pnpm*.yaml . COPY --chown=node:node package.json . COPY --chown=node:node api/ api/ -COPY --chown=node:node packages/shared/ packages/shared/ +COPY --chown=node:node packages/ packages/ RUN npm i -g pnpm@10 # Weirdly this config does not seem necessary for the new api (the same number @@ -52,10 +52,9 @@ WORKDIR /home/node/fcc COPY --from=builder --chown=node:node /home/node/build/api/dist/ ./ COPY --from=builder --chown=node:node /home/node/build/api/package.json api/ COPY --from=builder --chown=node:node /home/node/build/curriculum/generated/curriculum.json curriculum/generated/ -COPY --from=builder --chown=node:node /home/node/build/packages/shared/ packages/shared/ +COPY --from=builder --chown=node:node /home/node/build/packages/ packages/ COPY --from=deps --chown=node:node /home/node/build/node_modules/ node_modules/ COPY --from=deps --chown=node:node /home/node/build/api/node_modules/ api/node_modules/ -# packages/shared has no prod dependencies, so no need to copy them CMD ["node", "api/src/server.js"] diff --git a/package.json b/package.json index d4e4d994e49..6b3e4606bc9 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,6 @@ "knip:all": "npx -y knip@5 ", "lint-root": "pnpm npm-run-all lint:*", "lint": "turbo type-check && turbo lint && turbo lint-root", - "lint:challenges": "cd ./curriculum && pnpm run lint-challenges", "lint:prettier": "prettier --list-different .", "lint:css": "stylelint '**/*.css'", "preseed": "turbo setup", @@ -92,10 +91,8 @@ "js-yaml": "3.14.1", "lint-staged": "^16.2.7", "lodash": "4.17.21", - "markdownlint": "0.33.0", "npm-run-all2": "5.0.2", "prettier": "3.2.5", - "prismjs": "1.29.0", "stylelint": "16.14.1", "tsx": "4.19.1", "turbo": "^2.8.1", diff --git a/packages/challenge-linter/.gitignore b/packages/challenge-linter/.gitignore new file mode 100644 index 00000000000..1521c8b7652 --- /dev/null +++ b/packages/challenge-linter/.gitignore @@ -0,0 +1 @@ +dist diff --git a/tools/scripts/lint/.lintstagedrc.mjs b/packages/challenge-linter/.lintstagedrc.js similarity index 100% rename from tools/scripts/lint/.lintstagedrc.mjs rename to packages/challenge-linter/.lintstagedrc.js diff --git a/packages/challenge-linter/cli.js b/packages/challenge-linter/cli.js new file mode 100755 index 00000000000..d17768a371d --- /dev/null +++ b/packages/challenge-linter/cli.js @@ -0,0 +1,2 @@ +#!/usr/bin/env node +import './dist/cli.js'; diff --git a/tools/scripts/lint/eslint.config.mjs b/packages/challenge-linter/eslint.config.js similarity index 100% rename from tools/scripts/lint/eslint.config.mjs rename to packages/challenge-linter/eslint.config.js diff --git a/tools/scripts/lint/package.json b/packages/challenge-linter/package.json similarity index 58% rename from tools/scripts/lint/package.json rename to packages/challenge-linter/package.json index c8ba6608d12..ab84fb5e36e 100644 --- a/tools/scripts/lint/package.json +++ b/packages/challenge-linter/package.json @@ -1,10 +1,17 @@ { - "name": "@freecodecamp/scripts-lint", + "name": "@freecodecamp/challenge-linter", "version": "0.0.1", "description": "The freeCodeCamp.org open-source codebase and curriculum", "license": "BSD-3-Clause", "private": true, - "main": "none", + "main": "index.js", + "type": "module", + "bin": { + "challenge-linter": "./cli.js" + }, + "exports": { + ".": "./dist/index.js" + }, "repository": { "type": "git", "url": "git+https://github.com/freeCodeCamp/freeCodeCamp.git" @@ -15,15 +22,25 @@ "homepage": "https://github.com/freeCodeCamp/freeCodeCamp#readme", "author": "freeCodeCamp ", "scripts": { + "build": "tsc", "lint": "eslint --max-warnings 0", "test": "vitest run", "test:watch": "vitest", - "test:ui": "vitest --ui" + "test:ui": "vitest --ui", + "type-check": "tsc --noEmit" }, "devDependencies": { "@freecodecamp/eslint-config": "workspace:*", + "@types/glob": "^8.1.0", + "@types/js-yaml": "4.0.5", + "@types/yargs": "^17.0.35", "@vitest/ui": "^3.2.4", "eslint": "^9.39.1", - "vitest": "^3.2.4" + "glob": "^8.1.0", + "markdownlint": "0.33.0", + "prismjs": "1.29.0", + "typescript": "5.9.3", + "vitest": "^3.2.4", + "yargs": "^17.7.2" } } diff --git a/packages/challenge-linter/src/cli.ts b/packages/challenge-linter/src/cli.ts new file mode 100644 index 00000000000..112b5a8560e --- /dev/null +++ b/packages/challenge-linter/src/cli.ts @@ -0,0 +1,30 @@ +#!/usr/bin/env node +import yargs from 'yargs'; +import { hideBin } from 'yargs/helpers'; + +import { configure } from './index.js'; + +const argv = yargs(hideBin(process.argv)) + .options({ config: { type: 'string' } }) + .parseSync(); + +const configPath = argv.config; +const files = argv._; + +if (!configPath) { + console.error( + 'Error: Configuration path is required. Use --config ' + ); + process.exit(1); +} + +if (files.length === 0) { + console.error('Error: At least one file path is required to lint.'); + process.exit(1); +} + +const { lint } = configure(configPath); + +files.forEach(filePath => { + lint({ path: filePath }); +}); diff --git a/tools/scripts/lint/fixtures/badFencing.md b/packages/challenge-linter/src/fixtures/badFencing.md similarity index 100% rename from tools/scripts/lint/fixtures/badFencing.md rename to packages/challenge-linter/src/fixtures/badFencing.md diff --git a/tools/scripts/lint/fixtures/badYML.md b/packages/challenge-linter/src/fixtures/badYML.md similarity index 100% rename from tools/scripts/lint/fixtures/badYML.md rename to packages/challenge-linter/src/fixtures/badYML.md diff --git a/tools/scripts/lint/fixtures/good.md b/packages/challenge-linter/src/fixtures/good.md similarity index 100% rename from tools/scripts/lint/fixtures/good.md rename to packages/challenge-linter/src/fixtures/good.md diff --git a/packages/challenge-linter/src/fixtures/rules.yaml b/packages/challenge-linter/src/fixtures/rules.yaml new file mode 100644 index 00000000000..91bf9ed9c3d --- /dev/null +++ b/packages/challenge-linter/src/fixtures/rules.yaml @@ -0,0 +1,13 @@ +# This is a copy of those in the curriculum. They don't need to be in sync. +default: true # include all rules, with exceptions below +MD002: false # first heading should not be a top level heading +MD013: false # lines can be any length +MD022: false # headings don't need surrounding by newlines +MD024: false # no duplicate headers +MD025: false # headings are used as markers by the parser +MD031: true # fenced blocks do need surrounding by newlines +MD033: false # inline html is required +MD040: true # fenced code blocks should have a language specified +MD034: false # allow bare-URLs +MD036: false # TODO: **Example** is the main offender, should that be a heading? +whitespace: false # extra whitespace is ignored, so we don't enforce it. diff --git a/packages/challenge-linter/src/index.ts b/packages/challenge-linter/src/index.ts new file mode 100644 index 00000000000..5be777c5e01 --- /dev/null +++ b/packages/challenge-linter/src/index.ts @@ -0,0 +1,18 @@ +import { readFileSync } from 'node:fs'; +import YAML from 'js-yaml'; +import glob from 'glob'; + +import { linter } from './linter/index.js'; + +export const configure = (configPath: string) => { + const lintRules = readFileSync(configPath, 'utf8'); + const lint = linter(YAML.load(lintRules)); + const lintAll = (pattern: string) => { + glob(pattern, (err, files) => { + if (!files.length) throw Error('No files found'); + files.forEach(file => lint({ path: file })); + }); + }; + + return { lint, lintAll }; +}; diff --git a/tools/scripts/lint/index.test.js b/packages/challenge-linter/src/lint.test.js similarity index 84% rename from tools/scripts/lint/index.test.js rename to packages/challenge-linter/src/lint.test.js index 72c7948d6a5..51e6235cd00 100644 --- a/tools/scripts/lint/index.test.js +++ b/packages/challenge-linter/src/lint.test.js @@ -1,11 +1,27 @@ import path from 'path'; -import { describe, it, beforeEach, afterEach, expect, vi } from 'vitest'; -import lint from '.'; + +import { + afterEach, + beforeAll, + beforeEach, + describe, + expect, + it, + vi +} from 'vitest'; + +import { configure } from './index.js'; describe('markdown linter', () => { const good = { path: path.join(__dirname, './fixtures/good.md') }; const badYML = { path: path.join(__dirname, './fixtures/badYML.md') }; const badFencing = { path: path.join(__dirname, './fixtures/badFencing.md') }; + const configPath = path.join(__dirname, './fixtures/rules.yaml'); + let lint; + + beforeAll(() => { + lint = configure(configPath).lint; + }); beforeEach(() => { console.log = vi.fn(); // the linter signals that a file failed by setting diff --git a/packages/challenge-linter/src/linter/fenced-code-block.js b/packages/challenge-linter/src/linter/fenced-code-block.js new file mode 100644 index 00000000000..73a2ec5bee9 --- /dev/null +++ b/packages/challenge-linter/src/linter/fenced-code-block.js @@ -0,0 +1,16 @@ +export const names = ['closed-code-blocks']; +export const description = 'Code blocks must have closing triple backticks'; +export const tags = ['code']; +function rule(params, onError) { + params.parsers.micromark.tokens + .filter(token => token.type === 'codeFenced') + .forEach(token => { + if (token.text.trim().slice(-3) !== '```') { + onError({ + lineNumber: token.endLine, + detail: `Code blocks must have closing triple backticks.` + }); + } + }); +} +export { rule as function }; diff --git a/tools/scripts/lint/linter/index.js b/packages/challenge-linter/src/linter/index.js similarity index 58% rename from tools/scripts/lint/linter/index.js rename to packages/challenge-linter/src/linter/index.js index 54428592a57..33998af8e26 100644 --- a/tools/scripts/lint/linter/index.js +++ b/packages/challenge-linter/src/linter/index.js @@ -1,26 +1,29 @@ -const markdownlint = require('markdownlint'); +import markdownlint from 'markdownlint'; -const lintPrism = require('./markdown-prism'); -const lintYAML = require('./markdown-yaml'); -const fencedCodeBlock = require('./fenced-code-block'); +import * as lintPrism from './markdown-prism.js'; +import * as lintYAML from './markdown-yaml.js'; +import * as fencedCodeBlock from './fenced-code-block.js'; -function linter(rules) { +export function linter(rules) { const lint = (file, next) => { const options = { files: [file.path], config: rules, customRules: [lintYAML, lintPrism, fencedCodeBlock] }; + markdownlint(options, function callback(err, result) { const resultString = (result || '').toString(); if (resultString) { process.exitCode = 1; console.log(resultString); } + if (err) { + process.exitCode = 1; + console.error(err); + } if (next) next(err, file); }); }; return lint; } - -module.exports = linter; diff --git a/packages/challenge-linter/src/linter/markdown-prism.js b/packages/challenge-linter/src/linter/markdown-prism.js new file mode 100644 index 00000000000..d77c7de8952 --- /dev/null +++ b/packages/challenge-linter/src/linter/markdown-prism.js @@ -0,0 +1,45 @@ +import components from 'prismjs/components.js'; + +export const names = ['prism-langs']; +export const description = + 'Code block languages should be supported by PrismJS'; +export const tags = ['prism']; +function rule(params, onError) { + params.tokens + .filter(param => param.type === 'fence') + .forEach(codeBlock => { + // whitespace around the language is ignored by the parser, as is case: + const baseLang = codeBlock.info.trim().toLowerCase(); + const lang = getBaseLanguageName(baseLang); + // Rule MD040 checks if the block has a language, so this rule only + // comes into play if a language has been specified. + if (baseLang && !lang) { + onError({ + lineNumber: codeBlock.lineNumber, + detail: `'${baseLang}' is not recognised.` + }); + } + }); +} + +export { rule as function }; + +/* + * This is the method used by https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-remark-prismjs/src/load-prism-language.js + */ + +// Get the real name of a language given it or an alias +const getBaseLanguageName = nameOrAlias => { + if (components.languages[nameOrAlias]) { + return nameOrAlias; + } + return Object.keys(components.languages).find(language => { + const { alias } = components.languages[language]; + if (!alias) return false; + if (Array.isArray(alias)) { + return alias.includes(nameOrAlias); + } else { + return alias === nameOrAlias; + } + }); +}; diff --git a/packages/challenge-linter/src/linter/markdown-yaml.js b/packages/challenge-linter/src/linter/markdown-yaml.js new file mode 100644 index 00000000000..22bc3303d33 --- /dev/null +++ b/packages/challenge-linter/src/linter/markdown-yaml.js @@ -0,0 +1,24 @@ +import jsYaml from 'js-yaml'; + +export const names = ['yaml-linter']; +export const description = 'YAML code blocks should be valid'; +export const tags = ['yaml']; +function rule(params, onError) { + params.tokens + .filter(param => param.type === 'fence') + .filter(param => param.info === 'yml' || param.info === 'yaml') + // TODO since the parser only looks for yml, should we reject yaml blocks? + .forEach(codeBlock => { + try { + jsYaml.safeLoad(codeBlock.content); + } catch (e) { + onError({ + lineNumber: codeBlock.lineNumber, + detail: e.message, + context: codeBlock.line + }); + } + }); +} + +export { rule as function }; diff --git a/packages/challenge-linter/tsconfig.json b/packages/challenge-linter/tsconfig.json new file mode 100644 index 00000000000..023060dbc1e --- /dev/null +++ b/packages/challenge-linter/tsconfig.json @@ -0,0 +1,10 @@ +{ + "include": ["src"], + "extends": "../../tsconfig-base.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "outDir": "dist", + "noEmit": false + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2fa2bf75986..5b3f1205818 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -62,18 +62,12 @@ importers: lodash: specifier: 4.17.21 version: 4.17.21 - markdownlint: - specifier: 0.33.0 - version: 0.33.0 npm-run-all2: specifier: 5.0.2 version: 5.0.2 prettier: specifier: 3.2.5 version: 3.2.5 - prismjs: - specifier: 1.29.0 - version: 1.29.0 stylelint: specifier: 16.14.1 version: 16.14.1(typescript@5.9.3) @@ -719,6 +713,9 @@ importers: '@freecodecamp/challenge-builder': specifier: workspace:* version: link:../packages/challenge-builder + '@freecodecamp/challenge-linter': + specifier: workspace:* + version: link:../packages/challenge-linter '@freecodecamp/eslint-config': specifier: workspace:* version: link:../packages/eslint-config @@ -749,9 +746,6 @@ importers: eslint: specifier: ^9.39.1 version: 9.39.2(jiti@2.6.1) - glob: - specifier: 8.1.0 - version: 8.1.0 joi: specifier: 17.12.2 version: 17.12.2 @@ -850,6 +844,45 @@ importers: specifier: ^3.2.4 version: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.9)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@26.1.0)(msw@2.12.7(@types/node@24.10.9)(typescript@5.9.3))(terser@5.28.1)(tsx@4.21.0)(yaml@2.8.1) + packages/challenge-linter: + devDependencies: + '@freecodecamp/eslint-config': + specifier: workspace:* + version: link:../eslint-config + '@types/glob': + specifier: ^8.1.0 + version: 8.1.0 + '@types/js-yaml': + specifier: 4.0.5 + version: 4.0.5 + '@types/yargs': + specifier: ^17.0.35 + version: 17.0.35 + '@vitest/ui': + specifier: ^3.2.4 + version: 3.2.4(vitest@3.2.4) + eslint: + specifier: ^9.39.1 + version: 9.39.2(jiti@2.6.1) + glob: + specifier: ^8.1.0 + version: 8.1.0 + markdownlint: + specifier: 0.33.0 + version: 0.33.0 + prismjs: + specifier: 1.29.0 + version: 1.29.0 + typescript: + specifier: 5.9.3 + version: 5.9.3 + vitest: + specifier: ^3.2.4 + version: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.9)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@26.1.0)(msw@2.12.7(@types/node@24.10.9)(typescript@5.9.3))(terser@5.28.1)(tsx@4.21.0)(yaml@2.8.1) + yargs: + specifier: ^17.7.2 + version: 17.7.2 + packages/eslint-config: devDependencies: '@babel/eslint-parser': @@ -1188,21 +1221,6 @@ importers: specifier: 5.9.3 version: 5.9.3 - tools/scripts/lint: - devDependencies: - '@freecodecamp/eslint-config': - specifier: workspace:* - version: link:../../../packages/eslint-config - '@vitest/ui': - specifier: ^3.2.4 - version: 3.2.4(vitest@3.2.4) - eslint: - specifier: ^9.39.1 - version: 9.39.2(jiti@2.6.1) - vitest: - specifier: ^3.2.4 - version: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.9)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@26.1.0)(msw@2.12.7(@types/node@24.10.9)(typescript@5.9.3))(terser@5.28.1)(tsx@4.21.0)(yaml@2.8.1) - tools/scripts/seed: devDependencies: '@freecodecamp/eslint-config': @@ -5491,6 +5509,9 @@ packages: '@types/yargs@17.0.26': resolution: {integrity: sha512-Y3vDy2X6zw/ZCumcwLpdhM5L7jmyGpmBCTYMHDLqT2IKVMYRRLdv6ZakA+wxhra6Z/3bwhNbNl9bDGXaFU+6rw==} + '@types/yargs@17.0.35': + resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} + '@types/yauzl@2.10.3': resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} @@ -13746,8 +13767,8 @@ packages: engines: {node: '>=14.17'} hasBin: true - uc.micro@2.0.0: - resolution: {integrity: sha512-DffL94LsNOccVn4hyfRe5rdKa273swqeA5DJpMOeFmEn1wCDc7nAbbB0gXlgBCL7TNzeTv6G7XVWzan7iJtfig==} + uc.micro@2.1.0: + resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} umd@3.0.3: resolution: {integrity: sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow==} @@ -20832,6 +20853,10 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.1 + '@types/yargs@17.0.35': + dependencies: + '@types/yargs-parser': 21.0.1 + '@types/yauzl@2.10.3': dependencies: '@types/node': 24.10.9 @@ -26701,7 +26726,7 @@ snapshots: linkify-it@5.0.0: dependencies: - uc.micro: 2.0.0 + uc.micro: 2.1.0 lint-staged@16.2.7: dependencies: @@ -26944,7 +26969,7 @@ snapshots: linkify-it: 5.0.0 mdurl: 2.0.0 punycode.js: 2.3.1 - uc.micro: 2.0.0 + uc.micro: 2.1.0 markdown-table@2.0.0: dependencies: @@ -30907,7 +30932,7 @@ snapshots: terser@5.28.1: dependencies: '@jridgewell/source-map': 0.3.5 - acorn: 8.11.3 + acorn: 8.15.0 commander: 2.20.3 source-map-support: 0.5.21 @@ -31162,7 +31187,7 @@ snapshots: tsx@4.21.0: dependencies: esbuild: 0.27.2 - get-tsconfig: 4.10.1 + get-tsconfig: 4.13.0 optionalDependencies: fsevents: 2.3.3 @@ -31294,7 +31319,7 @@ snapshots: typescript@5.9.3: {} - uc.micro@2.0.0: {} + uc.micro@2.1.0: {} umd@3.0.3: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 2d929c727e7..123c5ef2322 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -9,7 +9,6 @@ packages: - 'tools/client-plugins/*' - 'tools/crowdin' - 'tools/daily-challenges' - - 'tools/scripts/lint' - 'tools/scripts/seed' - 'tools/scripts/seed-exams' - 'packages/*' diff --git a/tools/scripts/lint/index.js b/tools/scripts/lint/index.js deleted file mode 100644 index ebddce83613..00000000000 --- a/tools/scripts/lint/index.js +++ /dev/null @@ -1,20 +0,0 @@ -const fs = require('fs'); -const path = require('path'); -const YAML = require('js-yaml'); -const argv = require('yargs').argv; -const linter = require('./linter'); - -const CONFIG_PATH = path.resolve( - __dirname, - '../../../curriculum/challenges/.markdownlint.yaml' -); -const isMDRE = /.*\.md$/; - -const lintRules = fs.readFileSync(CONFIG_PATH, 'utf8'); - -const lint = linter(YAML.load(lintRules)); - -const files = argv._.filter(arg => isMDRE.test(arg)); -files.forEach(path => lint({ path: path })); - -module.exports = lint; diff --git a/tools/scripts/lint/linter/fenced-code-block.js b/tools/scripts/lint/linter/fenced-code-block.js deleted file mode 100644 index 830c73951f1..00000000000 --- a/tools/scripts/lint/linter/fenced-code-block.js +++ /dev/null @@ -1,17 +0,0 @@ -module.exports = { - names: ['closed-code-blocks'], - description: 'Code blocks must have closing triple backticks', - tags: ['code'], - function: function rule(params, onError) { - params.parsers.micromark.tokens - .filter(token => token.type === 'codeFenced') - .forEach(token => { - if (token.text.trim().slice(-3) !== '```') { - onError({ - lineNumber: token.endLine, - detail: `Code blocks must have closing triple backticks.` - }); - } - }); - } -}; diff --git a/tools/scripts/lint/linter/markdown-prism.js b/tools/scripts/lint/linter/markdown-prism.js deleted file mode 100644 index b4d5b8c2e8b..00000000000 --- a/tools/scripts/lint/linter/markdown-prism.js +++ /dev/null @@ -1,44 +0,0 @@ -const components = require(`prismjs/components`); - -module.exports = { - names: ['prism-langs'], - description: 'Code block languages should be supported by PrismJS', - tags: ['prism'], - function: function rule(params, onError) { - params.tokens - .filter(param => param.type === 'fence') - .forEach(codeBlock => { - // whitespace around the language is ignored by the parser, as is case: - const baseLang = codeBlock.info.trim().toLowerCase(); - const lang = getBaseLanguageName(baseLang); - // Rule MD040 checks if the block has a language, so this rule only - // comes into play if a language has been specified. - if (baseLang && !lang) { - onError({ - lineNumber: codeBlock.lineNumber, - detail: `'${baseLang}' is not recognised.` - }); - } - }); - } -}; - -/* - * This is the method used by https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-remark-prismjs/src/load-prism-language.js - */ - -// Get the real name of a language given it or an alias -const getBaseLanguageName = nameOrAlias => { - if (components.languages[nameOrAlias]) { - return nameOrAlias; - } - return Object.keys(components.languages).find(language => { - const { alias } = components.languages[language]; - if (!alias) return false; - if (Array.isArray(alias)) { - return alias.includes(nameOrAlias); - } else { - return alias === nameOrAlias; - } - }); -}; diff --git a/tools/scripts/lint/linter/markdown-yaml.js b/tools/scripts/lint/linter/markdown-yaml.js deleted file mode 100644 index 6920bd1ea65..00000000000 --- a/tools/scripts/lint/linter/markdown-yaml.js +++ /dev/null @@ -1,24 +0,0 @@ -const yaml = require('js-yaml'); - -module.exports = { - names: ['yaml-linter'], - description: 'YAML code blocks should be valid', - tags: ['yaml'], - function: function rule(params, onError) { - params.tokens - .filter(param => param.type === 'fence') - .filter(param => param.info === 'yml' || param.info === 'yaml') - // TODO since the parser only looks for yml, should we reject yaml blocks? - .forEach(codeBlock => { - try { - yaml.safeLoad(codeBlock.content); - } catch (e) { - onError({ - lineNumber: codeBlock.lineNumber, - detail: e.message, - context: codeBlock.line - }); - } - }); - } -}; diff --git a/tools/scripts/lint/tsconfig.json b/tools/scripts/lint/tsconfig.json deleted file mode 100644 index 94fe2cca852..00000000000 --- a/tools/scripts/lint/tsconfig.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "../../../tsconfig-base.json" -}