diff --git a/package-lock.json b/package-lock.json index eca6f691aff..c63d37d4d06 100644 --- a/package-lock.json +++ b/package-lock.json @@ -143,17 +143,13 @@ "cheerio": "1.0.0", "eslint-plugin-sort-destructure-keys": "2.0.0", "eslint-plugin-workspace": "file:./tools/lint-rules", - "fast-glob": "3.3.3", "form-data": "4.0.1", "fs-extra": "11.2.0", - "graphviz": "0.0.9", "husky": "8.0.3", "is-ci": "3.0.1", - "mock-fs": "5.4.1", "nock": "13.5.6", "p-timeout": "6.1.4", "serialize-javascript": "6.0.2", - "sinon": "18.0.1", "strip-ansi": "7.1.0", "temp-dir": "3.0.0", "tree-kill": "1.2.2", @@ -5473,50 +5469,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "11.2.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz", - "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^3.0.0" - } - }, - "node_modules/@sinonjs/samsam": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz", - "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^2.0.0", - "lodash.get": "^4.4.2", - "type-detect": "^4.0.8" - } - }, - "node_modules/@sinonjs/samsam/node_modules/@sinonjs/commons": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", - "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/text-encoding": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", - "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", - "dev": true - }, "node_modules/@szmarczak/http-timer": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", @@ -10514,15 +10466,6 @@ "node": ">=14.17" } }, - "node_modules/diff": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", - "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/diff-sequences": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", @@ -13527,18 +13470,6 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" }, - "node_modules/graphviz": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/graphviz/-/graphviz-0.0.9.tgz", - "integrity": "sha512-SmoY2pOtcikmMCqCSy2NO1YsRfu9OO0wpTlOYW++giGjfX1a6gax/m1Fo8IdUd0/3H15cTOfR1SMKwohj4LKsg==", - "dev": true, - "dependencies": { - "temp": "~0.4.0" - }, - "engines": { - "node": ">=0.6.8" - } - }, "node_modules/gunzip-maybe": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/gunzip-maybe/-/gunzip-maybe-1.4.2.tgz", @@ -15438,12 +15369,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/just-extend": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", - "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", - "dev": true - }, "node_modules/jwa": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", @@ -16773,15 +16698,6 @@ "ufo": "^1.3.2" } }, - "node_modules/mock-fs": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-5.4.1.tgz", - "integrity": "sha512-sz/Q8K1gXXXHR+qr0GZg2ysxCRr323kuN10O7CtQjraJsFDJ4SJ+0I5MzALz7aRp9lHk8Cc/YdsT95h9Ka1aFw==", - "dev": true, - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/module-definition": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/module-definition/-/module-definition-5.0.1.tgz", @@ -16967,25 +16883,6 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, - "node_modules/nise": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/nise/-/nise-6.0.0.tgz", - "integrity": "sha512-K8ePqo9BFvN31HXwEtTNGzgrPpmvgciDsFz8aztFjt4LqKO/JeFD8tBOeuDiCMXrIl/m1YvfH8auSpxfaD09wg==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^3.0.0", - "@sinonjs/fake-timers": "^11.2.2", - "@sinonjs/text-encoding": "^0.7.2", - "just-extend": "^6.2.0", - "path-to-regexp": "^6.2.1" - } - }, - "node_modules/nise/node_modules/path-to-regexp": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", - "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", - "dev": true - }, "node_modules/nock": { "version": "13.5.6", "resolved": "https://registry.npmjs.org/nock/-/nock-13.5.6.tgz", @@ -19963,36 +19860,6 @@ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" }, - "node_modules/sinon": { - "version": "18.0.1", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-18.0.1.tgz", - "integrity": "sha512-a2N2TDY1uGviajJ6r4D1CyRAkzE9NNVlYOV1wX5xQDuAk0ONgzgRl0EjCQuRCPxOwp13ghsMwt9Gdldujs39qw==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^3.0.1", - "@sinonjs/fake-timers": "11.2.2", - "@sinonjs/samsam": "^8.0.0", - "diff": "^5.2.0", - "nise": "^6.0.0", - "supports-color": "^7" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/sinon" - } - }, - "node_modules/sinon/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -20660,15 +20527,6 @@ "node": ">=10" } }, - "node_modules/temp": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/temp/-/temp-0.4.0.tgz", - "integrity": "sha1-ZxrWPVe+D+nXKUZks/xABjZnimA=", - "dev": true, - "engines": [ - "node >=0.4.0" - ] - }, "node_modules/temp-dir": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-3.0.0.tgz", @@ -26688,52 +26546,6 @@ } } }, - "@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, - "@sinonjs/fake-timers": { - "version": "11.2.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz", - "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==", - "dev": true, - "requires": { - "@sinonjs/commons": "^3.0.0" - } - }, - "@sinonjs/samsam": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz", - "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==", - "dev": true, - "requires": { - "@sinonjs/commons": "^2.0.0", - "lodash.get": "^4.4.2", - "type-detect": "^4.0.8" - }, - "dependencies": { - "@sinonjs/commons": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", - "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - } - } - }, - "@sinonjs/text-encoding": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", - "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", - "dev": true - }, "@szmarczak/http-timer": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", @@ -30284,12 +30096,6 @@ } } }, - "diff": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", - "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", - "dev": true - }, "diff-sequences": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", @@ -32494,15 +32300,6 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" }, - "graphviz": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/graphviz/-/graphviz-0.0.9.tgz", - "integrity": "sha512-SmoY2pOtcikmMCqCSy2NO1YsRfu9OO0wpTlOYW++giGjfX1a6gax/m1Fo8IdUd0/3H15cTOfR1SMKwohj4LKsg==", - "dev": true, - "requires": { - "temp": "~0.4.0" - } - }, "gunzip-maybe": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/gunzip-maybe/-/gunzip-maybe-1.4.2.tgz", @@ -33786,12 +33583,6 @@ "resolved": "https://registry.npmjs.org/junk/-/junk-4.0.1.tgz", "integrity": "sha512-Qush0uP+G8ZScpGMZvHUiRfI0YBWuB3gVBYlI0v0vvOJt5FLicco+IkP0a50LqTTQhmts/m6tP5SWE+USyIvcQ==" }, - "just-extend": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", - "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", - "dev": true - }, "jwa": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", @@ -34793,12 +34584,6 @@ "ufo": "^1.3.2" } }, - "mock-fs": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-5.4.1.tgz", - "integrity": "sha512-sz/Q8K1gXXXHR+qr0GZg2ysxCRr323kuN10O7CtQjraJsFDJ4SJ+0I5MzALz7aRp9lHk8Cc/YdsT95h9Ka1aFw==", - "dev": true - }, "module-definition": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/module-definition/-/module-definition-5.0.1.tgz", @@ -34933,27 +34718,6 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, - "nise": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/nise/-/nise-6.0.0.tgz", - "integrity": "sha512-K8ePqo9BFvN31HXwEtTNGzgrPpmvgciDsFz8aztFjt4LqKO/JeFD8tBOeuDiCMXrIl/m1YvfH8auSpxfaD09wg==", - "dev": true, - "requires": { - "@sinonjs/commons": "^3.0.0", - "@sinonjs/fake-timers": "^11.2.2", - "@sinonjs/text-encoding": "^0.7.2", - "just-extend": "^6.2.0", - "path-to-regexp": "^6.2.1" - }, - "dependencies": { - "path-to-regexp": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", - "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", - "dev": true - } - } - }, "nock": { "version": "13.5.6", "resolved": "https://registry.npmjs.org/nock/-/nock-13.5.6.tgz", @@ -37087,31 +36851,6 @@ } } }, - "sinon": { - "version": "18.0.1", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-18.0.1.tgz", - "integrity": "sha512-a2N2TDY1uGviajJ6r4D1CyRAkzE9NNVlYOV1wX5xQDuAk0ONgzgRl0EjCQuRCPxOwp13ghsMwt9Gdldujs39qw==", - "dev": true, - "requires": { - "@sinonjs/commons": "^3.0.1", - "@sinonjs/fake-timers": "11.2.2", - "@sinonjs/samsam": "^8.0.0", - "diff": "^5.2.0", - "nise": "^6.0.0", - "supports-color": "^7" - }, - "dependencies": { - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -37628,12 +37367,6 @@ "streamx": "^2.15.0" } }, - "temp": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/temp/-/temp-0.4.0.tgz", - "integrity": "sha1-ZxrWPVe+D+nXKUZks/xABjZnimA=", - "dev": true - }, "temp-dir": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-3.0.0.tgz", diff --git a/package.json b/package.json index 7c68b999065..947eecc38d5 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,6 @@ "test:dev:vitest": "vitest run tests/unit/ && vitest run tests/integration", "test:ci:vitest:unit": "vitest run --coverage tests/unit/", "test:ci:vitest:integration": "vitest run --coverage tests/integration/", - "test:affected": "node ./tools/affected-test.js", "e2e": "node ./tools/e2e/run.js", "docs": "node ./site/scripts/docs.mjs", "watch": "c8 --reporter=lcov vitest --watch", @@ -200,17 +199,13 @@ "cheerio": "1.0.0", "eslint-plugin-sort-destructure-keys": "2.0.0", "eslint-plugin-workspace": "file:./tools/lint-rules", - "fast-glob": "3.3.3", "form-data": "4.0.1", "fs-extra": "11.2.0", - "graphviz": "0.0.9", "husky": "8.0.3", "is-ci": "3.0.1", - "mock-fs": "5.4.1", "nock": "13.5.6", "p-timeout": "6.1.4", "serialize-javascript": "6.0.2", - "sinon": "18.0.1", "strip-ansi": "7.1.0", "temp-dir": "3.0.0", "tree-kill": "1.2.2", diff --git a/tools/affected-test.js b/tools/affected-test.js deleted file mode 100755 index a0cd5902c97..00000000000 --- a/tools/affected-test.js +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/env node - -import { existsSync, statSync } from 'fs' -import { join } from 'path' -import process from 'process' -import { fileURLToPath } from 'url' - -import chalk from 'chalk' -import execa from 'execa' -import glob from 'fast-glob' - -import { DependencyGraph, fileVisitor, visitorPlugins } from './project-graph/index.js' - -export const TEST_MATCHING_GLOB = /\.test\.cjs$/gm - -export const getChangedFiles = async (compareTarget = 'origin/main') => { - const { stdout } = await execa('git', ['diff', '--name-only', 'HEAD', compareTarget]) - // git is using posix paths so adjust them to the operating system by - // using nodes join function - return stdout.split('\n').map((filePath) => join(filePath)) -} - -/** - * Get the list of affected files - if some files are touched like the package.json - * everything is affected. - * @param {string[]} changedFiles - * @returns {string[]} - */ -export const getAffectedFiles = (changedFiles) => { - // glob is using only posix file paths on windows we need the `\` - // by using join the paths are adjusted to the operating system - const testFiles = glob.sync(['tests/integration/**/*.test.cjs']).map((filePath) => join(filePath)) - - // in this case all files are affected - if ( - changedFiles.includes('package-lock.json') || - changedFiles.includes('package.json') || - changedFiles.includes(join('.github', 'workflows', 'main.yml')) - ) { - console.log('All files are affected based on the changeset') - return testFiles - } - - const graph = new DependencyGraph() - - testFiles.forEach((file) => { - fileVisitor(file, { graph, visitorPlugins }) - }) - - return [...graph.affected(changedFiles, (file) => file.match(TEST_MATCHING_GLOB) !== null)] -} - -/** - * The main function - * @param {string[]} args - */ -const main = async (args) => { - const changedFiles = - args[0] && existsSync(args[0]) - ? args.filter((arg) => existsSync(arg) && statSync(arg).isFile()) - : await getChangedFiles(args[0]) - - const affectedFiles = getAffectedFiles(changedFiles) - - if (affectedFiles.length === 0) { - console.log('No files where affected by the changeset!') - return - } - console.log(`Running affected Tests: \n${chalk.grey([...affectedFiles].join(', '))}`) - const testRun = execa('c8', ['-r', 'json', 'vitest', ...affectedFiles], { - stdio: 'inherit', - preferLocal: true, - }) - - process.on('exit', () => { - testRun.kill() - }) - - try { - await testRun - } catch (error) { - if (error instanceof Error) { - console.log(error.message) - process.exit(1) - } - throw error - } -} - -// Can be invoked with two different arguments: -// Either a list of files where all affected tests should be calculated based on: -// $ npm run test:affected -- ./path/to/file.js ./other-file.js -// -// or by specifying a git diff target -// $ npm run test:affected -- HEAD~1 -// -// The default is when running without arguments a git diff target off 'origin/master' -if (process.argv[1] === fileURLToPath(import.meta.url)) { - const args = process.argv.slice(2) - // eslint-disable-next-line promise/prefer-await-to-callbacks,promise/prefer-await-to-then - main(args).catch((error) => { - console.error(error) - process.exit(1) - }) -} diff --git a/tools/project-graph/dependency-graph.js b/tools/project-graph/dependency-graph.js deleted file mode 100644 index 89c80390941..00000000000 --- a/tools/project-graph/dependency-graph.js +++ /dev/null @@ -1,80 +0,0 @@ -import { digraph } from 'graphviz' - -export class DependencyGraph { - /** @type {Map>} */ - graph = new Map() - - hasFile(fileName) { - return this.graph.has(fileName) - } - - /** - * Adds a node to the graph - * @param {string} fileName - * @returns {void} - */ - addFile(fileName) { - if (!this.graph.has(fileName)) { - this.graph.set(fileName, new Set()) - } - } - - addDependency(parent, child) { - if (!this.graph.has(parent)) { - this.addFile(parent) - } - if (!this.graph.has(child)) { - this.addFile(child) - } - - this.graph.set(parent, this.graph.get(parent).add(child)) - } - - /** - * Provide a list of all affected files inside the graph based on the provided files - * @param {string[]} files - * @param {(file:string) => boolean} filterFunction - * @returns {Set} - */ - affected(files, filterFunction) { - const affectedFiles = new Set() - - const findParents = (leaf) => { - if (((filterFunction && filterFunction(leaf)) || !filterFunction) && this.graph.has(leaf)) { - affectedFiles.add(leaf) - } - - this.graph.forEach((value, key) => { - if (value.has(leaf)) { - if ((filterFunction && filterFunction(leaf)) || !filterFunction) { - affectedFiles.add(key) - } - findParents(key) - } - }) - } - - files.forEach((file) => { - findParents(file) - }) - - return affectedFiles - } - - /** - * Visualizes a dependency graph the output is a graphviz graph - * that can be printed to `.to_dot()` or rendered to a png file - * @returns {import('graphviz').Graph} - */ - visualize() { - const graph = digraph('G') - this.graph.forEach((edges, node) => { - graph.addNode(node) - edges.forEach((edge) => { - graph.addEdge(node, edge) - }) - }) - - return graph - } -} diff --git a/tools/project-graph/file-visitor.js b/tools/project-graph/file-visitor.js deleted file mode 100644 index 59c0bcd6dad..00000000000 --- a/tools/project-graph/file-visitor.js +++ /dev/null @@ -1,147 +0,0 @@ -import { existsSync, readFileSync, statSync } from 'fs' -import { dirname, join, parse } from 'path' - -import ts from 'typescript' - -import { DependencyGraph } from './dependency-graph.js' - -/** - * tries to resolve a relative javascript module based on its specifier - * @param {string} moduleSpecifier - * @returns {(string|null)} - */ -export const resolveRelativeModule = (moduleSpecifier) => { - if (existsSync(moduleSpecifier) && statSync(moduleSpecifier).isFile()) { - return moduleSpecifier - } - if (existsSync(`${moduleSpecifier}.js`)) { - return `${moduleSpecifier}.js` - } - if (existsSync(`${moduleSpecifier}/index.js`)) { - return `${moduleSpecifier}/index.js` - } - if (existsSync(`${moduleSpecifier}.cjs`)) { - return `${moduleSpecifier}.cjs` - } - if (existsSync(`${moduleSpecifier}/index.cjs`)) { - return `${moduleSpecifier}/index.cjs` - } - if (existsSync(`${moduleSpecifier}.js`)) { - return `${moduleSpecifier}.js` - } - if (existsSync(`${moduleSpecifier}/index.js`)) { - return `${moduleSpecifier}/index.js` - } - if (existsSync(`${moduleSpecifier}.ts`)) { - return `${moduleSpecifier}.ts` - } - if (existsSync(`${moduleSpecifier}/index.ts`)) { - return `${moduleSpecifier}/index.ts` - } - if (existsSync(`${moduleSpecifier}.cts`)) { - return `${moduleSpecifier}.cts` - } - if (existsSync(`${moduleSpecifier}/index.cts`)) { - return `${moduleSpecifier}/index.cts` - } - if (existsSync(`${moduleSpecifier}.mts`)) { - return `${moduleSpecifier}.mts` - } - if (existsSync(`${moduleSpecifier}/index.mts`)) { - return `${moduleSpecifier}/index.mts` - } - - return null -} - -/** - * Parses the dependencies out of a file - * @param {string} fileName - * @param {import('./types.d').VisitorState} state - * @param {any} parent - */ -export const fileVisitor = function (fileName, state, parent) { - if (!state) { - state = { graph: new DependencyGraph(), visitorPlugins: [] } - } - - if (state.graph.hasFile(fileName)) { - // if the visitor was called with a parent we only need to add the dependency - if (parent) { - state.graph.addDependency(parent, fileName) - } - // no need to traverse the file again - return - } - - const folder = dirname(fileName) - const fileContent = readFileSync(fileName, 'utf-8') - const sourceFile = ts.createSourceFile(fileName, fileContent, ts.ScriptTarget.ES2020, true, ts.ScriptKind.JS) - - /** - * Resolves a javascript import location - * @param {string} importLocation - * @returns {(string|null)} - */ - const resolveLocation = function (importLocation) { - const parsed = parse(importLocation) - // absolute paths don't need to be resolved - if (parsed.root) { - return importLocation - } - if (importLocation.startsWith('.')) { - return resolveRelativeModule(join(folder, importLocation)) - } - // TODO: Ignored node_modules for now maybe add them later as they might be useful - } - - /** - * Visits a import or require statement location - * @param {string} moduleSpecifier - */ - const visitDependency = (moduleSpecifier) => { - if (moduleSpecifier.startsWith('.')) { - const resolvedImportLocation = resolveLocation(moduleSpecifier) - - if (resolvedImportLocation) { - fileVisitor(resolvedImportLocation, state, fileName) - } - } - } - - /** - * Visits a typescript node - * @type {ts.Visitor} - */ - const visitor = function (node) { - // TODO: once we need import specifiers (esm or typescript add them here) - if (ts.isCallExpression(node) && node.expression.getText() === 'require' && ts.isStringLiteral(node.arguments[0])) { - visitDependency(node.arguments[0].text) - } - - if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) { - visitDependency(node.moduleSpecifier.text) - } - - // go to the plugins - state.visitorPlugins.forEach((plugin) => { - const file = plugin(node) - if (file) { - fileVisitor(file, state, fileName) - } - }) - - node.getChildren().forEach((childNode) => { - ts.visitNode(childNode, visitor) - }) - } - // start visiting the sourceFile - ts.visitNode(sourceFile, visitor) - - // add node to graph - state.graph.addFile(fileName) - - if (parent) { - state.graph.addDependency(parent, fileName) - } -} diff --git a/tools/project-graph/index.js b/tools/project-graph/index.js deleted file mode 100644 index c5b1f609f15..00000000000 --- a/tools/project-graph/index.js +++ /dev/null @@ -1,3 +0,0 @@ -export { DependencyGraph } from './dependency-graph.js' -export { fileVisitor } from './file-visitor.js' -export * from './visitor-plugins.js' diff --git a/tools/project-graph/types.d.ts b/tools/project-graph/types.d.ts deleted file mode 100644 index 8738aa4015d..00000000000 --- a/tools/project-graph/types.d.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { Node } from 'typescript' - -import type { DependencyGraph } from './dependency-graph.js' - -export type Dependency = { - fileName: string - /** A list of imported identifiers from this dependency */ - identifiers: string[] - type: 'direct' | 'indirect' - dependencies: Dependency[] -} - -export type visitorPlugin = (node: Node) => string | undefined - -export type VisitorState = { - graph: DependencyGraph - visitorPlugins: visitorPlugin[] -} diff --git a/tools/project-graph/visitor-plugins.js b/tools/project-graph/visitor-plugins.js deleted file mode 100644 index 5d8ec75001e..00000000000 --- a/tools/project-graph/visitor-plugins.js +++ /dev/null @@ -1,41 +0,0 @@ -import { join } from 'path' - -import ts from 'typescript' - -import { resolveRelativeModule } from './file-visitor.js' - -const COMMANDS = 'src/commands' - -/** @type {import('./types').visitorPlugin[]} */ -export const visitorPlugins = [ - (node) => { - // check if `await execa(cliPath, ['build', ...flags], {` is used for the command - if ( - ts.isCallExpression(node) && - ts.isIdentifier(node.expression) && - node.expression.text === 'execa' && - ts.isIdentifier(node.arguments[0]) && - node.arguments[0].text === 'cliPath' && - ts.isArrayLiteralExpression(node.arguments[1]) && - ts.isStringLiteral(node.arguments[1].elements[0]) - ) { - return resolveRelativeModule(join(COMMANDS, node.arguments[1].elements[0].text)) - } - }, - (node) => { - // check if `await callCli(['api', 'listSites'], getCLIOptions(apiUrl))` - if ( - ts.isCallExpression(node) && - ts.isIdentifier(node.expression) && - node.expression.text === 'callCli' && - ts.isArrayLiteralExpression(node.arguments[0]) && - ts.isStringLiteral(node.arguments[0].elements[0]) - ) { - const [argument] = node.arguments[0].elements[0].text.split(':') - - if (!argument.startsWith('-')) { - return resolveRelativeModule(join(COMMANDS, node.arguments[0].elements[0].text)) - } - } - }, -] diff --git a/tools/tests/affected-files.test.js b/tools/tests/affected-files.test.js deleted file mode 100644 index f781d7b59b0..00000000000 --- a/tools/tests/affected-files.test.js +++ /dev/null @@ -1,74 +0,0 @@ -import { join } from 'path' - -import { test } from 'vitest' -import glob from 'fast-glob' -import mock from 'mock-fs' -import { stub, createSandbox } from 'sinon' - -import { simpleMockedFileSystem } from './utils/file-systems.js' -import { getAffectedFiles } from '../affected-test.js' - -/** - * Get a list of affected files for a mocked file system - * @param {string[]} changedFiles The list of changed files - * @param {Record} fileSystem The mocked file system - * @returns Returns a list of affected files - */ -const getAffectedFilesFromMock = async (changedFiles, fileSystem = simpleMockedFileSystem) => { - const mockedTestFiles = Object.keys(fileSystem).filter((file) => file.match(/\.test\.m?js$/gm)) - const globStub = stub(glob, 'sync').returns(mockedTestFiles) - - mock(fileSystem) - - const affectedFiles = getAffectedFiles(changedFiles) - - mock.restore() - globStub.restore() - - return { affectedFiles, mockedTestFiles } -} - -test.beforeEach((t) => { - t.context.sandbox = createSandbox() -}) - -test.afterEach((t) => { - t.context.sandbox.restore() -}) - -test('should get all files marked as affected when the package.json is touched', async (t) => { - const consoleStub = t.context.sandbox.stub(console, 'log').callsFake(() => {}) - const { affectedFiles, mockedTestFiles } = await getAffectedFilesFromMock(['package.json']) - - t.truthy(consoleStub.firstCall.calledWith('All files are affected based on the changeset')) - t.deepEqual(affectedFiles, mockedTestFiles) -}) - -test.serial('should get all files marked as affected when the package-lock.json is touched', async (t) => { - const consoleStub = t.context.sandbox.stub(console, 'log').callsFake(() => {}) - const { affectedFiles, mockedTestFiles } = await getAffectedFilesFromMock(['package-lock.json']) - - t.truthy(consoleStub.firstCall.calledWith('All files are affected based on the changeset')) - t.deepEqual(affectedFiles, mockedTestFiles) -}) - -test.serial('should get all files marked as affected when a leaf is touched that both tests depend on', async (t) => { - const consoleStub = stub(console, 'log').callsFake(() => {}) - const { affectedFiles, mockedTestFiles } = await getAffectedFilesFromMock([join('src/d.js')]) - - t.truthy(consoleStub.notCalled) - t.deepEqual(affectedFiles, mockedTestFiles) - consoleStub.restore() -}) - -test.serial('should only one test affected if a file for it was called', async (t) => { - const { affectedFiles } = await getAffectedFilesFromMock([join('src/nested/b.js')]) - - t.deepEqual(affectedFiles, [join('tests/a.test.js')]) -}) - -test.serial('should not have any file affected if a different file like a readme was affected', async (t) => { - const { affectedFiles } = await getAffectedFilesFromMock(['README.md']) - - t.is(affectedFiles.length, 0) -}) diff --git a/tools/tests/dependency-graph.test.js b/tools/tests/dependency-graph.test.js deleted file mode 100644 index edc4d3bb124..00000000000 --- a/tools/tests/dependency-graph.test.js +++ /dev/null @@ -1,31 +0,0 @@ -import { test } from 'vitest' - -import { DependencyGraph } from '../project-graph/index.js' - -/** @type {DependencyGraph} */ -let graph - -test.beforeEach(() => { - graph = new DependencyGraph() - graph.addDependency('tests/a.js', 'src/nested/a.js') - graph.addDependency('tests/c.js', 'src/c/index.js') - graph.addDependency('tests/c.js', 'tests/utils.js') - graph.addDependency('src/nested/a.js', 'src/nested/b.js') - graph.addDependency('src/nested/a.js', 'src/c/index.js') - graph.addDependency('src/c/index.js', 'src/d.js') -}) - -test('should test if all parents are affected by changing a src file on the bottom', (t) => { - t.deepEqual( - graph.affected(['src/d.js']), - new Set(['src/d.js', 'src/c/index.js', 'src/nested/a.js', 'tests/a.js', 'tests/c.js']), - ) -}) - -test('should test only the root leaf is affected if the root one is passed', (t) => { - t.deepEqual([...graph.affected(['tests/a.js'])], ['tests/a.js']) -}) - -test('should test that nothing is affected if the passed file is not in the dependency graph', (t) => { - t.is(graph.affected(['some-markdown.md']).size, 0) -}) diff --git a/tools/tests/file-visitor-module.test.js b/tools/tests/file-visitor-module.test.js deleted file mode 100644 index 04972685925..00000000000 --- a/tools/tests/file-visitor-module.test.js +++ /dev/null @@ -1,82 +0,0 @@ -import { join } from 'path' -import { format } from 'util' - -import { test } from 'vitest' -import mock, { restore } from 'mock-fs' - -import snapshots from '../../tests/integration/utils/snapshots.js' -import { DependencyGraph, fileVisitor } from '../project-graph/index.js' - -import { esModuleMockedFileSystem } from './utils/file-systems.js' - -test.before(() => { - mock(esModuleMockedFileSystem) -}) - -test.after(() => { - restore() -}) - -test('should visit the files that are dependents from the provided main file based on imports', (t) => { - const graph = new DependencyGraph() - fileVisitor(join('tests/a.test.js'), { graph, visitorPlugins: [] }) - t.is( - snapshots.normalize(graph.visualize().to_dot()), - `digraph G { - "src/nested/b.js"; - "src/nested/a.js"; - "src/c/index.js"; - "src/d.js"; - "tests/a.test.js"; - "src/nested/a.js" -> "src/nested/b.js"; - "src/nested/a.js" -> "src/c/index.js"; - "src/c/index.js" -> "src/d.js"; - "tests/a.test.js" -> "src/nested/a.js"; -}`, - ) -}) - -test('should merge the graph with files from a different entry point based on imports', (t) => { - const graph = new DependencyGraph() - fileVisitor(join('tests/a.test.js'), { graph, visitorPlugins: [] }) - fileVisitor(join('tests/c.test.js'), { graph, visitorPlugins: [] }) - t.is( - snapshots.normalize(graph.visualize().to_dot()), - `digraph G { - "src/nested/b.js"; - "src/nested/a.js"; - "src/c/index.js"; - "src/d.js"; - "tests/a.test.js"; - "tests/c.test.js"; - "tests/utils.js"; - "src/nested/a.js" -> "src/nested/b.js"; - "src/nested/a.js" -> "src/c/index.js"; - "src/c/index.js" -> "src/d.js"; - "tests/a.test.js" -> "src/nested/a.js"; - "tests/c.test.js" -> "src/c/index.js"; - "tests/c.test.js" -> "tests/utils.js"; -}`, - ) -}) - -test('should build a list of affected files based on a file with imports', (t) => { - const graph = new DependencyGraph() - fileVisitor(join('tests/a.test.js'), { graph, visitorPlugins: [] }) - fileVisitor(join('tests/c.test.js'), { graph, visitorPlugins: [] }) - - t.is( - format([...graph.affected([join('src/d.js')])]).replace(/\\+/gm, '/'), - `[ - 'src/d.js', - 'src/c/index.js', - 'src/nested/a.js', - 'tests/a.test.js', - 'tests/c.test.js' -]`, - ) - t.is( - format([...graph.affected([join('tests/utils.js')])]).replace(/\\+/gm, '/'), - "[ 'tests/utils.js', 'tests/c.test.js' ]", - ) -}) diff --git a/tools/tests/file-visitor.test.js b/tools/tests/file-visitor.test.js deleted file mode 100644 index 27d62e666b0..00000000000 --- a/tools/tests/file-visitor.test.js +++ /dev/null @@ -1,82 +0,0 @@ -import { join } from 'path' -import { format } from 'util' - -import { test } from 'vitest' -import mock, { restore } from 'mock-fs' - -import snapshots from '../../tests/integration/utils/snapshots.js' -import { DependencyGraph, fileVisitor } from '../project-graph/index.js' - -import { simpleMockedFileSystem } from './utils/file-systems.js' - -test.before(() => { - mock(simpleMockedFileSystem) -}) - -test.after(() => { - restore() -}) - -test('should visit the files that are dependents from the provided main file', (t) => { - const graph = new DependencyGraph() - fileVisitor(join('tests/a.test.js'), { graph, visitorPlugins: [] }) - t.is( - snapshots.normalize(graph.visualize().to_dot()), - `digraph G { - "src/nested/b.js"; - "src/nested/a.js"; - "src/c/index.js"; - "src/d.js"; - "tests/a.test.js"; - "src/nested/a.js" -> "src/nested/b.js"; - "src/nested/a.js" -> "src/c/index.js"; - "src/c/index.js" -> "src/d.js"; - "tests/a.test.js" -> "src/nested/a.js"; -}`, - ) -}) - -test('should merge the graph with files from a different entry point', (t) => { - const graph = new DependencyGraph() - fileVisitor(join('tests/a.test.js'), { graph, visitorPlugins: [] }) - fileVisitor(join('tests/c.test.js'), { graph, visitorPlugins: [] }) - t.is( - snapshots.normalize(graph.visualize().to_dot()), - `digraph G { - "src/nested/b.js"; - "src/nested/a.js"; - "src/c/index.js"; - "src/d.js"; - "tests/a.test.js"; - "tests/c.test.js"; - "tests/utils.js"; - "src/nested/a.js" -> "src/nested/b.js"; - "src/nested/a.js" -> "src/c/index.js"; - "src/c/index.js" -> "src/d.js"; - "tests/a.test.js" -> "src/nested/a.js"; - "tests/c.test.js" -> "src/c/index.js"; - "tests/c.test.js" -> "tests/utils.js"; -}`, - ) -}) - -test('should build a list of affected files based on a file', (t) => { - const graph = new DependencyGraph() - fileVisitor(join('tests/a.test.js'), { graph, visitorPlugins: [] }) - fileVisitor(join('tests/c.test.js'), { graph, visitorPlugins: [] }) - - t.is( - format([...graph.affected([join('src/d.js')])]).replace(/\\+/gm, '/'), - `[ - 'src/d.js', - 'src/c/index.js', - 'src/nested/a.js', - 'tests/a.test.js', - 'tests/c.test.js' -]`, - ) - t.is( - format([...graph.affected([join('tests/utils.js')])]).replace(/\\+/gm, '/'), - "[ 'tests/utils.js', 'tests/c.test.js' ]", - ) -}) diff --git a/tools/tests/utils/file-systems.js b/tools/tests/utils/file-systems.js deleted file mode 100644 index 9d3fb49f2f3..00000000000 --- a/tools/tests/utils/file-systems.js +++ /dev/null @@ -1,37 +0,0 @@ -import { join } from 'path' - -const baseFiles = { - 'package-lock.json': '', - 'README.md': '', -} - -export const simpleMockedFileSystem = { - [join('src/nested/a.js')]: `const b = require('./b');const asdf = require('asdf'); const {c} = require('../c');`, - [join('src/nested/b.js')]: '', - [join('src/c/index.js')]: `const d = require('../d');`, - [join('src/d.js')]: '', - [join('tests/a.test.js')]: `const a = require('../src/nested/a');`, - [join('tests/c.test.js')]: `const a = require('../src/c');const u = require('./utils');`, - [join('tests/utils.js')]: '', - ...baseFiles, -} - -export const esModuleMockedFileSystem = { - [join('src/nested/a.js')]: `import b from './b';import asdf from 'asdf'; import {c} from '../c';`, - [join('src/nested/b.js')]: '', - [join('src/c/index.js')]: `import * as d from '../d';`, - [join('src/d.js')]: '', - [join('tests/a.test.js')]: `import a from '../src/nested/a';`, - [join('tests/c.test.js')]: `import a from '../src/c';import u from './utils';`, - [join('tests/utils.js')]: '', - ...baseFiles, -} - -export const callCliMockedFileSystem = { - [join('src/commands/dev.js')]: `const {c} = require('../utils/c');`, - [join('src/commands/build/index.js')]: `const {c} = require('../../utils/c');`, - [join('src/utils/c.js')]: '', - [join('tests/a.test.js')]: ` `, - [join('tests/c.test.js')]: ` `, - ...baseFiles, -}