diff --git a/package.json b/package.json index 289b6fb7c66..37db2caeeae 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "test:dev:ava": "ava --verbose", "test:ci:ava:unit": "c8 -r json ava --no-worker-threads tests/unit/**/*.test.js tools/**/*.test.js", "test:ci:ava:integration": "c8 -r json ava --concurrency 1 --no-worker-threads tests/integration/**/*.test.js", - "test:affected": "node ./tools/affected-test.js", + "test:affected": "node ./tools/affected-test.mjs", "e2e": "node ./tools/e2e/run.mjs", "docs": "node ./site/scripts/docs.mjs", "watch": "c8 --reporter=lcov ava --watch", @@ -73,7 +73,7 @@ "postinstall": "node ./scripts/postinstall.js" }, "config": { - "eslint": "--ignore-path .gitignore --cache --format=codeframe --max-warnings=0 \"{src,tools,scripts,site,tests,.github}/**/*.{mjs,cjs,js,md,html}\" \"*.{mjs,cjs,js,md,html}\" \".*.{mjs,cjs,js,md,html}\"", + "eslint": "--ignore-path .gitignore --cache --format=codeframe --max-warnings=0 \"{src,scripts,site,tests,.github}/**/*.{mjs,cjs,js,md,html}\" \"*.{mjs,cjs,js,md,html}\" \".*.{mjs,cjs,js,md,html}\"", "prettier": "--ignore-path .gitignore --loglevel=warn \"{src,tools,scripts,site,tests,.github}/**/*.{mjs,cjs,js,md,yml,json,html}\" \"*.{mjs,cjs,js,yml,json,html}\" \".*.{mjs,cjs,js,yml,json,html}\" \"!CHANGELOG.md\" \"!npm-shrinkwrap.json\" \"!.github/**/*.md\"" }, "dependencies": { @@ -215,7 +215,7 @@ }, "ava": { "files": [ - "tools/**/*.test.js", + "tools/**/*.test.mjs", "tests/**/*.test.js" ], "cache": true, diff --git a/tools/affected-test.js b/tools/affected-test.mjs similarity index 71% rename from tools/affected-test.js rename to tools/affected-test.mjs index 40afa4d0920..ff181e8bbf2 100755 --- a/tools/affected-test.js +++ b/tools/affected-test.mjs @@ -1,16 +1,20 @@ #!/usr/bin/env node // @ts-check -const { existsSync, statSync } = require('fs') -const { join } = require('path') -const process = require('process') +import { existsSync, readFileSync, statSync } from 'fs' +import { join } from 'path' +import process from 'process' +import { fileURLToPath } from 'url' -const { grey } = require('chalk') -const execa = require('execa') -const { sync } = require('fast-glob') +import chalk from 'chalk' +import execa from 'execa' +import glob from 'fast-glob' -const { DependencyGraph, fileVisitor, visitorPlugins } = require('./project-graph') +import { DependencyGraph, fileVisitor, visitorPlugins } from './project-graph/index.mjs' -const getChangedFiles = async (compareTarget = 'origin/main') => { +export const TEST_MATCHING_GLOB = /\.test\.m?js$/gm +const { ava } = JSON.parse(readFileSync(fileURLToPath(new URL('../package.json', import.meta.url)), 'utf-8')) + +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 @@ -23,10 +27,10 @@ const getChangedFiles = async (compareTarget = 'origin/main') => { * @param {string[]} changedFiles * @returns {string[]} */ -const getAffectedFiles = (changedFiles) => { +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 = sync(['tests/integration/**/*.test.js']).map((filePath) => join(filePath)) + const testFiles = glob.sync(['tests/integration/**/*.test.js']).map((filePath) => join(filePath)) // in this case all files are affected if ( @@ -44,7 +48,7 @@ const getAffectedFiles = (changedFiles) => { fileVisitor(file, { graph, visitorPlugins }) }) - return [...graph.affected(changedFiles, (file) => file.endsWith('.test.js'))] + return [...graph.affected(changedFiles, (file) => file.match(TEST_MATCHING_GLOB) !== null)] } /** @@ -63,7 +67,7 @@ const main = async (args) => { console.log('No files where affected by the changeset!') return } - console.log(`Running affected Tests: \n${grey([...affectedFiles].join(', '))}`) + console.log(`Running affected Tests: \n${chalk.grey([...affectedFiles].join(', '))}`) const testRun = execa('c8', ['-r', 'json', 'ava', ...affectedFiles], { stdio: 'inherit', preferLocal: true, @@ -92,7 +96,7 @@ const main = async (args) => { // $ npm run test:affected -- HEAD~1 // // The default is when running without arguments a git diff target off 'origin/master' -if (require.main === module) { +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) => { @@ -100,5 +104,3 @@ if (require.main === module) { process.exit(1) }) } - -module.exports = { getChangedFiles, getAffectedFiles } diff --git a/tools/project-graph/dependency-graph.js b/tools/project-graph/dependency-graph.mjs similarity index 90% rename from tools/project-graph/dependency-graph.js rename to tools/project-graph/dependency-graph.mjs index 9f8b347c613..2117e36ab6d 100644 --- a/tools/project-graph/dependency-graph.js +++ b/tools/project-graph/dependency-graph.mjs @@ -1,8 +1,7 @@ // @ts-check +import { digraph } from 'graphviz' -const graphviz = require('graphviz') - -class DependencyGraph { +export class DependencyGraph { /** @type {Map>} */ graph = new Map() @@ -66,10 +65,10 @@ class DependencyGraph { /** * Visualizes a dependency graph the output is a graphviz graph * that can be printed to `.to_dot()` or rendered to a png file - * @returns {graphviz.Graph} + * @returns {import('graphviz').Graph} */ visualize() { - const graph = graphviz.digraph('G') + const graph = digraph('G') this.graph.forEach((edges, node) => { graph.addNode(node) edges.forEach((edge) => { @@ -80,5 +79,3 @@ class DependencyGraph { return graph } } - -module.exports = { DependencyGraph } diff --git a/tools/project-graph/file-visitor.js b/tools/project-graph/file-visitor.mjs similarity index 87% rename from tools/project-graph/file-visitor.js rename to tools/project-graph/file-visitor.mjs index 04235aacf74..5903c6573a6 100644 --- a/tools/project-graph/file-visitor.js +++ b/tools/project-graph/file-visitor.mjs @@ -1,17 +1,17 @@ // @ts-check -const { existsSync, readFileSync, statSync } = require('fs') -const { dirname, join, parse } = require('path') +import { existsSync, readFileSync, statSync } from 'fs' +import { dirname, join, parse } from 'path' -const ts = require('typescript') +import ts from 'typescript' -const { DependencyGraph } = require('./dependency-graph') +import { DependencyGraph } from './dependency-graph.mjs' /** * tries to resolve a relative javascript module based on its specifier * @param {string} moduleSpecifier * @returns {(string|null)} */ -const resolveRelativeModule = (moduleSpecifier) => { +export const resolveRelativeModule = (moduleSpecifier) => { if (existsSync(moduleSpecifier) && statSync(moduleSpecifier).isFile()) { return moduleSpecifier } @@ -21,16 +21,17 @@ const resolveRelativeModule = (moduleSpecifier) => { if (existsSync(`${moduleSpecifier}/index.js`)) { return `${moduleSpecifier}/index.js` } + return null } /** * Parses the dependencies out of a file * @param {string} fileName - * @param {import('./types').VisitorState} state + * @param {import('./types.d').VisitorState} state * @param {any} parent */ -const fileVisitor = function (fileName, state, parent) { +export const fileVisitor = function (fileName, state, parent) { if (!state) { state = { graph: new DependencyGraph(), visitorPlugins: [] } } @@ -115,5 +116,3 @@ const fileVisitor = function (fileName, state, parent) { state.graph.addDependency(parent, fileName) } } - -module.exports = { fileVisitor, resolveRelativeModule } diff --git a/tools/project-graph/index.js b/tools/project-graph/index.js deleted file mode 100644 index 4f64a3b91d8..00000000000 --- a/tools/project-graph/index.js +++ /dev/null @@ -1,9 +0,0 @@ -const { DependencyGraph } = require('./dependency-graph') -const { fileVisitor } = require('./file-visitor') -const visitorPlugins = require('./visitor-plugins') - -module.exports = { - DependencyGraph, - fileVisitor, - visitorPlugins, -} diff --git a/tools/project-graph/index.mjs b/tools/project-graph/index.mjs new file mode 100644 index 00000000000..e658703df14 --- /dev/null +++ b/tools/project-graph/index.mjs @@ -0,0 +1,3 @@ +export { DependencyGraph } from './dependency-graph.mjs' +export { fileVisitor } from './file-visitor.mjs' +export * from './visitor-plugins.mjs' diff --git a/tools/project-graph/types.d.ts b/tools/project-graph/types.d.ts index 2ebe048569c..2964e3dad6f 100644 --- a/tools/project-graph/types.d.ts +++ b/tools/project-graph/types.d.ts @@ -1,5 +1,6 @@ import type { Node } from 'typescript'; -import type { DependencyGraph } from './dependency-graph'; + +import type { DependencyGraph } from './dependency-graph.mjs'; export type Dependency = { fileName: string @@ -14,4 +15,4 @@ export type visitorPlugin = (node: Node) => string | undefined export type VisitorState = { graph: DependencyGraph visitorPlugins: visitorPlugin[] -} \ No newline at end of file +} diff --git a/tools/project-graph/visitor-plugins.js b/tools/project-graph/visitor-plugins.mjs similarity index 88% rename from tools/project-graph/visitor-plugins.js rename to tools/project-graph/visitor-plugins.mjs index 85829cdc7f0..aded0b4663f 100644 --- a/tools/project-graph/visitor-plugins.js +++ b/tools/project-graph/visitor-plugins.mjs @@ -1,13 +1,14 @@ // @ts-check -const { join } = require('path') +import { join } from 'path' -const ts = require('typescript') +import ts from 'typescript' + +import { resolveRelativeModule } from './file-visitor.mjs' const COMMANDS = 'src/commands' -const { resolveRelativeModule } = require('./file-visitor') /** @type {import('./types').visitorPlugin[]} */ -module.exports = [ +export const visitorPlugins = [ (node) => { // check if `await execa(cliPath, ['build', ...flags], {` is used for the command if ( diff --git a/tools/tests/affected-files.test.js b/tools/tests/affected-files.test.js deleted file mode 100644 index bfe275a0e76..00000000000 --- a/tools/tests/affected-files.test.js +++ /dev/null @@ -1,60 +0,0 @@ -const { join } = require('path') - -const test = require('ava') -const glob = require('fast-glob') -const mock = require('mock-fs') -const sinon = require('sinon') - -const { simpleMockedFileSystem } = require('./utils/file-systems') - -const mockedTestFiles = Object.keys(simpleMockedFileSystem).filter((file) => file.endsWith('.test.js')) -sinon.stub(glob, 'sync').returns(mockedTestFiles) - -// eslint-disable-next-line import/order -const { getAffectedFiles } = require('../affected-test') - -test.beforeEach(() => { - mock(simpleMockedFileSystem) -}) - -test.afterEach(() => { - mock.restore() - sinon.restore() -}) - -test('should get all files marked as affected when the package.json is touched', (t) => { - const consoleStub = sinon.stub(console, 'log').callsFake(() => {}) - const affectedFiles = getAffectedFiles(['package.json']) - - t.truthy(consoleStub.firstCall.calledWith('All files are affected based on the changeset')) - t.deepEqual(affectedFiles, mockedTestFiles) - consoleStub.restore() -}) - -test('should get all files marked as affected when the npm-shrinkwrap.json is touched', (t) => { - const consoleStub = sinon.stub(console, 'log').callsFake(() => {}) - const affectedFiles = getAffectedFiles(['npm-shrinkwrap.json']) - - t.truthy(consoleStub.firstCall.calledWith('All files are affected based on the changeset')) - t.deepEqual(affectedFiles, mockedTestFiles) - consoleStub.restore() -}) - -test('should get all files marked as affected when a leaf is touched that both tests depend on', (t) => { - const consoleStub = sinon.stub(console, 'log').callsFake(() => {}) - const affectedFiles = getAffectedFiles([join('src/d.js')]) - - t.truthy(consoleStub.notCalled) - t.deepEqual(affectedFiles, mockedTestFiles) - consoleStub.restore() -}) - -test('should only one test affected if a file for it was called', (t) => { - const affectedFiles = getAffectedFiles([join('src/nested/b.js')]) - t.deepEqual(affectedFiles, [join('tests/a.test.js')]) -}) - -test('should not have any file affected if a different file like a readme was affected', (t) => { - const affectedFiles = getAffectedFiles(['README.md']) - t.is(affectedFiles.length, 0) -}) diff --git a/tools/tests/affected-files.test.mjs b/tools/tests/affected-files.test.mjs new file mode 100644 index 00000000000..3168a33333c --- /dev/null +++ b/tools/tests/affected-files.test.mjs @@ -0,0 +1,74 @@ +import { join } from 'path' + +import test from 'ava' +import glob from 'fast-glob' +import mock from 'mock-fs' +import { stub, createSandbox } from 'sinon' + +import { simpleMockedFileSystem } from './utils/file-systems.mjs' + +/** + * 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) + + const { getAffectedFiles } = await import('../affected-test.mjs') + 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.only('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 npm-shrinkwrap.json is touched', async (t) => { + const consoleStub = t.context.sandbox.stub(console, 'log').callsFake(() => {}) + const { affectedFiles, mockedTestFiles } = await getAffectedFilesFromMock(['npm-shrinkwrap.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.mjs similarity index 92% rename from tools/tests/dependency-graph.test.js rename to tools/tests/dependency-graph.test.mjs index 3d2f5aa06e1..eee6140ba1a 100644 --- a/tools/tests/dependency-graph.test.js +++ b/tools/tests/dependency-graph.test.mjs @@ -1,6 +1,6 @@ -const test = require('ava') +import test from 'ava' -const { DependencyGraph } = require('../project-graph') +import { DependencyGraph } from '../project-graph/index.mjs' /** @type {DependencyGraph} */ let graph diff --git a/tools/tests/file-visitor-module.test.js b/tools/tests/file-visitor-module.test.mjs similarity index 80% rename from tools/tests/file-visitor-module.test.js rename to tools/tests/file-visitor-module.test.mjs index cad74a7c518..f36d5bad232 100644 --- a/tools/tests/file-visitor-module.test.js +++ b/tools/tests/file-visitor-module.test.mjs @@ -1,27 +1,27 @@ -const { join } = require('path') -const { format } = require('util') +import { join } from 'path' +import { format } from 'util' -const test = require('ava') -const mock = require('mock-fs') +import test from 'ava' +import mock, { restore } from 'mock-fs' -const { normalize } = require('../../tests/integration/utils/snapshots') -const { DependencyGraph, fileVisitor } = require('../project-graph') +import snapshots from '../../tests/integration/utils/snapshots.js' +import { DependencyGraph, fileVisitor } from '../project-graph/index.mjs' -const { esModuleMockedFileSystem } = require('./utils/file-systems') +import { esModuleMockedFileSystem } from './utils/file-systems.mjs' test.before(() => { mock(esModuleMockedFileSystem) }) test.after(() => { - mock.restore() + 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( - normalize(graph.visualize().to_dot()), + snapshots.normalize(graph.visualize().to_dot()), `digraph G { "src/nested/b.js"; "src/nested/a.js"; @@ -42,7 +42,7 @@ test('should merge the graph with files from a different entry point based on im fileVisitor(join('tests/a.test.js'), { graph, visitorPlugins: [] }) fileVisitor(join('tests/c.test.js'), { graph, visitorPlugins: [] }) t.is( - normalize(graph.visualize().to_dot()), + snapshots.normalize(graph.visualize().to_dot()), `digraph G { "src/nested/b.js"; "src/nested/a.js"; diff --git a/tools/tests/file-visitor.test.js b/tools/tests/file-visitor.test.mjs similarity index 80% rename from tools/tests/file-visitor.test.js rename to tools/tests/file-visitor.test.mjs index 431d338ab96..3cb635417ca 100644 --- a/tools/tests/file-visitor.test.js +++ b/tools/tests/file-visitor.test.mjs @@ -1,27 +1,27 @@ -const { join } = require('path') -const { format } = require('util') +import { join } from 'path' +import { format } from 'util' -const test = require('ava') -const mock = require('mock-fs') +import test from 'ava' +import mock, { restore } from 'mock-fs' -const { normalize } = require('../../tests/integration/utils/snapshots') -const { DependencyGraph, fileVisitor } = require('../project-graph') +import snapshots from '../../tests/integration/utils/snapshots.js' +import { DependencyGraph, fileVisitor } from '../project-graph/index.mjs' -const { simpleMockedFileSystem } = require('./utils/file-systems') +import { simpleMockedFileSystem } from './utils/file-systems.mjs' test.before(() => { mock(simpleMockedFileSystem) }) test.after(() => { - mock.restore() + 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( - normalize(graph.visualize().to_dot()), + snapshots.normalize(graph.visualize().to_dot()), `digraph G { "src/nested/b.js"; "src/nested/a.js"; @@ -42,7 +42,7 @@ test('should merge the graph with files from a different entry point', (t) => { fileVisitor(join('tests/a.test.js'), { graph, visitorPlugins: [] }) fileVisitor(join('tests/c.test.js'), { graph, visitorPlugins: [] }) t.is( - normalize(graph.visualize().to_dot()), + snapshots.normalize(graph.visualize().to_dot()), `digraph G { "src/nested/b.js"; "src/nested/a.js"; diff --git a/tools/tests/utils/file-systems.js b/tools/tests/utils/file-systems.mjs similarity index 83% rename from tools/tests/utils/file-systems.js rename to tools/tests/utils/file-systems.mjs index 7903ce2997a..7ca5c9ec2f4 100644 --- a/tools/tests/utils/file-systems.js +++ b/tools/tests/utils/file-systems.mjs @@ -1,11 +1,11 @@ -const { join } = require('path') +import { join } from 'path' const baseFiles = { 'npm-shrinkwrap.json': '', 'README.md': '', } -const simpleMockedFileSystem = { +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');`, @@ -16,7 +16,7 @@ const simpleMockedFileSystem = { ...baseFiles, } -const esModuleMockedFileSystem = { +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';`, @@ -27,7 +27,7 @@ const esModuleMockedFileSystem = { ...baseFiles, } -const callCliMockedFileSystem = { +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')]: '', @@ -35,5 +35,3 @@ const callCliMockedFileSystem = { [join('tests/c.test.js')]: ` `, ...baseFiles, } - -module.exports = { simpleMockedFileSystem, callCliMockedFileSystem, esModuleMockedFileSystem }