diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 82f5abd23a1..b6d47fd2020 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -14,34 +14,93 @@ on: jobs: run-linters: - name: Run linters + name: Run all linters runs-on: ubuntu-latest steps: - name: Check out Git repository uses: actions/checkout@v4 with: - submodules: 'recursive' + submodules: "recursive" - name: Install pnpm uses: pnpm/action-setup@v4 with: version: 10 - - name: Set up Node.js + - name: Set up Node uses: actions/setup-node@v4 with: - node-version-file: '.nvmrc' - cache: 'pnpm' + node-version-file: ".nvmrc" + cache: "pnpm" - - name: Install Node.js dependencies + - name: Install Node modules run: pnpm i - - name: Lint with Biome + # Lint files with Biome-Lint - https://biomejs.dev/linter/ + - name: Run Biome-Lint run: pnpm biome-ci + id: biome_lint + continue-on-error: true - - name: Check dependencies with depcruise + # Validate dependencies with dependency-cruiser - https://github.com/sverweij/dependency-cruiser + - name: Run Dependency-Cruise run: pnpm depcruise - - - name: Lint with ls-lint - run: pnpm ls-lint \ No newline at end of file + id: depcruise + continue-on-error: true + + # Validate types with tsc - https://www.typescriptlang.org/docs/handbook/compiler-options.html#using-the-cli + - name: Run Typecheck + run: pnpm typecheck + id: typecheck + continue-on-error: true + + # The exact same thing + - name: Run Typecheck (scripts) + run: pnpm typecheck:scripts + id: typecheck-scripts + continue-on-error: true + + - name: Evaluate for Errors + env: + BIOME_LINT_OUTCOME: ${{ steps.biome_lint.outcome }} + DEPCRUISE_OUTCOME: ${{ steps.depcruise.outcome }} + TYPECHECK_OUTCOME: ${{ steps.typecheck.outcome }} + TYPECHECK_SCRIPTS_OUTCOME: ${{ steps.typecheck-scripts.outcome }} + run: | + # Check for Errors + + # Make text red. + red () { + printf "\e[31m%s\e[0m" "$1" + } + + # Make text green. + green () { + printf "\e[32m%s\e[0m" "$1" + } + + print_result() { + local name=$1 + local outcome=$2 + if [ "$outcome" == "success" ]; then + printf "$(green "✅ $name: $outcome")\n" + else + printf "$(red "❌ $name: $outcome")\n" + fi + } + + print_result "Biome" "$BIOME_LINT_OUTCOME" + print_result "Depcruise" "$DEPCRUISE_OUTCOME" + print_result "Typecheck" "$TYPECHECK_OUTCOME" + print_result "Typecheck scripts" "$TYPECHECK_SCRIPTS_OUTCOME" + + if [[ "$BIOME_LINT_OUTCOME" != "success" || \ + "$DEPCRUISE_OUTCOME" != "success" || \ + "$TYPECHECK_OUTCOME" != "success" || \ + "$TYPECHECK_SCRIPTS_OUTCOME" != "success" ]]; then + printf "$(red "❌ One or more checks failed!")\n" >&2 + exit 1 + fi + + printf "$(green "✅ All checks passed!")\n" diff --git a/biome.jsonc b/biome.jsonc index a63ce0ee07d..5a6de48bd0a 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -176,9 +176,10 @@ }, // Overrides to prevent unused import removal inside `overrides.ts` and enums files (for TSDoc linkcodes), - // as well as in all TS files in `scripts/` (which are assumed to be boilerplate templates). + // as well as inside script boilerplate files. { - "includes": ["**/src/overrides.ts", "**/src/enums/**/*", "**/scripts/**/*.ts", "**/*.d.ts"], + // TODO: Rename existing boilerplates in the folder and remove this last alias + "includes": ["**/src/overrides.ts", "**/src/enums/**/*", "scripts/**/*.boilerplate.ts", "**/boilerplates/*.ts"], "linter": { "rules": { "correctness": { @@ -188,7 +189,7 @@ } }, { - "includes": ["**/src/overrides.ts", "**/scripts/**/*.ts"], + "includes": ["**/src/overrides.ts"], "linter": { "rules": { "style": { diff --git a/package.json b/package.json index d3ea890c005..8bc67d570a1 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,9 @@ "test:watch": "vitest watch --coverage --no-isolate", "test:silent": "vitest run --silent='passed-only' --no-isolate", "test:create": "node scripts/create-test/create-test.js", + "eggMoves:parse": "node scripts/parse-egg-moves/main.js", "typecheck": "tsc --noEmit", + "typecheck:scripts": "tsc -p scripts/jsconfig.json", "biome": "biome check --write --changed --no-errors-on-unmatched --diagnostic-level=error", "biome-ci": "biome ci --diagnostic-level=error --reporter=github --no-errors-on-unmatched", "docs": "typedoc", diff --git a/scripts/create-test/create-test.js b/scripts/create-test/create-test.js index 765993959d1..5e395783da7 100644 --- a/scripts/create-test/create-test.js +++ b/scripts/create-test/create-test.js @@ -156,7 +156,7 @@ async function runInteractive() { console.log(chalk.green.bold(`✔ File created at: test/${localDir}/${fileName}.test.ts\n`)); console.groupEnd(); } catch (err) { - console.error(chalk.red("✗ Error: ", err.message)); + console.error(chalk.red("✗ Error: ", err)); } } diff --git a/scripts/decrypt-save.js b/scripts/decrypt-save.js index e50f152f159..26b0a311378 100644 --- a/scripts/decrypt-save.js +++ b/scripts/decrypt-save.js @@ -1,7 +1,6 @@ // Usage: node decrypt-save.js [save-file] -// biome-ignore lint/performance/noNamespaceImport: This is how you import fs from node -import * as fs from "node:fs"; +import fs from "node:fs"; import crypto_js from "crypto-js"; const { AES, enc } = crypto_js; @@ -60,6 +59,11 @@ function decryptSave(path) { try { fileData = fs.readFileSync(path, "utf8"); } catch (e) { + if (!(e instanceof Error)) { + console.error(`Unrecognized error: ${e}`); + process.exit(1); + } + // @ts-expect-error - e is usually a SystemError (all of which have codes) switch (e.code) { case "ENOENT": console.error(`File not found: ${path}`); @@ -104,6 +108,13 @@ function writeToFile(filePath, data) { try { fs.writeFileSync(filePath, data); } catch (e) { + if (!(e instanceof Error)) { + console.error("Unknown error detected: ", e); + process.exitCode = 1; + return; + } + + // @ts-expect-error - e is usually a SystemError (all of which have codes) switch (e.code) { case "EACCES": console.error(`Could not open ${filePath}: Permission denied`); @@ -114,7 +125,8 @@ function writeToFile(filePath, data) { default: console.error(`Error writing file: ${e.message}`); } - process.exit(1); + process.exitCode = 1; + return; } } diff --git a/scripts/jsconfig.json b/scripts/jsconfig.json new file mode 100644 index 00000000000..aed71f4f576 --- /dev/null +++ b/scripts/jsconfig.json @@ -0,0 +1,17 @@ +{ + "include": ["**/*.js"], + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "rootDir": ".", + "target": "esnext", + "module": "nodenext", + "moduleResolution": "nodenext", + "erasableSyntaxOnly": true, + "strict": true, + "noEmit": true, + // Forcibly disable `node_modules` recursion to prevent TSC from typechecking random JS files. + // This is disabled by default in `tsconfig.json`, but needs to be explicitly disabled from the default of `2` + "maxNodeModuleJsDepth": 0 + } +} diff --git a/scripts/parse-egg-moves/egg-move-template.boilerplate.ts b/scripts/parse-egg-moves/egg-move-template.boilerplate.ts new file mode 100644 index 00000000000..bfac05f4bde --- /dev/null +++ b/scripts/parse-egg-moves/egg-move-template.boilerplate.ts @@ -0,0 +1,10 @@ +//! DO NOT EDIT THIS FILE - CREATED BY THE `eggMoves:parse` script automatically +import { MoveId } from "#enums/move-id"; +import { SpeciesId } from "#enums/species-id"; + +/** + * An object mapping all base form {@linkcode SpeciesId}s to an array of {@linkcode MoveId}s corresponding + * to their current egg moves. + * Generated by the `eggMoves:parse` script using a CSV sourced from the current Balance Team spreadsheet. + */ +export const speciesEggMoves = "{{table}}"; diff --git a/scripts/parse-egg-moves/help-message.js b/scripts/parse-egg-moves/help-message.js new file mode 100644 index 00000000000..397a28e5011 --- /dev/null +++ b/scripts/parse-egg-moves/help-message.js @@ -0,0 +1,17 @@ +import chalk from "chalk"; + +/** Show help/usage text for the `eggMoves:parse` CLI. */ +export function showHelpText() { + console.log(` +Usage: ${chalk.cyan("pnpm eggMoves:parse [options]")} +If given no options, assumes ${chalk.blue("\`--interactive\`")}. +If given only a file path, assumes ${chalk.blue("\`--file\`")}. + +${chalk.hex("#ffa500")("Options:")} + ${chalk.blue("-h, --help")} Show this help message. + ${chalk.blue("-f, --file[=PATH]")} Specify a path to a CSV file to read, or provide one from stdin. + ${chalk.blue("-t, --text[=TEXT]")} + ${chalk.blue("-c, --console[=TEXT]")} Specify CSV text to read, or provide it from stdin. + ${chalk.blue("-i, --interactive")} Run in interactive mode (default) +`); +} diff --git a/scripts/parse-egg-moves/interactive.js b/scripts/parse-egg-moves/interactive.js new file mode 100644 index 00000000000..68ee41e7900 --- /dev/null +++ b/scripts/parse-egg-moves/interactive.js @@ -0,0 +1,108 @@ +import fs from "fs"; +import chalk from "chalk"; +import inquirer from "inquirer"; +import { showHelpText } from "./help-message.js"; + +/** + * @import { Option } from "./main.js" + */ + +/** + * Prompt the user to interactively select an option (console/file) to retrieve the egg move CSV. + * @returns {Promise