config.mjs 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. /*---------------------------------------------------------
  2. * Copyright (C) Microsoft Corporation. All rights reserved.
  3. *--------------------------------------------------------*/
  4. import { existsSync, promises as fs } from 'fs';
  5. import { dirname, isAbsolute, join } from 'path';
  6. import { pathToFileURL } from 'url';
  7. import { CliExpectedError } from './error.mjs';
  8. import { mustResolve } from './resolver.mjs';
  9. import { ensureArray } from './util.mjs';
  10. const configFileRules = {
  11. json: (path) => fs.readFile(path, 'utf8').then(JSON.parse),
  12. js: (path) => import(pathToFileURL(path).toString()),
  13. cjs: (path) => import(pathToFileURL(path).toString()),
  14. mjs: (path) => import(pathToFileURL(path).toString()),
  15. };
  16. /** Loads the default config based on the process working directory. */
  17. export async function loadDefaultConfigFile() {
  18. const base = '.vscode-test';
  19. let dir = process.cwd();
  20. while (true) {
  21. for (const ext of Object.keys(configFileRules)) {
  22. const candidate = join(dir, `${base}.${ext}`);
  23. if (existsSync(candidate)) {
  24. return tryLoadConfigFile(candidate);
  25. }
  26. }
  27. const next = dirname(dir);
  28. if (next === dir) {
  29. break;
  30. }
  31. dir = next;
  32. }
  33. throw new CliExpectedError(`Could not find a ${base} file in this directory or any parent. You can specify one with the --config option.`);
  34. }
  35. /** Loads a specific config file by the path, throwing if loading fails. */
  36. export async function tryLoadConfigFile(path) {
  37. const ext = path.split('.').pop();
  38. if (!configFileRules.hasOwnProperty(ext)) {
  39. throw new CliExpectedError(`I don't know how to load the extension '${ext}'. We can load: ${Object.keys(configFileRules).join(', ')}`);
  40. }
  41. try {
  42. let loaded = await configFileRules[ext](path);
  43. if ('default' in loaded) {
  44. // handle default es module exports
  45. loaded = loaded.default;
  46. }
  47. // allow returned promises to resolve:
  48. loaded = await loaded;
  49. if (typeof loaded === 'object' && 'tests' in loaded) {
  50. return await ResolvedTestConfiguration.load(loaded, path);
  51. }
  52. return await ResolvedTestConfiguration.load({ tests: ensureArray(loaded) }, path);
  53. }
  54. catch (e) {
  55. throw new CliExpectedError(`Could not read config file ${path}: ${e.stack || e}`);
  56. }
  57. }
  58. export class ResolvedTestConfiguration {
  59. path;
  60. tests;
  61. coverage;
  62. /** Directory name the configuration file resides in. */
  63. dir;
  64. static async load(config, path) {
  65. // Resolve all mocha `require` locations relative to the configuration file,
  66. // since these are otherwise relative to the runner which is opaque to the user.
  67. const dir = dirname(path);
  68. for (const test of config.tests) {
  69. if (test.mocha?.require) {
  70. test.mocha.require = await Promise.all(ensureArray(test.mocha.require).map((f) => mustResolve(dir, f)));
  71. }
  72. }
  73. return new ResolvedTestConfiguration(config, path);
  74. }
  75. constructor(config, path) {
  76. this.path = path;
  77. this.coverage = config.coverage;
  78. this.tests = config.tests;
  79. this.dir = dirname(path);
  80. }
  81. /**
  82. * Gets the resolved extension development path for the test configuration.
  83. */
  84. extensionDevelopmentPath(test) {
  85. return ensureArray(test.extensionDevelopmentPath?.slice() || this.dir).map((p) => isAbsolute(p) ? p : join(this.dir, p));
  86. }
  87. }