Skip to content

Commit

Permalink
fix!: add compat tests
Browse files Browse the repository at this point in the history
BREAKING CHANGE: Peer dependency on `xstate` increased from `>=v5.0.0` to `>=v5.14.0`.
  • Loading branch information
boneskull committed Sep 5, 2024
1 parent 90a8ccd commit 273920a
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 9 deletions.
15 changes: 15 additions & 0 deletions .github/workflows/compat.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
- uses: ./.github/actions/prepare
- run: npm run test:compat

name: XState Compatibility

on:
pull_request: ~
push:
branches:
- main
7 changes: 3 additions & 4 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
// @ts-check

import eslint from '@eslint/js';
import stylistic from '@stylistic/eslint-plugin';
import n from 'eslint-plugin-n';
Expand Down Expand Up @@ -27,7 +27,7 @@ export default tseslint.config(
perfectionist.configs['recommended-natural'],
...tseslint.config({
extends: tseslint.configs.recommendedTypeChecked,
files: ['**/*.js', '**/*.ts'],
files: ['**/*.ts', '**/*.mts', '**/*.cts'],
languageOptions: {
parserOptions: {
projectService: {
Expand Down Expand Up @@ -119,7 +119,6 @@ export default tseslint.config(

// seems to be incompatible with tshy
'n/no-extraneous-import': 'off',

'n/no-unpublished-import': 'off',

'no-empty': [
Expand Down
54 changes: 52 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 7 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,13 @@
"lint:spelling": "cspell \"**\" \".github/**/*\"",
"lint:staged": "lint-staged",
"prepare": "husky",
"test": "node --import tsx --test \"./test/*.spec.ts\"",
"test": "node --test --import tsx \"./test/*.spec.ts\"",
"test:compat": "tsx ./test/xstate-compat.ts",
"test:coverage": "c8 npm test",
"test:types": "tsc -p tsconfig.tsc.json"
},
"peerDependencies": {
"xstate": ">=5.0.0"
"xstate": ">=5.14.0"
},
"devDependencies": {
"@commitlint/cli": "19.4.1",
Expand All @@ -87,6 +88,7 @@
"@stylistic/eslint-plugin": "2.7.2",
"@types/eslint__js": "8.42.3",
"@types/node": "20.16.3",
"@types/semver": "7.5.8",
"c8": "10.1.2",
"cspell": "8.14.2",
"eslint": "9.9.1",
Expand All @@ -104,6 +106,7 @@
"prettier-plugin-jsdoc": "1.3.0",
"prettier-plugin-organize-imports": "4.0.0",
"prettier-plugin-pkg": "0.18.1",
"semver": "7.6.3",
"serve": "14.2.3",
"tshy": "3.0.2",
"tsx": "4.19.0",
Expand All @@ -112,7 +115,8 @@
"typedoc-plugin-mdn-links": "3.2.11",
"typescript": "5.5.4",
"typescript-eslint": "8.3.0",
"xstate": "5.18.0"
"xstate": "5.18.0",
"zx": "8.1.5"
},
"commitlint": {
"extends": [
Expand Down
114 changes: 114 additions & 0 deletions test/xstate-compat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#!/usr/bin/env tsx

import {setMaxListeners} from 'node:events';
import {parse} from 'semver';
import {$} from 'zx';

/**
* This script runs the test suite against all versions of `xstate` greater than
* or equal to the {@link KNOWN_MINIMUM known minimum version}.
*
* This version should be the base for the `peerDependencies.xstate` range in
* `package.json`.
*
* Any version newer than the known minimum which fails the test suite will be
* reported, and this script will fail with a non-zero exit code.
*
* Upon completion, the minimum version that passed the test will be logged to
* `STDOUT`.
*
* @packageDocumentation
*/
// zx doesn't dispose its signal listeners, apparently
setMaxListeners(30);

/**
* The known minimum version of `xstate` that works with `xstate-audition`.
*/
const KNOWN_MINIMUM = '5.14.0';

/**
* All versions of `xstate` available on npm
*/
const allVersions = await $`npm show xstate versions --json`.json<string[]>();

/**
* All versions newer than the known minimum (inclusive)
*
* Versions must be `SemVer` versions and not naughty things instead
*/
const versionsUnderTest = allVersions
.slice(allVersions.indexOf(KNOWN_MINIMUM))
.filter((version) => parse(version) !== null);

/**
* The minimum version that passed the test suite
*/
let foundMinimum: string = '';

// We abort this signal upon SIGINT
const ac = new AbortController();

const {signal} = ac;

process
// if we got CTRL-C, then we need to restore the xstate version
.once('SIGINT', (signal) => {
ac.abort();
process.stderr.write('\nAborted; restoring xstate version …');
void $.sync({quiet: true})`npm install --force`;
process.stderr.write(' done\n');
process.emit('SIGINT', signal);
})
// and we need to restore it before exit as well
.once('beforeExit', () => {
process.stderr.write('\nRestoring xstate version …');
void $.sync({quiet: true})`npm install --force`;
process.stderr.write(' done\n');
});

const unexpectedFailures: string[] = [];

// unfortunately this must run in serial
for (const version of versionsUnderTest) {
if (signal.aborted) {
continue;
}
try {
await $({signal})`npm i xstate@${version} --no-save`;
} catch (err) {
if (signal.aborted) {
continue;
}
console.error(`xstate@${version} - uninstallable`, err);
continue;
}
try {
process.stderr.write(`xstate@${version} …`);
await $({signal})`npm test`;
foundMinimum ||= version;
process.stderr.write(` OK\n`);
} catch (err) {
if (signal.aborted) {
continue;
}
process.stderr.write(` NOT OK\n`);
console.error(err);
if (foundMinimum) {
unexpectedFailures.push(version);
}
}
}

if (!signal.aborted) {
if (unexpectedFailures.length) {
console.error('Unexpected failures:', unexpectedFailures.join(', '));
process.exitCode = 1;
}
if (foundMinimum) {
console.log(foundMinimum);
} else {
console.error('No passing versions found!');
process.exitCode = 1;
}
}

0 comments on commit 273920a

Please sign in to comment.