-
-
Notifications
You must be signed in to change notification settings - Fork 4.9k
chore: add initial ecosystem plugin tests workflow #19643
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 19 commits
baa84df
b53fc7a
4bb2f02
f17af7d
30404a4
d822b05
4345a1a
3e9eff8
ba6cc11
8674cba
08d32ac
073be89
ece43e6
27b1cf7
8b98d5b
d62f557
2365eaa
cb2fd6a
309d889
79b3c45
8312ff6
76daffd
1498b92
007a82e
b65db91
5454ba9
0d60d7c
5288f49
e69f023
bdf13de
aa75a32
c85df45
a03b450
afe15a4
997af03
70fef79
14d745a
ba685b1
2983232
d856e94
b185105
f60df01
5815b23
862dd99
3c712da
b941959
97e986e
0444246
4d5e41d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| name: Test Ecosystem Plugins | ||
|
|
||
| on: | ||
| push: | ||
| branches: | ||
| - "ecosystem/*" | ||
| schedule: | ||
| # “At 00:00.” https://crontab.guru/#0_0_*_*_* | ||
| - cron: "0 0 * * *" | ||
| workflow_dispatch: ~ | ||
|
|
||
| permissions: read-all | ||
|
|
||
| jobs: | ||
| test_ecosystem: | ||
| runs-on: ubuntu-latest | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: actions/setup-node@v4 | ||
JoshuaKGoldberg marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
JoshuaKGoldberg marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| with: | ||
| node-version: "lts/*" | ||
| - run: npm install | ||
| - id: tester | ||
| run: npm run test:ecosystem -- --plugin all | ||
| - if: steps.tester.outcome == 'failure' && github.head_ref == 'main' | ||
| env: | ||
| DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} | ||
JoshuaKGoldberg marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| uses: Ilshidur/action-discord@0c4b27844ba47cb1c7bee539c8eead5284ce9fa9 # v0.3.2 | ||
JoshuaKGoldberg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| with: | ||
| args: "Ecosystem tests failed." | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| name: Update Test Ecosystem Plugins | ||
|
|
||
| on: | ||
| schedule: | ||
| # “At 00:00 on Monday.” https://crontab.guru/#0_0_*_*_1 | ||
| - cron: "0 0 * * 1" | ||
JoshuaKGoldberg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| workflow_dispatch: ~ | ||
|
|
||
| permissions: read-all | ||
|
|
||
| jobs: | ||
| test_ecosystem: | ||
| runs-on: ubuntu-latest | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: actions/setup-node@v4 | ||
JoshuaKGoldberg marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| with: | ||
| node-version: "lts/*" | ||
| - run: npm install | ||
| - run: npm run test:ecosystem:update -- --plugin all | ||
| - uses: peter-evans/[email protected] | ||
| with: | ||
| commit-message: "chore: update ecosystem plugins" | ||
| title: "chore: update ecosystem plugins" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ | |
| test.js | ||
| coverage/ | ||
| build/ | ||
| ecosystem/ | ||
| npm-debug.log | ||
| yarn-error.log | ||
| .pnpm-debug.log | ||
|
|
||
fasttime marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| { | ||
| "@eslint/css": { | ||
| "commit": "1e8e225e0b4a21fbf638d13f4a45947184939331", | ||
| "repository": "https://github.com/eslint/css" | ||
| }, | ||
|
||
| "@eslint/json": { | ||
| "commit": "3686ee6a8149f82505f8ae50992edc3552cb7018", | ||
| "repository": "https://github.com/eslint/json" | ||
| }, | ||
| "@eslint/markdown": { | ||
| "commit": "8c24fad47a0a23906265ed1601122076891c460d", | ||
| "repository": "https://github.com/eslint/markdown" | ||
| }, | ||
| "@eslint-community/eslint-plugin-eslint-comments": { | ||
| "commit": "d6869dfbb794437c00e0e27e6cc0dbdfd3c56fac", | ||
| "repository": "https://github.com/eslint-community/eslint-plugin-eslint-comments" | ||
| }, | ||
| "eslint-plugin-unicorn": { | ||
| "commit": "2b8ebe579cf4e52a1acdc9f9169f86a5fae05635", | ||
| "repository": "https://github.com/sindresorhus/eslint-plugin-unicorn" | ||
| }, | ||
| "eslint-plugin-vue": { | ||
| "commit": "ca973010b511755488eeac2851ffd6597f23ccb2", | ||
| "repository": "https://github.com/vuejs/eslint-plugin-vue" | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| /** | ||
| * @fileoverview Data utilities for ecosystem tests and updates to data. | ||
| * @author Josh Goldberg | ||
| */ | ||
|
|
||
| //----------------------------------------------------------------------------- | ||
| // Requirements | ||
| //----------------------------------------------------------------------------- | ||
|
|
||
| import chalk from "chalk"; | ||
JoshuaKGoldberg marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| import util from "node:util"; | ||
| import path from "node:path"; | ||
|
|
||
| //----------------------------------------------------------------------------- | ||
| // Types | ||
| //----------------------------------------------------------------------------- | ||
|
|
||
| /** | ||
| * Settings for how to clone, set up, and test an ecosystem plugin. | ||
| * @typedef {Object} PluginData | ||
| * @property {string} commit Hash to check out after cloning the plugin. | ||
| * @property {string} repository Repository URL to clone the plugin from. | ||
| */ | ||
|
|
||
| //----------------------------------------------------------------------------- | ||
| // Constants | ||
| //----------------------------------------------------------------------------- | ||
|
|
||
| export const pluginDataFilePath = path.join( | ||
| import.meta.dirname, | ||
| "plugins-data.json", | ||
| ); | ||
|
|
||
| //----------------------------------------------------------------------------- | ||
| // Functions | ||
| //----------------------------------------------------------------------------- | ||
|
|
||
| /** | ||
| * | ||
JoshuaKGoldberg marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| * @param {"test" | "update"} action | ||
| * @returns {[string, PluginData][]} | ||
| */ | ||
| export async function getPlugins(action) { | ||
| const { values } = util.parseArgs({ | ||
| options: { | ||
| plugin: { | ||
| type: "string", | ||
| help: `The name of the plugin to ${action}, or 'all' for all plugins (default: 'all')`, | ||
JoshuaKGoldberg marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| }, | ||
| }, | ||
| }); | ||
|
|
||
| const { plugin: pluginRequested = "all" } = values; | ||
| const { default: pluginsData } = await import(pluginDataFilePath, { | ||
JoshuaKGoldberg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| with: { type: "json" }, | ||
| }); | ||
|
|
||
| if (pluginRequested !== "all" && !(pluginRequested in pluginsData)) { | ||
| console.error(`The plugin "${values.plugin}" is not supported.`); | ||
| console.error( | ||
| `Supported plugins are: ${["", ...Object.keys(pluginsData)].join( | ||
| "\n ", | ||
| )}`, | ||
| ); | ||
| console.error( | ||
| `Alternately, run with --plugin all to ${action} all plugins.`, | ||
| ); | ||
| process.exit(1); | ||
| } | ||
|
|
||
| const pluginsSelected = | ||
| pluginRequested === "all" | ||
| ? Object.entries(pluginsData) | ||
| : [[pluginRequested, pluginsData[pluginRequested]]]; | ||
|
|
||
| console.log( | ||
| `Plugins to ${action}:`, | ||
| chalk.bold(pluginsSelected.map(([key]) => key).join(", ")), | ||
| ); | ||
|
|
||
| return { pluginsData, pluginsSelected }; | ||
| } | ||
JoshuaKGoldberg marked this conversation as resolved.
Show resolved
Hide resolved
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,146 @@ | ||
| /** | ||
| * @fileoverview A utility to test ecosystem plugin(s) against the built ESLint. | ||
| * @author Josh Goldberg | ||
| */ | ||
|
|
||
| //----------------------------------------------------------------------------- | ||
| // Requirements | ||
| //----------------------------------------------------------------------------- | ||
|
|
||
| import chalk from "chalk"; | ||
JoshuaKGoldberg marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| import spawn from "nano-spawn"; | ||
| import fs from "node:fs/promises"; | ||
| import path from "node:path"; | ||
| import { getPlugins } from "./data.mjs"; | ||
|
|
||
| /** | ||
| * @typedef {import("./data").PluginSettings} PluginSettings | ||
| */ | ||
|
|
||
| //----------------------------------------------------------------------------- | ||
| // Helpers | ||
| //----------------------------------------------------------------------------- | ||
|
|
||
| /** | ||
| * Runs ecosystem tests for a single plugin. It will: | ||
| * 1. Clone the plugin repository into a sandbox directory | ||
| * 2. Check out the plugin's commit to test on | ||
| * 3. Install the plugin's dependencies | ||
| * 4. Link the built ESLint into the plugin | ||
JoshuaKGoldberg marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| * 5. Build, if the plugin defines a build script | ||
| * 6. Run tests | ||
| * This intentionally does not try/catch: any errors will be thrown. | ||
| * | ||
| * @param {string} pluginKey | ||
| * @param {PluginSettings} pluginSettings | ||
| */ | ||
| async function runTests(pluginKey, pluginSettings) { | ||
| const directory = path.join( | ||
| SANDBOX_DIRECTORY, | ||
| pluginKey | ||
| .replaceAll(/[^a-z-]/g, " ") | ||
| .trim() | ||
| .replaceAll(" ", "-"), | ||
| ); | ||
| console.log(chalk.bold(`Testing ${pluginKey} in ${directory}`)); | ||
|
|
||
| /** | ||
| * Attempts to run a command in the plugin sandbox directory. | ||
| * If it fails, any error stdout will be logged in red before a re-throw. | ||
| * @param {string} command | ||
| * @param {string[]} args | ||
| */ | ||
| const runCommand = async (command, ...args) => { | ||
| console.log(chalk.gray(`[${pluginKey}]`, [command, ...args].join(" "))); | ||
| try { | ||
| return await spawn(command, args, { | ||
| cwd: directory, | ||
| }); | ||
| } catch (error) { | ||
| console.error( | ||
| chalk.red(`[${pluginKey}]`), | ||
| "stdout" in error ? error.stdout : error, | ||
JoshuaKGoldberg marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ); | ||
| throw error; | ||
| } | ||
| }; | ||
|
|
||
| // 1. Clone the plugin repository into a sandbox directory | ||
| await fs.mkdir(directory, { force: true }); | ||
| await runCommand( | ||
| "git", | ||
| "clone", | ||
| pluginSettings.repository, | ||
| directory, | ||
| "--depth", | ||
| "1", | ||
| ); | ||
|
|
||
| // 2. Check out the plugin's commit to test on | ||
| await runCommand("git", "fetch", "origin", pluginSettings.commit); | ||
| await runCommand("git", "checkout", pluginSettings.commit); | ||
|
|
||
| // 3. Install the plugin's dependencies | ||
| await runCommand("pwd"); | ||
| await runCommand("ni"); | ||
JoshuaKGoldberg marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| // 4. Link the built ESLint into the plugin | ||
| await runCommand("npm", "link", "eslint"); | ||
|
|
||
| const packageJsonPath = path.resolve( | ||
| process.cwd(), | ||
| path.join(directory, "package.json"), | ||
| ); | ||
| const packageJson = await import(packageJsonPath, { | ||
| with: { type: "json" }, | ||
| }); | ||
|
|
||
| // 5. Build, if the plugin defines a build script | ||
| if (packageJson.default.scripts.build) { | ||
| await runCommand("nr", "build"); | ||
| } | ||
|
|
||
| // 6. Run test | ||
| await runCommand("nr", "test"); | ||
| } | ||
|
|
||
| //----------------------------------------------------------------------------- | ||
| // Main | ||
| //----------------------------------------------------------------------------- | ||
|
|
||
| const { pluginsSelected } = await getPlugins("test"); | ||
|
|
||
| const SANDBOX_DIRECTORY = path.join(process.cwd(), "ecosystem"); | ||
|
|
||
| console.log(`Clearing existing sandbox directory: ${SANDBOX_DIRECTORY}`); | ||
| await fs.rm(SANDBOX_DIRECTORY, { | ||
| force: true, | ||
| maxRetries: 8, | ||
| recursive: true, | ||
| }); | ||
| await fs.mkdir(SANDBOX_DIRECTORY, { recursive: true }); | ||
| console.log(""); | ||
|
|
||
| const errors = []; | ||
|
|
||
| // For each plugin to test, we try to runTests, recording thrown exceptions in errors | ||
| for (const [pluginKey, pluginSettings] of pluginsSelected) { | ||
| try { | ||
| await runTests(pluginKey, pluginSettings); | ||
| } catch (error) { | ||
| errors.push({ error, pluginKey }); | ||
| } | ||
|
|
||
| console.log(""); | ||
| } | ||
|
|
||
| // If we had any errors, report them and exit as failed | ||
| if (errors.length) { | ||
| console.error(chalk.red("Errors occurred while testing plugins:")); | ||
| for (const { error, pluginKey } of errors) { | ||
| console.error(`${chalk.bold.red(pluginKey)}: ${chalk.red(error)}`); | ||
| } | ||
| process.exitCode = 1; | ||
| } else { | ||
| console.log(chalk.green("All tests completed successfully.")); | ||
| } | ||
JoshuaKGoldberg marked this conversation as resolved.
Show resolved
Hide resolved
JoshuaKGoldberg marked this conversation as resolved.
Show resolved
Hide resolved
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| { | ||
| "@eslint/css": { | ||
| "commit": "1e8e225e0b4a21fbf638d13f4a45947184939331", | ||
| "repository": "https://github.com/eslint/css" | ||
| }, | ||
| "@eslint/json": { | ||
| "commit": "3686ee6a8149f82505f8ae50992edc3552cb7018", | ||
| "repository": "https://github.com/eslint/json" | ||
| }, | ||
| "@eslint/markdown": { | ||
| "commit": "8c24fad47a0a23906265ed1601122076891c460d", | ||
| "repository": "https://github.com/eslint/markdown" | ||
| }, | ||
| "@eslint-community/eslint-plugin-eslint-comments": { | ||
| "commit": "d6869dfbb794437c00e0e27e6cc0dbdfd3c56fac", | ||
| "repository": "https://github.com/eslint-community/eslint-plugin-eslint-comments" | ||
| }, | ||
| "eslint-plugin-unicorn": { | ||
| "commit": "2b8ebe579cf4e52a1acdc9f9169f86a5fae05635", | ||
| "repository": "https://github.com/sindresorhus/eslint-plugin-unicorn" | ||
| }, | ||
| "eslint-plugin-vue": { | ||
| "commit": "ca973010b511755488eeac2851ffd6597f23ccb2", | ||
| "repository": "https://github.com/vuejs/eslint-plugin-vue" | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.