diff --git a/.changeset/thirty-crews-buy.md b/.changeset/thirty-crews-buy.md new file mode 100644 index 000000000..0988d828e --- /dev/null +++ b/.changeset/thirty-crews-buy.md @@ -0,0 +1,7 @@ +--- +'modular-scripts': minor +'@modular-scripts/workspace-resolver': minor +'@modular-scripts/modular-types': minor +--- + +Support for non-modular packages in test runner and workspace resolver diff --git a/__fixtures__/extraneous-packages/.gitignore b/__fixtures__/extraneous-packages/.gitignore new file mode 100644 index 000000000..080a4e94d --- /dev/null +++ b/__fixtures__/extraneous-packages/.gitignore @@ -0,0 +1,22 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +/dist diff --git a/__fixtures__/extraneous-packages/.yarnrc b/__fixtures__/extraneous-packages/.yarnrc new file mode 100644 index 000000000..67d13ac5f --- /dev/null +++ b/__fixtures__/extraneous-packages/.yarnrc @@ -0,0 +1 @@ +disable-self-update-check true \ No newline at end of file diff --git a/__fixtures__/extraneous-packages/README.md b/__fixtures__/extraneous-packages/README.md new file mode 100644 index 000000000..414a140b2 --- /dev/null +++ b/__fixtures__/extraneous-packages/README.md @@ -0,0 +1,11 @@ +This monorepository has inter-workspace dependencies: + +```mermaid +graph TD; + app-->a; + a-->b; + a-->c; + b-->c; + c-->d; + e-->a; +``` diff --git a/__fixtures__/extraneous-packages/modular/setupEnvironment.ts b/__fixtures__/extraneous-packages/modular/setupEnvironment.ts new file mode 100644 index 000000000..5b785ac2b --- /dev/null +++ b/__fixtures__/extraneous-packages/modular/setupEnvironment.ts @@ -0,0 +1,2 @@ +// Allows for adding setup configuration to Jest +export {}; diff --git a/__fixtures__/extraneous-packages/modular/setupTests.ts b/__fixtures__/extraneous-packages/modular/setupTests.ts new file mode 100644 index 000000000..74b1a275a --- /dev/null +++ b/__fixtures__/extraneous-packages/modular/setupTests.ts @@ -0,0 +1,5 @@ +// jest-dom adds custom jest matchers for asserting on DOM nodes. +// allows you to do things like: +// expect(element).toHaveTextContent(/react/i) +// learn more: https://github.com/testing-library/jest-dom +import '@testing-library/jest-dom/extend-expect'; diff --git a/__fixtures__/extraneous-packages/package.json b/__fixtures__/extraneous-packages/package.json new file mode 100644 index 000000000..3e5d44b7a --- /dev/null +++ b/__fixtures__/extraneous-packages/package.json @@ -0,0 +1,60 @@ +{ + "name": "ghost-tests-monorepo", + "version": "1.0.0", + "main": "index.js", + "author": "Cristiano Belloni ", + "license": "MIT", + "private": true, + "workspaces": [ + "packages/**" + ], + "modular": { + "type": "root" + }, + "scripts": { + "start": "modular start", + "build": "modular build", + "test": "modular test", + "lint": "eslint . --ext .js,.ts,.tsx", + "prettier": "prettier --write ." + }, + "eslintConfig": { + "extends": "modular-app" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "prettier": { + "singleQuote": true, + "trailingComma": "all", + "printWidth": 80, + "proseWrap": "always" + }, + "dependencies": { + "@testing-library/dom": "^8.16.1", + "@testing-library/jest-dom": "^5.16.5", + "@testing-library/react": "^13.3.0", + "@testing-library/user-event": "^7.2.1", + "@types/jest": "^28.1.6", + "@types/node": "^18.7.2", + "@types/react": "^18.0.17", + "@types/react-dom": "^18.0.6", + "eslint-config-modular-app": "^3.0.1", + "modular-scripts": "^3.3.1", + "modular-template-app": "^1.1.0", + "modular-template-package": "^1.1.0", + "prettier": "^2.7.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "typescript": ">=4.2.1 <4.5.0" + } +} diff --git a/__fixtures__/extraneous-packages/packages/README.md b/__fixtures__/extraneous-packages/packages/README.md new file mode 100644 index 000000000..021e98810 --- /dev/null +++ b/__fixtures__/extraneous-packages/packages/README.md @@ -0,0 +1 @@ +This will be the readme inside /packages diff --git a/__fixtures__/extraneous-packages/packages/a/package.json b/__fixtures__/extraneous-packages/packages/a/package.json new file mode 100644 index 000000000..0ce38fab3 --- /dev/null +++ b/__fixtures__/extraneous-packages/packages/a/package.json @@ -0,0 +1,12 @@ +{ + "name": "a", + "private": false, + "modular": { + "type": "package" + }, + "main": "./src/index.ts", + "version": "1.0.0", + "dependencies": { + "failing-extraneous-test": "1.0.0" + } +} diff --git a/__fixtures__/extraneous-packages/packages/a/src/__tests__/index.test.ts b/__fixtures__/extraneous-packages/packages/a/src/__tests__/index.test.ts new file mode 100644 index 000000000..81e3e046d --- /dev/null +++ b/__fixtures__/extraneous-packages/packages/a/src/__tests__/index.test.ts @@ -0,0 +1,6 @@ +import add from '../index'; + +test('it should add two numbers', () => { + console.log('testing a:index.test.ts'); + expect(add(0.1, 0.2)).toEqual(0.30000000000000004); +}); diff --git a/__fixtures__/extraneous-packages/packages/a/src/__tests__/utils/utils.test.ts b/__fixtures__/extraneous-packages/packages/a/src/__tests__/utils/utils.test.ts new file mode 100644 index 000000000..499f8ac1e --- /dev/null +++ b/__fixtures__/extraneous-packages/packages/a/src/__tests__/utils/utils.test.ts @@ -0,0 +1,6 @@ +import add from '../../index'; + +test('it should add two numbers', () => { + console.log('testing a:/utils/utils.test.ts'); + expect(add(0.1, 0.2)).toEqual(0.30000000000000004); +}); diff --git a/__fixtures__/extraneous-packages/packages/a/src/dummy.ts b/__fixtures__/extraneous-packages/packages/a/src/dummy.ts new file mode 100644 index 000000000..be096926a --- /dev/null +++ b/__fixtures__/extraneous-packages/packages/a/src/dummy.ts @@ -0,0 +1,2 @@ +console.log('DUMMY a:dummy.ts - SHOULD NOT EXECUTE!'); +export {}; diff --git a/__fixtures__/extraneous-packages/packages/a/src/index.ts b/__fixtures__/extraneous-packages/packages/a/src/index.ts new file mode 100644 index 000000000..b92ce9fdf --- /dev/null +++ b/__fixtures__/extraneous-packages/packages/a/src/index.ts @@ -0,0 +1,3 @@ +export default function add(a: number, b: number): number { + return a + b; +} diff --git a/__fixtures__/extraneous-packages/packages/b/package.json b/__fixtures__/extraneous-packages/packages/b/package.json new file mode 100644 index 000000000..c91f6097c --- /dev/null +++ b/__fixtures__/extraneous-packages/packages/b/package.json @@ -0,0 +1,12 @@ +{ + "name": "b", + "private": false, + "modular": { + "type": "package" + }, + "main": "./src/index.ts", + "version": "1.0.0", + "dependencies": { + "successful-extraneous-test": "1.0.0" + } +} diff --git a/__fixtures__/extraneous-packages/packages/b/src/__tests__/index.test.ts b/__fixtures__/extraneous-packages/packages/b/src/__tests__/index.test.ts new file mode 100644 index 000000000..f887640d1 --- /dev/null +++ b/__fixtures__/extraneous-packages/packages/b/src/__tests__/index.test.ts @@ -0,0 +1,6 @@ +import multiply from '../index'; + +test('it should multiply two numbers', () => { + console.log('testing b:index.test.ts'); + expect(multiply(4, 5)).toEqual(20); +}); diff --git a/__fixtures__/extraneous-packages/packages/b/src/__tests__/utils/utils.test.ts b/__fixtures__/extraneous-packages/packages/b/src/__tests__/utils/utils.test.ts new file mode 100644 index 000000000..b1db4927d --- /dev/null +++ b/__fixtures__/extraneous-packages/packages/b/src/__tests__/utils/utils.test.ts @@ -0,0 +1,6 @@ +import multiply from '../../index'; + +test('it should multiply two numbers', () => { + console.log('testing b:/utils/utils.test.ts'); + expect(multiply(2, 4)).toEqual(8); +}); diff --git a/__fixtures__/extraneous-packages/packages/b/src/index.ts b/__fixtures__/extraneous-packages/packages/b/src/index.ts new file mode 100644 index 000000000..98bf0d4b8 --- /dev/null +++ b/__fixtures__/extraneous-packages/packages/b/src/index.ts @@ -0,0 +1,3 @@ +export default function multiply(...operands: number[]): number { + return operands.reduce((a, o) => a * o); +} diff --git a/__fixtures__/extraneous-packages/packages/c/package.json b/__fixtures__/extraneous-packages/packages/c/package.json new file mode 100644 index 000000000..0b4e93548 --- /dev/null +++ b/__fixtures__/extraneous-packages/packages/c/package.json @@ -0,0 +1,12 @@ +{ + "name": "c", + "private": false, + "modular": { + "type": "package" + }, + "main": "./src/index.ts", + "version": "1.0.0", + "dependencies": { + "no-extraneous-test": "1.0.0" + } +} diff --git a/__fixtures__/extraneous-packages/packages/c/src/__tests__/index.test.ts b/__fixtures__/extraneous-packages/packages/c/src/__tests__/index.test.ts new file mode 100644 index 000000000..0c8dadcb5 --- /dev/null +++ b/__fixtures__/extraneous-packages/packages/c/src/__tests__/index.test.ts @@ -0,0 +1,6 @@ +import add from '../index'; + +test('it should add two numbers', () => { + console.log('testing c:index.test.ts'); + expect(add(0.1, 0.2)).toEqual(0.30000000000000004); +}); diff --git a/__fixtures__/extraneous-packages/packages/c/src/__tests__/utils/utils.test.ts b/__fixtures__/extraneous-packages/packages/c/src/__tests__/utils/utils.test.ts new file mode 100644 index 000000000..f81acda84 --- /dev/null +++ b/__fixtures__/extraneous-packages/packages/c/src/__tests__/utils/utils.test.ts @@ -0,0 +1,6 @@ +import add from '../../index'; + +test('it should add two numbers', () => { + console.log('testing c:/utils/utils.test.ts'); + expect(add(0.1, 0.2)).toEqual(0.30000000000000004); +}); diff --git a/__fixtures__/extraneous-packages/packages/c/src/index.ts b/__fixtures__/extraneous-packages/packages/c/src/index.ts new file mode 100644 index 000000000..b92ce9fdf --- /dev/null +++ b/__fixtures__/extraneous-packages/packages/c/src/index.ts @@ -0,0 +1,3 @@ +export default function add(a: number, b: number): number { + return a + b; +} diff --git a/__fixtures__/extraneous-packages/packages/failing-extraneous-test/package.json b/__fixtures__/extraneous-packages/packages/failing-extraneous-test/package.json new file mode 100644 index 000000000..8b1391ded --- /dev/null +++ b/__fixtures__/extraneous-packages/packages/failing-extraneous-test/package.json @@ -0,0 +1,9 @@ +{ + "name": "failing-extraneous-test", + "private": false, + "main": "./src/index.ts", + "scripts": { + "test": "echo 'this is the test for failing-extraneous-test' && exit -1" + }, + "version": "1.0.0" +} diff --git a/__fixtures__/extraneous-packages/packages/failing-extraneous-test/src/__tests__/index.test.ts b/__fixtures__/extraneous-packages/packages/failing-extraneous-test/src/__tests__/index.test.ts new file mode 100644 index 000000000..62824a092 --- /dev/null +++ b/__fixtures__/extraneous-packages/packages/failing-extraneous-test/src/__tests__/index.test.ts @@ -0,0 +1,5 @@ +import add from '../index'; + +test('it should add two numbers', () => { + expect(add(0.1, 0.2)).toEqual(0.30000000000000004); +}); diff --git a/__fixtures__/extraneous-packages/packages/failing-extraneous-test/src/index.ts b/__fixtures__/extraneous-packages/packages/failing-extraneous-test/src/index.ts new file mode 100644 index 000000000..b92ce9fdf --- /dev/null +++ b/__fixtures__/extraneous-packages/packages/failing-extraneous-test/src/index.ts @@ -0,0 +1,3 @@ +export default function add(a: number, b: number): number { + return a + b; +} diff --git a/__fixtures__/extraneous-packages/packages/no-extraneous-test/package.json b/__fixtures__/extraneous-packages/packages/no-extraneous-test/package.json new file mode 100644 index 000000000..9cde90f10 --- /dev/null +++ b/__fixtures__/extraneous-packages/packages/no-extraneous-test/package.json @@ -0,0 +1,6 @@ +{ + "name": "no-extraneous-test", + "private": false, + "main": "./src/index.ts", + "version": "1.0.0" +} diff --git a/__fixtures__/extraneous-packages/packages/no-extraneous-test/src/__tests__/index.test.ts b/__fixtures__/extraneous-packages/packages/no-extraneous-test/src/__tests__/index.test.ts new file mode 100644 index 000000000..62824a092 --- /dev/null +++ b/__fixtures__/extraneous-packages/packages/no-extraneous-test/src/__tests__/index.test.ts @@ -0,0 +1,5 @@ +import add from '../index'; + +test('it should add two numbers', () => { + expect(add(0.1, 0.2)).toEqual(0.30000000000000004); +}); diff --git a/__fixtures__/extraneous-packages/packages/no-extraneous-test/src/index.ts b/__fixtures__/extraneous-packages/packages/no-extraneous-test/src/index.ts new file mode 100644 index 000000000..b92ce9fdf --- /dev/null +++ b/__fixtures__/extraneous-packages/packages/no-extraneous-test/src/index.ts @@ -0,0 +1,3 @@ +export default function add(a: number, b: number): number { + return a + b; +} diff --git a/__fixtures__/extraneous-packages/packages/successful-extraneous-test/package.json b/__fixtures__/extraneous-packages/packages/successful-extraneous-test/package.json new file mode 100644 index 000000000..e9526c6c9 --- /dev/null +++ b/__fixtures__/extraneous-packages/packages/successful-extraneous-test/package.json @@ -0,0 +1,9 @@ +{ + "name": "successful-extraneous-test", + "private": false, + "main": "./src/index.ts", + "scripts": { + "test": "echo 'this is the test for successful-extraneous-test'" + }, + "version": "1.0.0" +} diff --git a/__fixtures__/extraneous-packages/packages/successful-extraneous-test/src/__tests__/index.test.ts b/__fixtures__/extraneous-packages/packages/successful-extraneous-test/src/__tests__/index.test.ts new file mode 100644 index 000000000..62824a092 --- /dev/null +++ b/__fixtures__/extraneous-packages/packages/successful-extraneous-test/src/__tests__/index.test.ts @@ -0,0 +1,5 @@ +import add from '../index'; + +test('it should add two numbers', () => { + expect(add(0.1, 0.2)).toEqual(0.30000000000000004); +}); diff --git a/__fixtures__/extraneous-packages/packages/successful-extraneous-test/src/index.ts b/__fixtures__/extraneous-packages/packages/successful-extraneous-test/src/index.ts new file mode 100644 index 000000000..b92ce9fdf --- /dev/null +++ b/__fixtures__/extraneous-packages/packages/successful-extraneous-test/src/index.ts @@ -0,0 +1,3 @@ +export default function add(a: number, b: number): number { + return a + b; +} diff --git a/__fixtures__/extraneous-packages/tsconfig.json b/__fixtures__/extraneous-packages/tsconfig.json new file mode 100644 index 000000000..d2e2dbe0a --- /dev/null +++ b/__fixtures__/extraneous-packages/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "modular-scripts/tsconfig.json", + "include": ["modular", "packages/**/src"] +} diff --git a/__fixtures__/resolve-workspace/non-modular-workspace-1/package.json b/__fixtures__/resolve-workspace/non-modular-workspace-1/package.json new file mode 100644 index 000000000..b206203cd --- /dev/null +++ b/__fixtures__/resolve-workspace/non-modular-workspace-1/package.json @@ -0,0 +1,16 @@ +{ + "name": "non-modular-workspace-1", + "version": "1.0.0", + "author": "App Frameworks team", + "license": "MIT", + "private": true, + "workspaces": [ + "packages/**" + ], + "modular": { + "type": "root" + }, + "dependencies": { + "lodash": "^4.17.21" + } +} diff --git a/__fixtures__/resolve-workspace/non-modular-workspace-1/packages/app-one/package.json b/__fixtures__/resolve-workspace/non-modular-workspace-1/packages/app-one/package.json new file mode 100644 index 000000000..a4f4944f8 --- /dev/null +++ b/__fixtures__/resolve-workspace/non-modular-workspace-1/packages/app-one/package.json @@ -0,0 +1,12 @@ +{ + "name": "app-one", + "private": true, + "modular": { + "type": "app" + }, + "dependencies": { + "package-one": "1.0.0", + "package-two": "1.0.0" + }, + "version": "1.0.0" +} diff --git a/__fixtures__/resolve-workspace/non-modular-workspace-1/packages/package-extraneous-1/package.json b/__fixtures__/resolve-workspace/non-modular-workspace-1/packages/package-extraneous-1/package.json new file mode 100644 index 000000000..d0d41e9a8 --- /dev/null +++ b/__fixtures__/resolve-workspace/non-modular-workspace-1/packages/package-extraneous-1/package.json @@ -0,0 +1,6 @@ +{ + "name": "package-extraneous-1", + "private": true, + "main": "./src/index.ts", + "version": "1.0.0" +} diff --git a/__fixtures__/resolve-workspace/non-modular-workspace-1/packages/package-extraneous-2/package.json b/__fixtures__/resolve-workspace/non-modular-workspace-1/packages/package-extraneous-2/package.json new file mode 100644 index 000000000..502facc63 --- /dev/null +++ b/__fixtures__/resolve-workspace/non-modular-workspace-1/packages/package-extraneous-2/package.json @@ -0,0 +1,9 @@ +{ + "name": "package-extraneous-2", + "private": true, + "modular": { + "anotherProperty": "value" + }, + "main": "./src/index.ts", + "version": "1.0.0" +} diff --git a/__fixtures__/resolve-workspace/non-modular-workspace-1/packages/package-extraneous-3/package.json b/__fixtures__/resolve-workspace/non-modular-workspace-1/packages/package-extraneous-3/package.json new file mode 100644 index 000000000..4fac34920 --- /dev/null +++ b/__fixtures__/resolve-workspace/non-modular-workspace-1/packages/package-extraneous-3/package.json @@ -0,0 +1,9 @@ +{ + "name": "package-extraneous-3", + "private": true, + "modular": { + "type": "" + }, + "main": "./src/index.ts", + "version": "1.0.0" +} diff --git a/__fixtures__/resolve-workspace/non-modular-workspace-1/packages/package-one/package.json b/__fixtures__/resolve-workspace/non-modular-workspace-1/packages/package-one/package.json new file mode 100644 index 000000000..ee6acb268 --- /dev/null +++ b/__fixtures__/resolve-workspace/non-modular-workspace-1/packages/package-one/package.json @@ -0,0 +1,9 @@ +{ + "name": "package-one", + "private": true, + "modular": { + "type": "package" + }, + "main": "./src/index.ts", + "version": "1.0.0" +} diff --git a/packages/modular-scripts/src/__tests__/test.test.ts b/packages/modular-scripts/src/__tests__/test.test.ts index c21ed373c..a4cbd4c4e 100644 --- a/packages/modular-scripts/src/__tests__/test.test.ts +++ b/packages/modular-scripts/src/__tests__/test.test.ts @@ -270,6 +270,74 @@ describe('Modular test command', () => { }); }); + describe('test command supports extraneous (non-modular) packages', () => { + const fixturesFolder = path.join( + __dirname, + Array.from({ length: 4 }).reduce( + (acc) => `${acc}..${path.sep}`, + '', + ), + '__fixtures__', + 'extraneous-packages', + ); + + const currentModularFolder = getModularRoot(); + let randomOutputFolder: string; + + beforeEach(() => { + // Create random dir + randomOutputFolder = tmp.dirSync({ unsafeCleanup: true }).name; + fs.copySync(fixturesFolder, randomOutputFolder); + execa.sync('yarn', { + cwd: randomOutputFolder, + }); + }); + + // Run in a single test, serially for performance reasons (the setup time is quite long) + it("Runs tests on the dependencies of an extraneous test along with ancestors, then fail when the extraneous test fails, succeed when it suceeds and warn when it doesn't exist", () => { + let errorNumber; + try { + runRemoteModularTest(currentModularFolder, randomOutputFolder, [ + 'test', + '--package', + 'failing-extraneous-test', + '--ancestors', + ]); + } catch (error) { + errorNumber = (error as ExecaError).exitCode; + } + expect(errorNumber).toEqual(1); + + const resultPackagesNoTest = runRemoteModularTest( + currentModularFolder, + randomOutputFolder, + ['test', '--package', 'no-extraneous-test', '--ancestors'], + ); + + expect(resultPackagesNoTest.stderr).toContain( + 'PASS test packages/c/src/__tests__/utils/utils.test.ts', + ); + + expect(resultPackagesNoTest.stderr).toContain( + "Can't run tests for non-modular workspace no-extraneous-test: test script is not defined", + ); + + const resultPackagesSuccessfulTest = runRemoteModularTest( + currentModularFolder, + randomOutputFolder, + ['test', '--package', 'successful-extraneous-test', '--ancestors'], + ); + + expect(resultPackagesSuccessfulTest.stderr).toContain( + 'PASS test packages/b/src/__tests__/utils/utils.test.ts', + ); + + expect(resultPackagesSuccessfulTest.stdout).toContain( + 'this is the test for successful-extraneous-test', + ); + }); + }); + describe('test command has error states', () => { // Run in a single test, serially for performance reasons (the setup time is quite long) it('errors when specifying --package with --changed', async () => { diff --git a/packages/modular-scripts/src/__tests__/utils/getChangedWorkspaces.test.ts b/packages/modular-scripts/src/__tests__/utils/getChangedWorkspaces.test.ts index 7e0e7c5a8..d090b290c 100644 --- a/packages/modular-scripts/src/__tests__/utils/getChangedWorkspaces.test.ts +++ b/packages/modular-scripts/src/__tests__/utils/getChangedWorkspaces.test.ts @@ -15,6 +15,7 @@ describe('matchWorkspaces', () => { modular: { type: 'package', }, + type: 'package', children: [], parent: null, dependencies: undefined, @@ -29,6 +30,7 @@ describe('matchWorkspaces', () => { modular: { type: 'package', }, + type: 'package', children: [], parent: null, dependencies: undefined, @@ -43,6 +45,7 @@ describe('matchWorkspaces', () => { modular: { type: 'package', }, + type: 'package', children: [], parent: null, dependencies: undefined, @@ -90,6 +93,7 @@ describe('matchWorkspaces', () => { modular: { type: 'package', }, + type: 'package', children: [], parent: null, dependencies: undefined, @@ -104,6 +108,7 @@ describe('matchWorkspaces', () => { modular: { type: 'package', }, + type: 'package', children: [], parent: null, dependencies: undefined, @@ -139,6 +144,7 @@ describe('matchWorkspaces', () => { modular: { type: 'package', }, + type: 'package', children: [], parent: null, dependencies: undefined, @@ -153,6 +159,7 @@ describe('matchWorkspaces', () => { modular: { type: 'package', }, + type: 'package', children: [], parent: null, dependencies: undefined, @@ -167,6 +174,7 @@ describe('matchWorkspaces', () => { modular: { type: 'package', }, + type: 'package', children: [], parent: null, dependencies: undefined, @@ -215,6 +223,7 @@ describe('matchWorkspaces', () => { modular: { type: 'root', }, + type: 'root', children: [], parent: null, dependencies: undefined, @@ -229,6 +238,7 @@ describe('matchWorkspaces', () => { modular: { type: 'package', }, + type: 'package', children: [], parent: null, dependencies: undefined, diff --git a/packages/modular-scripts/src/test/index.ts b/packages/modular-scripts/src/test/index.ts index 28e8063a8..f25838d60 100644 --- a/packages/modular-scripts/src/test/index.ts +++ b/packages/modular-scripts/src/test/index.ts @@ -12,6 +12,7 @@ import * as logger from '../utils/logger'; import type { WorkspaceContent, ModularWorkspacePackage, + WorkspaceMap, } from '@modular-scripts/modular-types'; export interface TestOptions { @@ -70,6 +71,61 @@ async function test( options: TestOptions, userRegexes?: string[], ): Promise { + const { ancestors, changed, package: packages, compareBranch } = options; + + // There are two ways of discovering the test regexes we need: either they're specified by the user as CLI arguments + // or they have to be calculated from selective options (--changed and --package) and optionally agumented with --ancestors + const isSelective = changed || packages?.length; + const { regexes, extraneous } = isSelective + ? await computeSelectiveWorkspaces({ + changed, + compareBranch, + packages, + ancestors, + }) + : { regexes: userRegexes, extraneous: undefined }; + + const [extraneousWorkspaces] = extraneous ?? []; + + logger.debug( + `Computed regexes are: ${JSON.stringify( + regexes, + )}. Extraneous workspaces are: ${JSON.stringify([ + ...(extraneousWorkspaces?.keys() || []), + ])}`, + ); + + // If test is selective (user set --changed or --package) and we computed no regexes or extraneous packages involved, then bail out + if (!regexes?.length && !extraneousWorkspaces?.size && isSelective) { + process.stdout.write( + changed + ? 'No changed workspaces found\n' + : 'No workspaces found in selection\n', + ); + process.exit(0); + } + + // Only run the jest tests if we have regexes or if the test is not selective. + // This is because empty regexes in a selective test mean "run no tests", while empty regexes in a non-selective test mean "run all tests" + if (regexes?.length || !isSelective) { + await runJestTests({ + regexes, + options, + }); + } + + if (extraneousWorkspaces?.size) { + await runExtraneousTests(extraneousWorkspaces); + } +} + +async function runJestTests({ + regexes, + options, +}: { + regexes: string[] | undefined; + options: TestOptions; +}) { const { ancestors, changed, @@ -121,28 +177,6 @@ async function test( const additionalOptions: string[] = []; const cleanRegexes: string[] = []; - // There are two ways of discovering the test regexes we need: either they're specified by the user as CLI arguments - // or they have to be calculated from selective options (--changed and --package) and optionally agumented with --ancestors - const isSelective = changed || packages?.length; - const regexes = isSelective - ? await computeSelectiveRegexes({ - changed, - compareBranch, - packages, - ancestors, - }) - : userRegexes; - - // If test is selective (user set --changed or --package) and we computed no regexes, then bail out - if (!regexes?.length && isSelective) { - process.stdout.write( - changed - ? 'No changed workspaces found\n' - : 'No workspaces found in selection\n', - ); - process.exit(0); - } - if (regexes?.length) { regexes.forEach((reg) => { if (/^(--)([\w]+)/.exec(reg)) { @@ -201,8 +235,42 @@ async function test( } } -// This function takes all the selective options, validates them and returns a subset of workspaces to test -async function computeSelectiveRegexes({ +async function runExtraneousTests(extraneousWorkspaces: WorkspaceContent[0]) { + for (const workspace of extraneousWorkspaces) { + const [pkgName, pkg] = workspace; + const testScript = pkg.rawPackageJson?.scripts?.test; + if (testScript) { + logger.debug( + `Running tests for non-modular workspace ${pkgName}, using its test script: \`${testScript}\``, + ); + + try { + await execAsync('yarnpkg', ['workspace', pkgName, 'test'], { + cwd: getModularRoot(), + log: false, + // @ts-ignore + env: { + BABEL_ENV: 'test', + NODE_ENV: 'test', + }, + }); + } catch (err) { + logger.debug((err as ExecaError).message); + // ✕ Modular test did not pass + throw new Error('\u2715 Custom test did not pass'); + } + } else { + logger.warn( + `Can't run tests for non-modular workspace ${pkgName}: test script is not defined`, + ); + } + } +} + +// This function takes all the selective options, validates them and returns: +// - The modular workspaces to test as a collection of regular expressions to pass to Jest +// - The non-modular workspaces to test as a subset of WorkspaceContent +async function computeSelectiveWorkspaces({ changed, compareBranch, packages, @@ -247,13 +315,48 @@ async function computeSelectiveRegexes({ ); } + // Partition selected packages that have a modular type vs those who haven't + const [workspacePackages, workspaceMap] = resultWorkspaceContent; + + const modularWorkspacePackages = new Map(); + const modularWorkspaceMap: WorkspaceMap = {}; + const nonModularWorkspacePackages = new Map< + string, + ModularWorkspacePackage + >(); + const nonModularWorkspaceMap: WorkspaceMap = {}; + + for (const [pkgName, pkg] of workspacePackages) { + if (!pkg.type) { + // This package is not a Modular package + nonModularWorkspacePackages.set(pkgName, pkg); + nonModularWorkspaceMap[pkgName] = workspaceMap[pkgName]; + } else { + modularWorkspacePackages.set(pkgName, pkg); + modularWorkspaceMap[pkgName] = workspaceMap[pkgName]; + } + } + + const extraneous: WorkspaceContent = [ + nonModularWorkspacePackages, + nonModularWorkspaceMap, + ]; + logger.debug( `Selected test packages are: ${JSON.stringify( Object.keys(resultWorkspaceContent[1]), )}`, ); - return computeTestsRegexes(resultWorkspaceContent); + // We return regexes to pass to yarn for Modular workspaces; we can run them with our Yarn configuration + // We return a subset of WorkspaceContent for non-modular (extraneous) workspaces; we will run them using the test script, if present + return { + regexes: computeTestsRegexes([ + modularWorkspacePackages, + modularWorkspaceMap, + ]), + extraneous, + }; } // This function returns a WorkspaceContent containing all the changed workspaces, compared to targetBranch diff --git a/packages/modular-types/src/types.ts b/packages/modular-types/src/types.ts index 433b5c00b..e40f0dc3e 100644 --- a/packages/modular-types/src/types.ts +++ b/packages/modular-types/src/types.ts @@ -12,9 +12,10 @@ export type ModularWorkspacePackage = { name: string; version: string; workspace: boolean; - modular: { - type: ModularType; + modular?: { + type: ModularType | undefined; }; + type: ModularType | undefined; children: ModularWorkspacePackage[]; parent: ModularWorkspacePackage | null; dependencies: Record | undefined; diff --git a/packages/workspace-resolver/src/__tests__/resolve-dependencies.test.ts b/packages/workspace-resolver/src/__tests__/resolve-dependencies.test.ts index c07ad143d..d456b34c2 100644 --- a/packages/workspace-resolver/src/__tests__/resolve-dependencies.test.ts +++ b/packages/workspace-resolver/src/__tests__/resolve-dependencies.test.ts @@ -28,6 +28,7 @@ describe('resolve-dependencies', () => { modular: { type: 'package', }, + type: 'package', children: [], parent: null, dependencies: undefined, diff --git a/packages/workspace-resolver/src/__tests__/resolve-workspace.test.ts b/packages/workspace-resolver/src/__tests__/resolve-workspace.test.ts index 0be12864c..bdc8fad3b 100644 --- a/packages/workspace-resolver/src/__tests__/resolve-workspace.test.ts +++ b/packages/workspace-resolver/src/__tests__/resolve-workspace.test.ts @@ -151,6 +151,35 @@ describe('@modular-scripts/workspace-resolver', () => { 'The package "app-one" has an invalid version. Modular requires workspace packages to have a version.', ); }); + + it('supports type property', async () => { + const projectRoot = path.join(fixturesPath, 'non-modular-workspace-1'); + const [allPackages] = await resolveWorkspace(projectRoot, projectRoot); + expect(allPackages.has('non-modular-workspace-1')).toEqual(true); + expect(allPackages.has('app-one')).toEqual(true); + expect(allPackages.get('app-one')?.modular).toEqual({ + type: 'app', + }); + expect(allPackages.get('app-one')?.type).toBe('app'); + expect(allPackages.has('package-one')).toEqual(true); + expect(allPackages.get('package-one')?.modular).toEqual({ + type: 'package', + }); + expect(allPackages.get('package-one')?.type).toBe('package'); + expect(allPackages.has('package-extraneous-1')).toEqual(true); + expect(allPackages.get('package-extraneous-1')?.modular).toBeUndefined(); + expect(allPackages.get('package-extraneous-1')?.type).toBeUndefined(); + expect(allPackages.has('package-extraneous-2')).toEqual(true); + expect(allPackages.get('package-extraneous-2')?.modular).toEqual({ + anotherProperty: 'value', + }); + expect(allPackages.get('package-extraneous-2')?.type).toBeUndefined(); + expect(allPackages.has('package-extraneous-3')).toEqual(true); + expect(allPackages.get('package-extraneous-3')?.modular).toEqual({ + type: '', + }); + expect(allPackages.get('package-extraneous-3')?.type).toBeUndefined(); + }); }); describe('analyzeWorkspaceDependencies', () => { diff --git a/packages/workspace-resolver/src/resolve-workspace.ts b/packages/workspace-resolver/src/resolve-workspace.ts index 7804ee28d..d8bf03eb3 100644 --- a/packages/workspace-resolver/src/resolve-workspace.ts +++ b/packages/workspace-resolver/src/resolve-workspace.ts @@ -65,7 +65,8 @@ export async function resolveWorkspace( const pkgPath = packageJsonPath(root); const json = await readPackageJson(isRoot, workingDirToUse, pkgPath); - const isModularRoot = json.modular?.type === 'root'; + const type = json.modular?.type || undefined; + const isModularRoot = type === 'root'; if (!json.name) { throw new Error( @@ -89,10 +90,8 @@ export async function resolveWorkspace( workspace: !!json.workspaces, children: [], parent, - modular: { - type: 'unknown', - ...json.modular, - }, + modular: json.modular, + type, // Like yarn classic `workspaces info`, we include all except peerDependencies dependencies: { ...json.optionalDependencies, @@ -153,7 +152,7 @@ export function analyzeWorkspaceDependencies( // Exclude the root when analyzing package inter-dependencies const packagesWithoutRoot = Array.from(workspacePackages.entries()).filter( ([, pkg]) => { - return pkg.modular.type !== 'root'; + return pkg.modular?.type !== 'root'; }, );