Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolving tsconfig.paths with es module and tsconfig-paths not working #2910

Closed
bhvngt opened this issue Jan 19, 2022 · 14 comments
Closed

Resolving tsconfig.paths with es module and tsconfig-paths not working #2910

bhvngt opened this issue Jan 19, 2022 · 14 comments

Comments

@bhvngt
Copy link

bhvngt commented Jan 19, 2022

Issue description or question

I have a monorepo with es module sub-packages. When I use tsconfig-paths with mocha as described in docs, it fails to resolve path alias.

From this dividab/tsconfig-paths#122, it seems that tsconfig-paths is not supported for esm modules. Solutions that have come out requires writing a separate loader. An alternate loader - esbuild-node-loader supports it natively.

Does wallaby supports configuring loaders? Currently, my mocha tests runs on cli with the support of these loaders. However, wallaby fails to resolve due to above issues.

Wallaby diagnostics report

{
  editorVersion: 'IntelliJ IDEA 2021.3.2 Preview',
  pluginVersion: '1.0.231',
  editorType: 'IntelliJ',
  osVersion: 'darwin 21.2.0',
  nodeVersion: 'v16.13.1',
  coreVersion: '1.0.1209',
  checksum: 'MmUxNzQ4YTIxNGUwZjZhODg5YWRhNzc5Y2E0OWY3MzgsMTY2OTU5MzYwMDAwMCww',
  config: {
    files: [
      { pattern: 'tsconfig.json', instrument: false, ignore: false, trigger: true, load: true, order: 1 },
      { pattern: 'package.json', instrument: false, ignore: false, trigger: true, load: true, order: 2 },
      { pattern: 'libs/{service,domain,store}/{src,test,types}/**/!(*.+(test|spec)).{js,ts}', ignore: false, trigger: true, load: true, instrument: true, order: 3 },
      { pattern: '**/node_modules/**', instrument: false, ignore: true, trigger: true, load: true },
      { pattern: '.env', instrument: false, ignore: false, trigger: true, load: true, order: 4 }
    ],
    tests: [
      { pattern: 'libs/{service,domain,store}/test/**/*.+(test|spec).ts', ignore: false, trigger: true, load: true, test: true, order: 5 },
      { pattern: '**/node_modules/**', ignore: true, trigger: true, load: true, test: true }
    ],
    testFramework: { version: 'mocha@2.1.0', configurator: 'mocha@2.1.0', reporter: 'mocha@2.1.0', starter: 'mocha@2.1.0' },
    filesWithNoCoverageCalculated: [ '**/node_modules/**', '**/test/**/*.+(test|spec).{js,ts}' ],
    env: {
      type: 'node',
      params: { runner: '--experimental-specifier-resolution=node' },
      DOTENV_CONFIG_PATH: '.env',
      runner: '<homeDir>/.fnm/node-versions/v16.13.1/installation/bin/node',
      viewportSize: { width: 800, height: 600 },
      options: { width: 800, height: 600 },
      bundle: true
    },
    workers: { restart: true, initial: 0, regular: 0, recycle: true },
    debug: true,
    reportConsoleErrorAsError: true,
    diagnostics: {},
    runAllTestsInAffectedTestFile: false,
    updateNoMoreThanOneSnapshotPerTestFileRun: false,
    addModifiedTestFileToExclusiveTestRun: true,
    compilers: { '**/*.?(lit)coffee?(.md)': [Function (anonymous)] },
    preprocessors: {},
    maxConsoleMessagesPerTest: 100,
    autoConsoleLog: true,
    delays: { run: 0, edit: 100, update: 0 },
    teardown: undefined,
    hints: {
      ignoreCoverage: '__REGEXP /ignore coverage|istanbul ignore/',
      ignoreCoverageForFile: '__REGEXP /ignore file coverage/',
      commentAutoLog: '?',
      testFileSelection: { include: '__REGEXP /file\\.only/', exclude: '__REGEXP /file\\.skip/' }
    },
    automaticTestFileSelection: true,
    runSelectedTestsOnly: false,
    mapConsoleMessagesStackTrace: false,
    extensions: {},
    reportUnhandledPromises: true,
    slowTestThreshold: 75,
    lowCoverageThreshold: 80,
    loose: undefined,
    symlinkNodeModules: true,
    configCode: 'module.exports = function(wallaby) {\n' +
      '\treturn {\n' +
      '\t\tfiles: [\n' +
      '\t\t\t{ pattern: "tsconfig.json", instrument: false },\n' +
      '\t\t\t{ pattern: "package.json", instrument: false },\n' +
      '\t\t\t"libs/{service,domain,store}/{src,test,types}/**/!(*.+(test|spec)).{js,ts}",\n' +
      '\t\t\t{ pattern: "!**/node_modules/**", instrument: false },\n' +
      '\t\t\t{ pattern: ".env", instrument: false }\n' +
      '\t\t],\n' +
      '\n' +
      '\t\ttests: [\n' +
      '\t\t\t"libs/{service,domain,store}/test/**/*.+(test|spec).ts",\n' +
      '\t\t\t"!**/node_modules/**"\n' +
      '\t\t],\n' +
      '\n' +
      '\t\ttestFramework: "mocha",\n' +
      '\t\tfilesWithNoCoverageCalculated: ["**/node_modules/**", "**/test/**/*.+(test|spec).{js,ts}"],\n' +
      '\n' +
      '\t\tenv: {\n' +
      '\t\t\ttype: "node",\n' +
      '\t\t\tparams: {\n' +
      '\t\t\t\trunner: "--experimental-specifier-resolution=node"\n' +
      '\t\t\t},\n' +
      '\t\t\t"DOTENV_CONFIG_PATH": ".env"\n' +
      '\t\t},\n' +
      '\n' +
      '\t\tworkers: { restart: true },\n' +
      '\t\tdebug: true,\n' +
      '\t\treportConsoleErrorAsError: true,\n' +
      '\t\tsetup: async (wallaby) => {\n' +
      '\t\t\tconst fs = require("fs");\n' +
      '\t\t\tconst json5 = require("json5");\n' +
      '\t\t\tconst path = require("path");\n' +
      '\t\t\tconst chai = require("chai");\n' +
      '\t\t\tconst symlinkProjectModules = () => {\n' +
      '\t\t\t\tconst rushJson = json5.parse(fs.readFileSync(`${wallaby.localProjectDir}/rush.json`, "utf-8"));\n' +
      '\t\t\t\tconsole.log("Sym linking rush modules");\n' +
      '\t\t\t\trushJson.projects.map(project => {\n' +
      '\t\t\t\t\tconst [packageCategory, packageName] = project.packageName.split("/");\n' +
      '\t\t\t\t\tconst categoryDir = `./node_modules/${packageCategory}`;\n' +
      '\t\t\t\t\tif (!fs.existsSync(categoryDir)) fs.mkdirSync(categoryDir);\n' +
      '\t\t\t\t\tif (!fs.existsSync(`${categoryDir}/${packageName}`)) {\n' +
      '\t\t\t\t\t\tconsole.log("Sym linking", project.packageName);\n' +
      '\t\t\t\t\t\tconst sourcePath = `${wallaby.localProjectDir}/${project.projectFolder}`;\n' +
      '\t\t\t\t\t\tconst targetPath = `${categoryDir}/${packageName}`;\n' +
      '\t\t\t\t\t\tconsole.log(`Sym linking ${sourcePath} with ${targetPath}`);\n' +
      '\t\t\t\t\t\tfs.symlinkSync(sourcePath, targetPath);\n' +
      '\t\t\t\t\t}\n' +
      '\t\t\t\t});\n' +
      '\t\t\t};\n' +
      '\n' +
      '\t\t\tconst setupChai = async () => {\n' +
      '\t\t\t\tchai.should();\n' +
      '\t\t\t\tchai.use(require("chai-as-promised"));\n' +
      '\t\t\t\tconst libStoreSetup = await import(path.join(wallaby.localProjectDir, "libs/store/test/helpers/globalSetup.js"));\n' +
      '\t\t\t\tconst libServiceSetup = await import(path.join(wallaby.localProjectDir, "libs/service/test/helpers/globalSetup.js"));\n' +
      '\t\t\t\tawait libStoreSetup.mochaGlobalSetup();\n' +
      '\t\t\t\tawait libServiceSetup.mochaGlobalSetup();\n' +
      '\t\t\t};\n' +
      '\n' +
      '\t\t\tif (global._ranSetup) return;\n' +
      '\t\t\trequire("dotenv/config");\n' +
      '\t\t\tconst tsConfigPaths = require("tsconfig-paths");\n' +
      '\t\t\tconst tsconfig = require("./tsconfig.json");\n' +
      '\t\t\ttsConfigPaths.register({\n' +
      '\t\t\t\tbaseUrl: tsconfig.compilerOptions.baseUrl,\n' +
      '\t\t\t\tpaths: tsconfig.compilerOptions.paths\n' +
      '\t\t\t});\n' +
      '\t\t\tsymlinkProjectModules();\n' +
      '\t\t\tawait setupChai();\n' +
      '\n' +
      '\t\t\tglobal._ranSetup = true;\n' +
      '\t\t}\n' +
      '\t};\n' +
      '};'
  },
  packageJSON: { dependencies: undefined, devDependencies: undefined },
  fs: { numberOfFiles: 33 },
  debug: [
    '2022-01-19T15:58:44.426Z project Wallaby Node version: v16.13.1\n',
    '2022-01-19T15:58:44.426Z project Wallaby config: <homeDir>/Projects/lab/innerview/wallaby.cjs\n',
    '2022-01-19T15:58:54.524Z fs File system scan has finished by timeout\n',
    '2022-01-19T15:58:54.543Z project File cache: <homeDir>/Library/Caches/JetBrains/IntelliJIdea2021.3/wallaby/projects/57e7e640d3458563\n',
    '2022-01-19T15:58:54.723Z uiService Listening port 51235\n',
    '2022-01-19T15:58:55.185Z workers Parallelism for initial run: 2, for regular run: 2\n',
    '2022-01-19T15:58:55.186Z workers Starting run worker instance #0\n',
    '2022-01-19T15:58:55.186Z workers Starting run worker instance #1\n',
    '2022-01-19T15:58:55.187Z workers Web server is listening at 50706\n',
    '2022-01-19T15:58:55.891Z project Stopping process pool\n',
    '2022-01-19T15:58:55.891Z project File cache is up-to-date, starting full test run\n',
    '2022-01-19T15:58:55.937Z project Running postprocessor\n',
    '2022-01-19T15:58:55.940Z postprocessor New TypeScript language service is required\n',
    '2022-01-19T15:58:57.365Z workers Started run worker instance (delayed) #1\n',
    '2022-01-19T15:58:57.367Z workers Started run worker instance (delayed) #0\n',
    '2022-01-19T15:58:57.424Z project Postprocessor execution finished\n',
    '2022-01-19T15:58:57.424Z project Test run started; run priority: 3\n',
    '2022-01-19T15:58:57.425Z project Running all tests\n',
    '2022-01-19T15:58:57.427Z workers Starting test run, priority: 3\n',
    '2022-01-19T15:58:57.427Z workers Distributing tests between 2 workers\n',
    '2022-01-19T15:58:57.428Z workers Running tests in parallel\n',
    '2022-01-19T15:58:57.428Z nodeRunner Starting sandbox [worker #0, session #thsx0]\n',
    '2022-01-19T15:58:57.428Z nodeRunner Starting sandbox [worker #1, session #mgase]\n',
    '2022-01-19T15:58:57.428Z nodeRunner Preparing sandbox [worker #0, session #thsx0]\n',
    '2022-01-19T15:58:57.429Z nodeRunner Preparing sandbox [worker #1, session #mgase]\n',
    '2022-01-19T15:58:57.429Z nodeRunner Prepared sandbox [worker #0, session #thsx0]\n',
    '2022-01-19T15:58:57.429Z nodeRunner Prepared sandbox [worker #1, session #mgase]\n',
    '2022-01-19T15:58:57.429Z workers [worker #0, session #thsx0] Running tests in sandbox\n',
    '2022-01-19T15:58:57.430Z workers [worker #1, session #mgase] Running tests in sandbox\n',
    '2022-01-19T15:58:59.925Z workers [mgase] Loaded 14 test(s)\n',
    '2022-01-19T15:59:00.531Z workers [mgase] Test executed: should pass valid operations\n',
    '2022-01-19T15:59:00.531Z workers [thsx0] Loaded 9 test(s)\n',
    '2022-01-19T15:59:00.655Z workers [mgase] Test executed: should pass valid initiator event details\n',
    '2022-01-19T15:59:00.655Z workers [thsx0] Test executed: should pass valid user profile\n',
    '2022-01-19T15:59:00.753Z workers [mgase] Test executed: should pass valid initiator user id\n',
    '2022-01-19T15:59:00.753Z workers [thsx0] Test executed: should add default tenants when passed payload without tenants\n',
    '2022-01-19T15:59:01.073Z workers [mgase] Test executed: should pass valid add operation\n',
    '2022-01-19T15:59:01.074Z workers [thsx0] Test executed: should fail without required attributes\n',
    '2022-01-19T15:59:01.278Z workers [mgase] Test executed: should pass valid remove operation\n',
    '2022-01-19T15:59:01.278Z workers [thsx0] Test executed: should sign in authenticated user\n',
    '2022-01-19T15:59:01.448Z workers [mgase] Test executed: should pass valid replace operation\n',
    '2022-01-19T15:59:01.448Z workers [thsx0] Test executed: should save valid command\n',
    '2022-01-19T15:59:01.645Z workers [mgase] Test executed: should pass operation with valid payload\n',
    '2022-01-19T15:59:01.645Z workers [thsx0] Test executed: should throw error for invalid command\n',
    '2022-01-19T15:59:01.686Z workers [thsx0] Test executed: should save command with multiple valid operations\n',
    '2022-01-19T15:59:01.686Z workers [mgase] Test executed: should fail with missing schemaId, eventName and aggregateName\n',
    '2022-01-19T15:59:01.730Z workers [thsx0] Test executed: should throw error for command with mix of valid and invalid operations\n',
    '2022-01-19T15:59:01.787Z workers [mgase] Test executed: should fail with missing patch or payload\n',
    '2022-01-19T15:59:01.908Z workers [thsx0] Test executed: should throw Unauthorized Error for invalid session\n',
    '2022-01-19T15:59:01.963Z workers [mgase] Test executed: should fail operation containing both payload as well as patch\n',
    '2022-01-19T15:59:02.119Z workers [mgase] Test executed: should add User Profile\n',
    '2022-01-19T15:59:02.291Z workers [mgase] Test executed: anon role with public schema\n',
    '2022-01-19T15:59:02.435Z workers [mgase] Test executed: service role with public schema\n',
    '2022-01-19T15:59:02.531Z workers [mgase] Test executed: service role with innerview schema\n',
    '2022-01-19T15:59:03.500Z workers [thsx0] Run 9 test(s), skipped 0 test(s)\n',
    '2022-01-19T15:59:03.509Z workers [thsx0] Sandbox is responsive, closing it\n',
    '2022-01-19T15:59:03.510Z workers Starting run worker instance #0\n',
    '2022-01-19T15:59:03.533Z workers [mgase] Run 14 test(s), skipped 0 test(s)\n',
    '2022-01-19T15:59:03.541Z workers [mgase] Sandbox is responsive, closing it\n',
    '2022-01-19T15:59:03.541Z workers Starting run worker instance #1\n',
    '2022-01-19T15:59:03.541Z workers Merging parallel test run results\n',
    '2022-01-19T15:59:03.542Z project Test run finished\n',
    '2022-01-19T15:59:03.543Z project Processed console.log entries\n',
    '2022-01-19T15:59:03.543Z project Processed loading sequences\n',
    '2022-01-19T15:59:03.544Z project Processed executed tests\n',
    '2022-01-19T15:59:03.546Z project Processed code coverage\n',
    '2022-01-19T15:59:03.572Z project Test run result processed and sent to IDE\n',
    '2022-01-19T15:59:04.145Z workers Started run worker instance (delayed) #0\n',
    '2022-01-19T15:59:04.412Z workers Started run worker instance (delayed) #1\n',
    '2022-01-19T15:59:16.363Z fs File changed in editor: libs/store/test/CommandRepo.test.ts\n',
    '2022-01-19T15:59:16.364Z extended-core New file or complex file change\n',
    '2022-01-19T15:59:16.378Z project Running postprocessor\n',
    '2022-01-19T15:59:16.378Z testTask Test files from affected: 1, from deleted or manually requested: 0, from recently changed: 0, from loaded by: 0, from failing: 0\n',
    '2022-01-19T15:59:17.889Z fs File changed in editor: libs/store/test/CommandRepo.test.ts\n',
    '2022-01-19T15:59:17.894Z fs File changed in editor: libs/store/test/CommandRepo.test.ts\n',
    '2022-01-19T15:59:17.907Z project Test run cancelled, re-queueing run data\n',
    '2022-01-19T15:59:17.908Z project Test run finished\n',
    '2022-01-19T15:59:17.908Z project Test run data re-queued\n',
    '2022-01-19T15:59:18.027Z project Running postprocessor\n',
    '2022-01-19T15:59:18.028Z testTask Test files from affected: 1, from deleted or manually requested: 0, from recently changed: 0, from loaded by: 0, from failing: 0\n',
    '2022-01-19T15:59:18.994Z fs File changed: libs/store/test/CommandRepo.test.ts\n',
    '2022-01-19T15:59:19.007Z fs No metadata for added file found: .idea/workspace.xml\n',
    '2022-01-19T15:59:19.027Z project Postprocessor execution finished\n',
    '2022-01-19T15:59:19.027Z project Test run started; run priority: 2\n',
    '2022-01-19T15:59:19.029Z testTask Test files from affected: 1, from deleted or manually requested: 0, from recently changed: 0, from loaded by: 0, from failing: 0\n',
    '2022-01-19T15:59:19.034Z workers Starting test run, priority: 2\n',
    '2022-01-19T15:59:19.034Z nodeRunner Starting sandbox [worker #0, session #r9mnp]\n',
    '2022-01-19T15:59:19.034Z nodeRunner Preparing sandbox [worker #0, session #r9mnp]\n',
    '2022-01-19T15:59:19.038Z nodeRunner Prepared sandbox [worker #0, session #r9mnp]\n',
    '2022-01-19T15:59:19.039Z workers [worker #0, session #r9mnp] Running tests in sandbox\n',
    '2022-01-19T15:59:19.043Z fs No changes detected for libs/store/test/CommandRepo.test.ts\n',
    '2022-01-19T15:59:19.045Z extended-core File was not changed, but file markers may need to be synced\n',
    "2022-01-19T15:59:20.761Z workers Sandbox (active) [r9mnp] error: Cannot find package '$src' imported from <homeDir>/Library/Caches/JetBrains/IntelliJIdea2021.3/wallaby/projects/57e7e640d3458563/instrumented/libs/store/test/CommandRepo.test.js\n",
    "2022-01-19T15:59:20.763Z workers Failed to map the stack to user code, entry message: Cannot find package '$src' imported from <homeDir>/Library/Caches/JetBrains/IntelliJIdea2021.3/wallaby/projects/57e7e640d3458563/instrumented/libs/store/test/CommandRepo.test.js, stack: Error [ERR_MODULE_NOT_FOUND]: Cannot find package '$src' imported from <homeDir>/Library/Caches/JetBrains/IntelliJIdea2021.3/wallaby/projects/57e7e640d3458563/instrumented/libs/store/test/CommandRepo.test.js\n" +
      '    at new NodeError (node:internal/errors:371:5)\n' +
      '    at packageResolve (node:internal/modules/esm/resolve:884:9)\n' +
      '    at moduleResolve (node:internal/modules/esm/resolve:929:18)\n' +
      '    at defaultResolve (node:internal/modules/esm/resolve:1044:11)\n' +
      '    at ESMLoader.resolve (node:internal/modules/esm/loader:422:30)\n' +
      '    at ESMLoader.getModuleJob (node:internal/modules/esm/loader:222:40)\n' +
      '    at ModuleWrap.<anonymous> (node:internal/modules/esm/module_job:76:40)\n' +
      '    at link (node:internal/modules/esm/module_job:75:36)\n',
    '2022-01-19T15:59:20.862Z workers [r9mnp] Run 0 test(s), skipped 0 test(s)\n',
    '2022-01-19T15:59:20.868Z workers [r9mnp] Sandbox is responsive, closing it\n',
    '2022-01-19T15:59:20.871Z workers Starting run worker instance #0\n',
    '2022-01-19T15:59:20.873Z project Test run finished\n',
    '2022-01-19T15:59:20.874Z project Processed console.log entries\n',
    '2022-01-19T15:59:20.874Z project Processed loading sequences\n',
    '2022-01-19T15:59:20.874Z project Processed executed tests\n',
    '2022-01-19T15:59:20.874Z project Processed code coverage\n',
    '2022-01-19T15:59:20.886Z project Test run result processed and sent to IDE\n',
    '2022-01-19T15:59:21.839Z workers Started run worker instance (delayed) #0\n'
  ]
}
@ArtemGovorov
Copy link
Member

Can you please create a sample project repo with the working mocha setup (as you want it to work ideally) and we'll take a look to see how to configure the same thing in Wallaby? If you like, you may combine the sample repo with the other issue you've raised #2909.

@bhvngt
Copy link
Author

bhvngt commented Jan 20, 2022

@ArtemGovorov Here's the repo that reproduces the error I am facing.

I am using rushjs to maintain my monorepo and pnpm. Hence you may have to install rushjs.io and pnpm in order to setup this project. You will need to run pnpm run build inside libs/domain and libs/store folder to compile ts files.

@smcenlly
Copy link
Member

@bhvngt - the repo doesn't run for me. I am getting some errors, function: Cannot find module 'json5' and then after fixing that, cannot find chai. It looks like there's no node_modules in the mono repo root, where are these packages being resolved from in your case?

I'll continue to try get it working...

@smcenlly
Copy link
Member

I'm not sure I've set your repo up correctly but it seems to be working. This is what I did:

I tried linking common/temp/node_modules to the root folder (ln -s ./common/temp/node_modules node_modules) and managed to get a little further, but then got this error:


Uncaught Error in Wallaby `setup` function: ENOENT: no such file or directory, mkdir './node_modules/@libs' 
    at Object.mkdirSync (node:fs:1334:3)

I then changed your wallaby.cjs configuration file to try fix this error:

...
+        if (!fs.existsSync('node_modules')) fs.mkdirSync('node_modules');
          if (!fs.existsSync(categoryDir)) fs.mkdirSync(categoryDir);
...

At this point, I have a number of problems:

‌0 failing tests, 0 passing  ​Launch Coverage & Test Explorer​ | ​Search Tests​

Cannot use import statement outside a module 
  ​​​​​at ​​​​​​libs/domain/test/index.test.ts:1​

Cannot use import statement outside a module 
  ​​​​​at ​​​​​​libs/domain/test/utils/Foo.test.ts:1​

Cannot use import statement outside a module 
  ​​​​​at ​​​​​​libs/store/test/index.test.ts:1​

Uncaught Error in Wallaby `setup` function: EEXIST: file already exists, symlink './/libs/domain' -> './node_modules/@libs/domain' 
    at Object.symlinkSync (node:fs:1651:3) 
    at Array.map (<anonymous>) 
    at WebSocket.emit (node:events:390:28) 
    at afterWrite (node:internal/streams/writable:497:5) 
    at onwrite (node:internal/streams/writable:477:7) 
    at Zlib.cb (node:internal/streams/transform:201:7) 
    at Zlib.processCallback (node:zlib:612:8) 

(node:47220) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension. 
(Use `node --trace-warnings ...` to show where the warning was created) 

(node:47221) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension. 
(Use `node --trace-warnings ...` to show where the warning was created) 

(node:47222) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension. 
(Use `node --trace-warnings ...` to show where the warning was created) 

After creating a package.json in the mono-repo root, I was able to get the working commit to execute:

package.json

{
  "name": "wallaby-mocha-rushjs",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "type": "module",
  "dependencies": {
    "json5": "^2.2.0",
    "esbuild-node-loader": "^0.6.4",
    "ts-node": "^10.4.0",
    "chai": "^4.3.4",
    "tsconfig-paths": "^3.12.0",
    "typescript": "^4.5.4",
    "tsup": "^5.11.11"
  },
  "devDependencies": {
    "mocha": "^9.1.4"
  },
  "scripts": {
    "test": "mocha"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/bhvngt/wallaby-mocha-rushjs.git"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/bhvngt/wallaby-mocha-rushjs/issues"
  },
  "homepage": "https://github.com/bhvngt/wallaby-mocha-rushjs#readme"
}

@bhvngt
Copy link
Author

bhvngt commented Jan 21, 2022

@smcenlly I am sorry for the trouble you went through in setting it up. I missed putting following instructions

  • Executing ln -s ./common/temp/node_modules node_modules in the project root folder
  • I forgot to commit project root folder package.json that contains
{
  "name": "wallaby-mocha-rushjs",
  "version": "1.0.0",
  "description": "",
  "type": "module"
}

With these the Working Version should have worked fine.

After this the other two commits should have reproduced this issue and #2909.

@bhvngt
Copy link
Author

bhvngt commented Jan 21, 2022

I typically run mocha test from inside each of the sub project folder. you could run mocha tests/**/*.test.ts from each of the sub-project folder - ./libs/domain, ./libs/store.

@smcenlly
Copy link
Member

@smcenlly I am sorry for the trouble you went through in setting it up. I missed putting following instructions

No problem. Looks like I worked it out correctly :)


I will reply for both this issue and #2909 here as the cause is the same.

Right now, when you run Wallaby in an ESM context, it does the following things:

  1. Copies your source files to a cache directory
  2. Transforms your source files to JavaScript if required (e.g. in the case of TypeScript)
  3. Runs your tests from the cache directory

In the case of ESM, and specifically when you are using mocha with esbuild-node-loader, the ESM loader is doing some of the same things at a different point in the execution pipeline, which Wallaby is not currently compatible with.

In short, Wallaby doesn't support either of your cases at the moment, but we have plans to work on this later in the year. I expect we would start this work within the next few months but unfortunately we can't share a timeline right now as there are a number of problems that we don't yet know the solution to.


The sharp edges that you're running into at the moment are related to experimental node features. You may already be aware that you are using a number of experimental node features (e.g. https://nodejs.org/api/esm.html#loaders). We had recently had some problems with the latest version of node because of these (the API had breaking changes within existing node versions).

As an aside, I'm interested to know how you plan on running your code in production? I understand the use of ESM modules, but technically there's no performance benefit in directly linking to your TypeScript file. Either in your tests or in your production code, the code still needs to be transpiled to JavaScript. If you do it with a custom loader (e.g. esbuild-node-loader) it happens on each new process start (and import chain I believe) vs. once at build time as a separate compiler step.

One short term option for you (for right now) is to get your tests running from Wallaby using mocha without ESM (e.g. using commonjs). So your code in production would run with ESM but tests would not. Obviously there's more configuration involved that you may not be interested in working on. You may also try using a different testing framework (such as jest) with esbuild-jest and configure your paths with Jest's moduleNameMapper.

@bhvngt
Copy link
Author

bhvngt commented Jan 21, 2022

Thanks @smcenlly for your reply.

As an aside, I'm interested to know how you plan on running your code in production?

These libraries gets weaved into my svelte-kit app which uses vite for bundling. Hence, there I don't need to worry about esbuild-node-loaders for both front-end as well backend files (I am using cloudflare workers for backend).

but technically there's no performance benefit in directly linking to your TypeScript file. Either in your tests or in your production code, the code still needs to be transpiled to JavaScript.

Intermittent build step ends up adding unnecessary complexity into my current dev environment. So this ask is not for performance but for ease in development

You may also try using a different testing framework (such as jest) with esbuild-jest and configure your paths with Jest's moduleNameMapper.

I have tried my hands with jest but its esm support is still half-baked. I haven't tried my hands with esbuild-jest. This project was updated 11 months back and does not seem to be very active so I am little reluctant to use it.

Since this is a greenfield project and key components of my architecture (vite, svelte-kit) are esm compliant, I would prefer to take the esm route. mocha as such has worked quiet well for me in this regard. What it does not support (watch for esm projects) is well taken care by wallaby.

Is there an option to supply custom loaders to wallaby workers like what I currently do with mocha. Do you think that will resolve these issues.

One short term option for you (for right now) is to get your tests running from Wallaby using mocha without ESM (e.g. using commonjs). So your code in production would run with ESM but tests would not.

I can explore this option. It may workout if the overheads are reasonable and does not affect my larger architectural goal.

@bhvngt
Copy link
Author

bhvngt commented Jan 21, 2022

In short, Wallaby doesn't support either of your cases at the moment, but we have plans to work on this later in the year. I expect we would start this work within the next few months but unfortunately we can't share a timeline right now as there are a number of problems that we don't yet know the solution to.

Appreciate your plans and do look forward to it.

@smcenlly
Copy link
Member

These libraries gets weaved into my svelte-kit app which uses vite for bundling. Hence, there I don't need to worry about esbuild-node-loaders for both front-end as well backend files (I am using cloudflare workers for backend).

Thanks for explaining.

Intermittent build step ends up adding unnecessary complexity into my current dev environment. So this ask is not for performance but for ease in development

Understood. I had misinterpreted your earlier comments.

I have tried my hands with jest but its esm support is still half-baked. I haven't tried my hands with esbuild-jest. This project was updated 11 months back and does not seem to be very active so I am little reluctant to use it.

We are personally using jest with @swc/jest for some of our larger projects. I'm not sure if it'll do what esbuild does for you but it may also be an option.

Is there an option to supply custom loaders to wallaby workers like what I currently do with mocha. Do you think that will resolve these issues.

At the moment, yes. Technically it's not something that we officially support and the changes that we want/need to make to better support ESM may make what I'm going to suggest break for you in a future version of Wallaby (if it does, you may need to make some changes to your configuration).

We had tried to apply the esbuild-node-loader earlier when debugging your problem but it didn't work for us.

I believe you'll need to write your own loader.

For example:

Change your wallaby.cjs configuration file to:

...
env: {
-  runner: "--experimental-specifier-resolution=node"
+. runner: "--experimental-specifier-resolution=node --experimental-loader " + require('path').join(__dirname, 'loader.mjs')
}
...
setup: async (wallaby) => {
+  process.env.projectCacheDir = 'file://' + wallaby.projectCacheDir;
+  process.env.localProjectDir = 'file://' + wallaby.localProjectDir;
...
}
...

Create loader.mjs with the following content:

export function resolve(specifier, context, defaultResolve) {
  return defaultResolve(specifier, context, defaultResolve);
}

export function load(url, context, defaultLoad) {
  if (url.startsWith(process.env.localProjectDir) && url.endsWith('.ts')) {
    url = process.env.projectCacheDir + '/' + url.substring(process.env.localProjectDir.length, url.length - 2) + 'js';
  }

  return defaultLoad(url, context, defaultLoad);
}

This should fix your version that breaks wallaby when typescript is directly linked.

It's a little bit hacky but seems to work for me. You would also need to reimplement tsconfig-paths loading rules in the loader (not entirely sure how to do this but it should be possible). A good way to debug the loader is to add console.log statements and view the output in the Wallaby Console of your editor.


I am going to close the issue for now as I think we've given you a few options that you should be able to work through. If you have any further problems though, feel free to reply (and we can re-open this issue) or else create a new issue.

@bhvngt
Copy link
Author

bhvngt commented Jan 21, 2022

This is awesome @smcenlly. With this little hack I was able to completely remove the intermittent build process, making my dev setup much simpler. As a side-effect, my .ts file editor navigation across packages have become better. Now it does not take me to ./dist/*.d.ts files anymore.

At the moment, yes. Technically it's not something that we officially support and the changes that we want/need to make to better support ESM may make what I'm going to suggest break for you in a future version of Wallaby (if it does, you may need to make some changes to your configuration).

I completely understand this and I am fine with the compromise since this hack is quiet isolated and minimal.

You would also need to reimplement tsconfig-paths loading rules in the loader (not entirely sure how to do this but it should be possible).

I was able to completely get around this issue by using self-referencing package with its name which is esm standard compliant. This removes all the relative imports that were troublesome in my setup. This also removes my need to rely on typescript's path alias which IMO is meant for a very different use case.

I am very happy with the setup now. Thanks for your as always awesome support. My DevX with wallabyjs is now multifold better.

@bhvngt
Copy link
Author

bhvngt commented Feb 14, 2022

@smcenlly I have recently shifted my project to Apple's new M1 based laptop. While doing so, my wallaby setup is breaking. It's throwing

TypeError [ERR_INVALID_MODULE_SPECIFIER]: Invalid module "file://./node_modules/@libs/domain/src/utils/Foo.ts" .

I have updated the sample repo that should reproduce this error.
To successfully setup repo, you will need to run following commands

<root folder>: pnpm i -g @microsoft/rush
<root folder>: rush update
<root folder>: ln -s $(pwd)/common/temp/node_modules node_modules

Mocha test when run from respective sub-projects using mocha test/**/*.ts is running fine.

I tried debugging this and am somewhat at loss on why it is breaking. Error does not seems to be due to ARM based architecture. Since, it works fine if I make following changes.

--- a/libs/store/src/utils/Bar.ts
+++ b/libs/store/src/utils/Bar.ts
@@ -1,4 +1,4 @@
-import {Foo} from "@libs/domain/utils/Foo"
+import {Foo} from "../../../domain/src/utils/Foo"

Here's the diagnostic dump.

{
  editorVersion: 'IntelliJ IDEA 2021.3.2',
  pluginVersion: '1.0.232',
  editorType: 'IntelliJ',
  osVersion: 'darwin 21.3.0',
  nodeVersion: 'v17.4.0',
  coreVersion: '1.0.1222',
  checksum: 'MmUxNzQ4YTIxNGUwZjZhODg5YWRhNzc5Y2E0OWY3MzgsMTY2OTU5MzYwMDAwMCww',
  config: {
    files: [
      { pattern: 'tsconfig.json', instrument: false, ignore: false, trigger: true, load: true, order: 1 },
      { pattern: 'package.json', instrument: false, ignore: false, trigger: true, load: true, order: 2 },
      { pattern: 'libs/{service,domain,store}/{src,test,types}/**/!(*.+(test|spec)).{js,ts}', ignore: false, trigger: true, load: true, instrument: true, order: 3 },
      { pattern: '**/node_modules/**', instrument: false, ignore: true, trigger: true, load: true },
      { pattern: '.env', instrument: false, ignore: false, trigger: true, load: true, order: 4 }
    ],
    tests: [
      { pattern: 'libs/{service,domain,store}/test/**/*.+(test|spec).ts', ignore: false, trigger: true, load: true, test: true, order: 5 },
      { pattern: '**/node_modules/**', ignore: true, trigger: true, load: true, test: true }
    ],
    testFramework: { version: 'mocha@2.1.0', configurator: 'mocha@2.1.0', reporter: 'mocha@2.1.0', starter: 'mocha@2.1.0' },
    filesWithNoCoverageCalculated: [ '**/node_modules/**', '**/test/**/*.+(test|spec).{js,ts}' ],
    env: {
      type: 'node',
      params: { runner: '--experimental-specifier-resolution=node --experimental-loader <homeDir>/Projects/lab/temp/wallaby-mocha-rushjs/loader.mjs' },
      DOTENV_CONFIG_PATH: '.env',
      runner: '<homeDir>/Library/Application Support/fnm/node-versions/v17.4.0/installation/bin/node',
      viewportSize: { width: 800, height: 600 },
      options: { width: 800, height: 600 },
      bundle: true
    },
    workers: { initial: 1, regular: 1, restart: true, recycle: true },
    debug: true,
    reportConsoleErrorAsError: true,
    diagnostics: {},
    runAllTestsInAffectedTestFile: false,
    updateNoMoreThanOneSnapshotPerTestFileRun: false,
    addModifiedTestFileToExclusiveTestRun: true,
    compilers: { '**/*.?(lit)coffee?(.md)': [Function (anonymous)] },
    preprocessors: {},
    maxConsoleMessagesPerTest: 100,
    autoConsoleLog: true,
    delays: { run: 0, edit: 100, update: 0 },
    teardown: undefined,
    hints: {
      ignoreCoverage: '__REGEXP /ignore coverage|istanbul ignore/',
      ignoreCoverageForFile: '__REGEXP /ignore file coverage/',
      commentAutoLog: '?',
      testFileSelection: { include: '__REGEXP /file\\.only/', exclude: '__REGEXP /file\\.skip/' }
    },
    automaticTestFileSelection: true,
    runSelectedTestsOnly: false,
    mapConsoleMessagesStackTrace: false,
    extensions: {},
    reportUnhandledPromises: true,
    slowTestThreshold: 75,
    lowCoverageThreshold: 80,
    loose: undefined,
    symlinkNodeModules: true,
    configCode: 'process.env.INITIALIZED = false;\n' +
      'module.exports = function(wallaby) {\n' +
      '\treturn {\n' +
      '\t\tfiles: [\n' +
      '\t\t\t{ pattern: "tsconfig.json", instrument: false },\n' +
      '\t\t\t{ pattern: "package.json", instrument: false },\n' +
      '\t\t\t"libs/{service,domain,store}/{src,test,types}/**/!(*.+(test|spec)).{js,ts}",\n' +
      '\t\t\t{ pattern: "!**/node_modules/**", instrument: false },\n' +
      '\t\t\t{ pattern: ".env", instrument: false }\n' +
      '\t\t],\n' +
      '\n' +
      '\t\ttests: [\n' +
      '\t\t\t"libs/{service,domain,store}/test/**/*.+(test|spec).ts",\n' +
      '\t\t\t"!**/node_modules/**"\n' +
      '\t\t],\n' +
      '\n' +
      '\t\ttestFramework: "mocha",\n' +
      '\t\tfilesWithNoCoverageCalculated: ["**/node_modules/**", "**/test/**/*.+(test|spec).{js,ts}"],\n' +
      '\n' +
      '\t\tenv: {\n' +
      '\t\t\ttype: "node",\n' +
      '\t\t\tparams: {\n' +
      `\t\t\t\trunner: "--experimental-specifier-resolution=node --experimental-loader " + require('path').join(__dirname, 'loader.mjs')\n` +
      '\t\t\t},\n' +
      '\t\t\t"DOTENV_CONFIG_PATH": ".env"\n' +
      '\t\t},\n' +
      '\n' +
      '\t\tworkers: { initial: 1, regular: 1, restart: true },\n' +
      '\t\tdebug: true,\n' +
      '\t\treportConsoleErrorAsError: true,\n' +
      '\t\tsetup: async (wallaby) => {\n' +
      "\t\t\tprocess.env.projectCacheDir = 'file://' + wallaby.projectCacheDir;\n" +
      "\t\t\tprocess.env.localProjectDir = 'file://' + wallaby.localProjectDir;\n" +
      '\n' +
      '\t\t\tconst fs = require("fs");\n' +
      '\t\t\tconst path = require("path");\n' +
      '\n' +
      '\t\t\tsymlinkNodeModules();\n' +
      '\t\t\tsymlinkProjectModules();\n' +
      '\t\t\tawait setupChai();\n' +
      '\t\t\tprocess.env.INITIALIZED = true;\n' +
      '\n' +
      '\t\t\tfunction symlinkNodeModules() {\n' +
      '\t\t\t\tconst nodeModulesDir = path.join(wallaby.localProjectDir, "node_modules");\n' +
      '\t\t\t\tconst commonTempDir = path.join(wallaby.localProjectDir, "common/temp/node_modules");\n' +
      '\t\t\t\tif (!fs.existsSync(nodeModulesDir)) {\n' +
      '\t\t\t\t\tconsole.log(`Sym linking ${nodeModulesDir} with ${commonTempDir}`);\n' +
      '\t\t\t\t\tfs.symlinkSync(commonTempDir, nodeModulesDir);\n' +
      '\t\t\t\t}\n' +
      '\t\t\t}\n' +
      '\t\t\tfunction rushJson() {\n' +
      '\t\t\t\tconst json5 = require("json5");\n' +
      '\t\t\t\treturn json5.parse(fs.readFileSync(path.join(wallaby.localProjectDir, "rush.json"), "utf-8"));\n' +
      '\t\t\t}\n' +
      '\n' +
      '\t\t\tfunction symlinkProjectModules() {\n' +
      '\t\t\t\tconsole.log("Sym linking rush modules");\n' +
      '\t\t\t\trushJson().projects.map(project => {\n' +
      '\t\t\t\t\tconst [packageCategory, packageName] = project.packageName.split("/");\n' +
      '\t\t\t\t\tconst categoryDir = path.join(wallaby.localProjectDir, "node_modules", packageCategory);\n' +
      '\t\t\t\t\tif (!fs.existsSync(categoryDir)) fs.mkdirSync(categoryDir);\n' +
      '\t\t\t\t\tif (!fs.existsSync(`${categoryDir}/${packageName}`)) {\n' +
      '\t\t\t\t\t\tconsole.log("Sym linking", project.packageName);\n' +
      '\t\t\t\t\t\tconst sourcePath = `${wallaby.localProjectDir}/${project.projectFolder}`;\n' +
      '\t\t\t\t\t\tconst targetPath = `${categoryDir}/${packageName}`;\n' +
      '\t\t\t\t\t\tfs.symlinkSync(sourcePath, targetPath);\n' +
      '\t\t\t\t\t}\n' +
      '\t\t\t\t});\n' +
      '\t\t\t}\n' +
      '\n' +
      '\t\t\tasync function setupChai() {\n' +
      '\t\t\t\tconst chai = require("chai");\n' +
      '\t\t\t\tchai.should();\n' +
      '\t\t\t\tconst projectPaths = rushJson().projects.map(p => path.join(wallaby.localProjectDir, p.projectFolder))\n' +
      '\t\t\t\tawait Promise.all(projectPaths.map(async (path) => {\n' +
      '\t\t\t\t\tconst libStoreSetup = await import(`${path}/test/helpers/globalSetup.js`);\n' +
      '\t\t\t\t\tlibStoreSetup.mochaGlobalSetup?.();\n' +
      '\t\t\t\t}));\n' +
      '\t\t\t}\n' +
      '\t\t}\n' +
      '\t};\n' +
      '};'
  },
  packageJSON: { dependencies: {}, devDependencies: undefined },
  fs: { numberOfFiles: 12 },
  debug: [
    '2022-02-14T13:36:37.465Z project Wallaby Node version: v17.4.0\n',
    '2022-02-14T13:36:37.465Z project Wallaby config: <homeDir>/Projects/lab/temp/wallaby-mocha-rushjs/wallaby.cjs\n',
    '2022-02-14T13:36:38.826Z fs File system scan has finished by timeout\n',
    '2022-02-14T13:36:38.838Z project File cache: <homeDir>/Library/Caches/JetBrains/IntelliJIdea2021.3/wallaby/projects/bafc736c9820b9c4\n',
    '2022-02-14T13:36:38.883Z uiService Listening port 51235\n',
    '2022-02-14T13:36:38.888Z workers Parallelism for initial run: 1, for regular run: 1\n',
    '2022-02-14T13:36:38.889Z workers Starting run worker instance #0\n',
    '2022-02-14T13:36:38.889Z workers Web server is listening at 53096\n',
    '2022-02-14T13:36:38.895Z project File cache requires some updates, waiting required files from IDE\n',
    '2022-02-14T13:36:39.020Z workers Started run worker instance (delayed) #0\n',
    '2022-02-14T13:36:39.105Z project Stopping process pool\n',
    '2022-02-14T13:36:39.107Z project Running postprocessor\n',
    '2022-02-14T13:36:39.111Z postprocessor New TypeScript language service is required\n',
    '2022-02-14T13:36:39.203Z project Postprocessor execution finished\n',
    '2022-02-14T13:36:39.203Z project Test run started; run priority: 3\n',
    '2022-02-14T13:36:39.204Z project Running all tests\n',
    '2022-02-14T13:36:39.204Z workers Starting test run, priority: 3\n',
    '2022-02-14T13:36:39.204Z nodeRunner Starting sandbox [worker #0, session #2tj1o]\n',
    '2022-02-14T13:36:39.204Z nodeRunner Preparing sandbox [worker #0, session #2tj1o]\n',
    '2022-02-14T13:36:39.204Z nodeRunner Prepared sandbox [worker #0, session #2tj1o]\n',
    '2022-02-14T13:36:39.205Z workers [worker #0, session #2tj1o] Running tests in sandbox\n',
    '2022-02-14T13:36:39.232Z workers Sandbox (active) [2tj1o] error: Invalid module "file://<homeDir>/Library/Caches/JetBrains/IntelliJIdea2021.3/wallaby/projects/bafc736c9820b9c4/instrumented/node_modules/@libs/domain/src/utils/Foo.ts" \n',
    '2022-02-14T13:36:39.233Z workers Failed to map the stack to user code, entry message: Invalid module "file://<homeDir>/Library/Caches/JetBrains/IntelliJIdea2021.3/wallaby/projects/bafc736c9820b9c4/instrumented/node_modules/@libs/domain/src/utils/Foo.ts" , stack: TypeError [ERR_INVALID_MODULE_SPECIFIER]: Invalid module "file://<homeDir>/Library/Caches/JetBrains/IntelliJIdea2021.3/wallaby/projects/bafc736c9820b9c4/instrumented/node_modules/@libs/domain/src/utils/Foo.ts" \n' +
      '    at new NodeError (node:internal/errors:371:5)\n' +
      '    at ESMLoader.load (node:internal/modules/esm/loader:380:13)\n' +
      '    at async ESMLoader.moduleProvider (node:internal/modules/esm/loader:280:47)\n',
    '2022-02-14T13:36:39.335Z workers [2tj1o] Run 0 test(s), skipped 0 test(s)\n',
    '2022-02-14T13:36:39.336Z workers [2tj1o] Sandbox is responsive, closing it\n',
    '2022-02-14T13:36:39.336Z workers Starting run worker instance #0\n',
    '2022-02-14T13:36:39.337Z project Test run finished\n',
    '2022-02-14T13:36:39.337Z project Processed console.log entries\n',
    '2022-02-14T13:36:39.337Z project Processed loading sequences\n',
    '2022-02-14T13:36:39.337Z project Processed executed tests\n',
    '2022-02-14T13:36:39.338Z project Processed code coverage\n',
    '2022-02-14T13:36:39.343Z project Test run result processed and sent to IDE\n',
    '2022-02-14T13:36:39.440Z workers Started run worker instance (delayed) #0\n'
  ]
}

@smcenlly
Copy link
Member

It seems like something must have changed in your project (I assume unrelated to Apple's new M1 based laptop) as it was previously working for me on my M1 laptop.

From what I can see, the @libs are now getting resolved from the node_modules path in Wallaby's instrumented files folder. I assume that this has something to do with the symlinking code in your wallaby.cjs file.

I was able to fix your problem by updating the loader.mjs (see below):

loader.mjs

import * as path from 'path';

export function resolve(specifier, context, defaultResolve) {
    return defaultResolve(specifier, context, defaultResolve);
}

export function load(url, context, defaultLoad) {
    if (url.startsWith(process.env.localProjectDir) && url.endsWith('.ts')) {
        url = path.join(process.env.projectCacheDir, url.substring(process.env.localProjectDir.length, url.length - 2) + 'js');
    }

    // Resolve `.ts` files trying to be imported from Wallaby's project cache as JavaScript
    if (url.startsWith(process.env.projectCacheDir) && url.endsWith('.ts')) {
        url = path.join(process.env.projectCacheDir, url.substring(process.env.projectCacheDir.length, url.length - 2) + 'js');
    }

    // If for some reason we've asked to load the file by alias, resolve it to the real path
    if (process.env.projectCacheDir) {
        const nodeModulesLibs = path.join(process.env.projectCacheDir, 'node_modules', '@libs');
        if (url.startsWith(nodeModulesLibs)) {
            url = path.join(process.env.projectCacheDir, 'libs', url.substring(nodeModulesLibs.length));
        }    
    }

    return defaultLoad(url, context, defaultLoad);
}

@bhvngt
Copy link
Author

bhvngt commented Feb 15, 2022

Thanks @smcenlly. Your solutions works well for me.

I am still in dark on how it worked before as I took the same commit and did not make any changes in my sample repo. Guess something must have changed in one of the dependent tool/library in my eco system.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants