feat(providence): improve dashboard

This commit is contained in:
Thijs Louisse 2021-11-16 11:48:13 +01:00
parent 5d275205c6
commit 96ae18c487
17 changed files with 332 additions and 209 deletions

View file

@ -0,0 +1,12 @@
---
'providence-analytics': minor
---
Improved dashboard:
- allows to configure categories in `providence.conf.(m)js`that show up in dashboard
- exposes dashboard in cli: `npx providence dashboard`
BREAKING CHANGES:
- `providence.conf.(m)js` must be in ESM format.

View file

@ -12,7 +12,7 @@ trim_trailing_whitespace = true
indent_size = unset
trim_trailing_whitespace = false
[*.{html,js,md,mdx}]
[*.{html,js,mjs,md,mdx}]
block_comment_start = /**
block_comment = *
block_comment_end = */
@ -23,4 +23,4 @@ indent_style = unset
indent_size = unset
end_of_line = unset
insert_final_newline = unset
trim_trailing_whitespace = unset
trim_trailing_whitespace = unset

View file

@ -1,7 +1,11 @@
# Node Tools >> Providence Analytics >> Local configuration ||40
The file `providence.conf.js` is read by providence cli and by the dashboard to get all
default configurations.
The Providence configuration file is read by providence cli (optional) and by the dashboard (required).
It has a few requirements:
- it must be called `providence.conf.js` or `providence.conf.mjs`
- it must be in ESM format
- it must be located in the root of a repository (under `process.cwd()`)
## Meta data
@ -11,6 +15,7 @@ Based on the filePath of a result, a category can be added.
For example:
```js
export default {
metaConfig: {
categoryConfig: [
{
@ -29,6 +34,7 @@ For example:
},
],
},
}
```
> N.B. category info is regarded as subjective, therefore it's advised to move this away from
@ -38,7 +44,7 @@ For example:
### referenceCollections
A list of file system paths. They can be defined relative from the current project root (`process.cwd()`) or they can be full paths.
A list of file system paths. They can be defined relative from the current project root or they can be full paths.
When a [MatchAnalyzer](../../../docs/node-tools/providence-analytics/analyzer.md) like `match-imports` or `match-subclasses` is used, the default reference(s) can be configured here. For instance: ['/path/to/@lion/form']
An example:
@ -57,6 +63,6 @@ An example:
### searchTargetCollections
A list of file system paths. They can be defined relative from the current project root
(`process.cwd()`) or they can be full paths.
or they can be full paths.
When not defined, the current project will be the search target (this is most common when
providence is used as a dev dependency).

View file

@ -6,19 +6,17 @@ application.
## Run
Start the dashboard via `npm run dashboard` to automatically open the browser and start the dashboard.
Start the dashboard via `providence dashboard` to automatically open the browser and start the dashboard.
## Interface
- Select all reference projects
- Select all target projects
Press `show table` to see the result based on the updated configuration.
### Generate csv
When `get csv` is pressed, a `.csv` will be downloaded that can be loaded into Excel.
## Analyzer support
Currently, only the `match-imports` is supported, more analyzers will be added in the future.
Currently, `match-imports` and `match-subclasses` are supported, more analyzers will be added in the future.

View file

@ -38,11 +38,11 @@ npm i --save-dev providence-analytics
```json
"scripts": {
"providence": "providence analyze match-imports -r 'node_modules/@lion/*'",
"providence:match-imports": "providence analyze match-imports -r 'node_modules/@lion/*'",
}
```
> The example above illustrates how to run the "match-imports" analyzer for reference project 'lion-based-ui'. Note that it is possible to run other analyzers and configurations supported by providence as well. For a full overview of cli options, run `providence --help`. All supported analyzers will be viewed when running `providence analyze`
> The example above illustrates how to run the "match-imports" analyzer for reference project 'lion-based-ui'. Note that it is possible to run other analyzers and configurations supported by providence as well. For a full overview of cli options, run `npx providence --help`. All supported analyzers will be viewed when running `npx providence analyze`
You are now ready to use providence in your project. All
data will be stored in json files in the folder `./providence-output`
@ -57,20 +57,21 @@ data will be stored in json files in the folder `./providence-output`
...
"scripts": {
...
"providence:dashboard": "node node_modules/providence/dashboard/src/server.js"
"providence:dashboard": "providence dashboard"
}
```
### Add providence.conf.js
```js
const providenceConfig = {
export default {
referenceCollections: {
'lion-based-ui collection': ['./node_modules/lion-based-ui'],
'lion-based-ui-collection': [
'./node_modules/lion-based-ui/packages/x',
'./node_modules/lion-based-ui/packages/y',
],
},
};
module.exports = providenceConfig;
```
Run `npm run providence:dashboard`
@ -124,16 +125,16 @@ Providence requires a queries as input.
Queries are defined as objects and can be of two types:
- feature-query
- analyzer
- ast-analyzer
A `queryConfig` is required as input to run the `providenceMain` function.
A `QueryConfig` is required as input to run the `providenceMain` function.
This object specifies the type of query and contains the relevant meta
information that will later be outputted in the `QueryResult` (the JSON object that
the `providenceMain` function returns.)
## Analyzer Query
Analyzers queries are also created via `queryConfig`s.
Analyzer queries are also created via `QueryConfig`s.
Analyzers can be described as predefined queries that use AST traversal.
@ -147,12 +148,14 @@ Now you will get a list of all predefined analyzers:
- find-imports
- find-exports
- find-classes
- match-imports
- find-subclasses
- match-subclasses
- etc...
![Analyzer query](./assets/analyzer-query.gif 'Analyzer query')
<!--
## Running providence from its own repo
### How to add a new search target project
@ -186,3 +189,4 @@ Please run:
```bash
sh ./rm-submodule.sh <path/to/submodule>
```
-->

View file

@ -1,3 +1,4 @@
/* eslint-disable lit-a11y/no-invalid-change-handler */
/* eslint-disable max-classes-per-file */
import { LitElement, html, css } from 'lit-element';
import { tooltip as tooltipStyles } from './styles/tooltip.css.js';
@ -15,6 +16,31 @@ PTable.decorateStyles(tableDecoration);
customElements.define('p-table', PTable);
/**
*
* @param {{ project:string, filePath:string, name:string }} specifierRes
* @param {{ categoryConfig:object }} metaConfig
* @returns {string[]}
*/
function getCategoriesForMatchedSpecifier(specifierRes, { metaConfig }) {
const resultCats = [];
if (metaConfig && metaConfig.categoryConfig) {
const { project, filePath, name } = specifierRes.exportSpecifier;
// First of all, do we have a matching project?
// TODO: we should allow different configs for different (major) versions
const match = metaConfig.categoryConfig.find(cat => cat.project === project);
if (match) {
Object.entries(match.categories).forEach(([categoryName, matchFn]) => {
if (matchFn(filePath, name)) {
resultCats.push(categoryName);
}
});
}
}
return resultCats;
}
function checkedValues(checkboxOrNodeList) {
if (!checkboxOrNodeList.length) {
return checkboxOrNodeList.checked && checkboxOrNodeList.value;
@ -148,7 +174,7 @@ class PBoard extends DecorateMixin(LitElement) {
_activeAnalyzerSelectTemplate() {
return html`
<select id="active-analyzer">
<select id="active-analyzer" @change="${this._onActiveAnalyzerChanged}">
${Object.keys(this.__resultFiles).map(
analyzerName => html` <option value="${analyzerName}">${analyzerName}</option> `,
)}
@ -156,6 +182,10 @@ class PBoard extends DecorateMixin(LitElement) {
`;
}
_onActiveAnalyzerChanged() {
this._aggregateResults();
}
get _selectionMenuFormNode() {
return this.shadowRoot.getElementById('selection-menu-form');
}
@ -228,7 +258,7 @@ class PBoard extends DecorateMixin(LitElement) {
async __init() {
await this.__fetchMenuData();
await this.__fetchResults();
// await this.__fetchProvidenceConf();
await this.__fetchProvidenceConf();
this._enrichMenuData();
}
@ -258,36 +288,20 @@ class PBoard extends DecorateMixin(LitElement) {
const activeRepos = [...new Set(checkedValues(repos))];
const activeAnalyzer = this._activeAnalyzerNode.value;
const totalQueryOutput = this.__aggregateResultData(activeRefs, activeRepos, activeAnalyzer);
// function addCategories(specifierRes, metaConfig) {
// const resultCats = [];
// if (metaConfig.categoryConfig) {
// const { project, filePath, name } = specifierRes.exportSpecifier;
// // First of all, do we have a matching project?
// // TODO: we should allow different configs for different (major) versions
// const match = metaConfig.categoryConfig.find(cat => cat.project === project);
// console.log('match', match);
// if (match) {
// Object.entries(match.categories, ([categoryName, matchFn]) => {
// if (matchFn(filePath, name)) {
// resultCats.push(categoryName);
// }
// });
// }
// }
// console.log('resultCats', resultCats, metaConfig);
// return resultCats;
// }
// Prepare viewData
const dataResult = [];
// When we support more analyzers than match-imports and match-subclasses, make a switch
// here
totalQueryOutput.forEach((specifierRes, i) => {
dataResult[i] = {};
dataResult[i].specifier = specifierRes.exportSpecifier;
dataResult[i].sourceProject = specifierRes.exportSpecifier.project;
// dataResult[i].categories = undefined; // addCategories(specifierRes, this.__providenceConf);
dataResult[i].categories = getCategoriesForMatchedSpecifier(
specifierRes,
this.__providenceConf,
);
dataResult[i].type = specifierRes.exportSpecifier.name === '[file]' ? 'file' : 'specifier';
dataResult[i].count = specifierRes.matchesPerProject
.map(mpp => mpp.files)
@ -299,6 +313,7 @@ class PBoard extends DecorateMixin(LitElement) {
__aggregateResultData(activeRefs, activeRepos, activeAnalyzer) {
const jsonResultsActiveFilter = [];
activeRefs.forEach(ref => {
const refSearch = `_${ref.replace('#', '_')}_`;
activeRepos.forEach(dep => {
@ -419,13 +434,15 @@ class PBoard extends DecorateMixin(LitElement) {
}
async __fetchMenuData() {
// Derived from providence.conf.js
// Derived from providence.conf.js, generated in server.mjs
this.__initialMenuData = await fetch('/menu-data').then(response => response.json());
}
async __fetchProvidenceConf() {
// Gets an
this.__providenceConf = await fetch('/providence.conf.js').then(response => response.json());
// Gets the providence conf as defined by the end user in providence-conf.(m)js
// @ts-ignore
// eslint-disable-next-line import/no-absolute-path
this.__providenceConf = (await import('/providence-conf.js')).default;
}
async __fetchResults() {

View file

@ -0,0 +1,9 @@
// @ts-ignore
const { LogService } = require('../../src/program/services/LogService.js');
LogService.warn(
'Running via "dashboard/src/server.js" is deprecated. Please run "providence dashboard" instead.',
);
// @ts-ignore
import('./server.mjs');

View file

@ -0,0 +1,161 @@
import fs from 'fs';
import pathLib, { dirname } from 'path';
import { fileURLToPath } from 'url';
import { createConfig, startServer } from 'es-dev-server';
// eslint-disable-next-line import/no-unresolved
import { ReportService } from '../../src/program/services/ReportService.js';
// eslint-disable-next-line import/no-unresolved
import { getProvidenceConf } from '../../src/program/utils/get-providence-conf.mjs';
const __dirname = dirname(fileURLToPath(import.meta.url));
/**
* Gets all results found in cache folder with all results
* @param {{ supportedAnalyzers: `match-${string}`[], resultsPath: string }} options
*/
async function getCachedProvidenceResults({
supportedAnalyzers = ['match-imports', 'match-subclasses'],
resultsPath = ReportService.outputPath,
} = {}) {
/**
* Paths of every individual cachde result
* @type {string[]}
*/
let outputFilePaths;
try {
outputFilePaths = fs.readdirSync(resultsPath);
} catch (_) {
throw new Error(`Please make sure providence results can be found in ${resultsPath}`);
}
const resultFiles = {};
let searchTargetDeps;
outputFilePaths.forEach(fileName => {
const content = JSON.parse(fs.readFileSync(pathLib.join(resultsPath, fileName), 'utf-8'));
if (fileName === 'search-target-deps-file.json') {
searchTargetDeps = content;
} else {
const analyzerName = fileName.split('_-_')[0];
if (!supportedAnalyzers.includes(analyzerName)) {
return;
}
if (!resultFiles[analyzerName]) {
resultFiles[analyzerName] = [];
}
resultFiles[analyzerName].push({ fileName, content });
}
});
return { searchTargetDeps, resultFiles };
}
/**
* @param {{ providenceConf: object; earchTargetDeps: object; resultFiles: string[]; }}
*/
function createMiddleWares({ providenceConf, providenceConfRaw, searchTargetDeps, resultFiles }) {
/**
* @param {string} projectPath
* @returns {object|null}
*/
function getPackageJson(projectPath) {
try {
const file = pathLib.resolve(projectPath, 'package.json');
return JSON.parse(fs.readFileSync(file, 'utf8'));
} catch (_) {
return null;
}
}
/**
* @param {object[]} collections
* @returns {{[keu as string]: }}
*/
function transformToProjectNames(collections) {
const res = {};
// eslint-disable-next-line array-callback-return
Object.entries(collections).map(([key, val]) => {
res[key] = val.map(c => {
const pkg = getPackageJson(c);
return pkg && pkg.name;
});
});
return res;
}
const pathFromServerRootToHere = `/${pathLib.relative(process.cwd(), __dirname)}`;
return [
// eslint-disable-next-line consistent-return
async (ctx, next) => {
// TODO: Quick and dirty solution: refactor in a nicer way
if (ctx.url.startsWith('/app')) {
ctx.url = `${pathFromServerRootToHere}/${ctx.url}`;
return next();
}
if (ctx.url === '/') {
ctx.url = `${pathFromServerRootToHere}/index.html`;
return next();
}
if (ctx.url === '/results') {
ctx.body = resultFiles;
} else if (ctx.url === '/menu-data') {
// Gathers all data that are relevant to create a configuration menu
// at the top of the dashboard:
// - referenceCollections as defined in providence.conf.js
// - searchTargetCollections (aka programs) as defined in providence.conf.js
// - searchTargetDeps as found in search-target-deps-file.json
// Also do some processing on the presentation of a project, so that it can be easily
// outputted in frontend
let searchTargetCollections;
if (providenceConf.searchTargetCollections) {
searchTargetCollections = transformToProjectNames(providenceConf.searchTargetCollections);
} else {
searchTargetCollections = Object.keys(searchTargetDeps).map(d => d.split('#')[0]);
}
const menuData = {
// N.B. theoratically there can be a mismatch between basename and pkgJson.name,
// but we assume folder names and pkgJson.names to be similar
searchTargetCollections,
referenceCollections: transformToProjectNames(providenceConf.referenceCollections),
searchTargetDeps,
};
ctx.body = menuData;
} else if (ctx.url === '/providence-conf.js') {
// Alloes frontend dasbboard app to find categoriesand other configs
ctx.type = 'text/javascript';
ctx.body = providenceConfRaw;
} else {
await next();
}
},
];
}
(async function main() {
const { providenceConf, providenceConfRaw } = await getProvidenceConf();
const { searchTargetDeps, resultFiles } = await getCachedProvidenceResults();
// Needed for dev purposes (we call it from ./packages-node/providence-analytics/ instead of ./)
// Allows es-dev-server to find the right moduleDirs
const fromPackageRoot = process.argv.includes('--serve-from-package-root');
const moduleRoot = fromPackageRoot ? pathLib.resolve(process.cwd(), '../../') : process.cwd();
const config = createConfig({
port: 8080,
appIndex: pathLib.resolve(__dirname, 'index.html'),
rootDir: moduleRoot,
nodeResolve: true,
moduleDirs: pathLib.resolve(moduleRoot, 'node_modules'),
watch: false,
open: true,
middlewares: createMiddleWares({
providenceConf,
providenceConfRaw,
searchTargetDeps,
resultFiles,
}),
});
await startServer(config);
})();

View file

@ -1,134 +0,0 @@
const fs = require('fs');
const pathLib = require('path');
const { createConfig, startServer } = require('es-dev-server');
const { ReportService } = require('../../src/program/services/ReportService.js');
const { LogService } = require('../../src/program/services/LogService.js');
// eslint-disable-next-line import/no-dynamic-require
const providenceConf = require(`${pathLib.join(process.cwd(), 'providence.conf.js')}`);
let outputFilePaths;
try {
outputFilePaths = fs.readdirSync(ReportService.outputPath);
} catch (_) {
LogService.error(
`Please make sure providence results can be found in ${ReportService.outputPath}`,
);
process.exit(1);
}
const resultFiles = {};
let searchTargetDeps;
const supportedAnalyzers = ['match-imports', 'match-subclasses'];
outputFilePaths.forEach(fileName => {
const content = JSON.parse(
fs.readFileSync(pathLib.join(ReportService.outputPath, fileName), 'utf-8'),
);
if (fileName === 'search-target-deps-file.json') {
searchTargetDeps = content;
} else {
const analyzerName = fileName.split('_-_')[0];
if (!supportedAnalyzers.includes(analyzerName)) {
return;
}
if (!resultFiles[analyzerName]) {
resultFiles[analyzerName] = [];
}
resultFiles[analyzerName].push({ fileName, content });
}
});
function getPackageJson(projectPath) {
let pkgJson;
try {
const file = pathLib.resolve(projectPath, 'package.json');
pkgJson = JSON.parse(fs.readFileSync(file, 'utf8'));
} catch (_) {
// eslint-disable-next-line no-empty
}
return pkgJson;
}
function transformToProjectNames(collections) {
const res = {};
// eslint-disable-next-line array-callback-return
Object.entries(collections).map(([key, val]) => {
res[key] = val.map(c => {
const pkg = getPackageJson(c);
return pkg && pkg.name;
});
});
return res;
}
const pathFromServerRootToHere = `/${pathLib.relative(process.cwd(), __dirname)}`;
// Needed for dev purposes (we call it from ./packages-node/providence-analytics/ instead of ./)
// Allows es-dev-server to find the right moduleDirs
const fromPackageRoot = process.argv.includes('--serve-from-package-root');
const moduleRoot = fromPackageRoot ? pathLib.resolve(process.cwd(), '../../') : process.cwd();
const config = createConfig({
port: 8080,
appIndex: pathLib.resolve(__dirname, 'index.html'),
rootDir: moduleRoot,
nodeResolve: true,
moduleDirs: pathLib.resolve(moduleRoot, 'node_modules'),
watch: false,
open: true,
middlewares: [
// eslint-disable-next-line consistent-return
async (ctx, next) => {
// TODO: Quick and dirty solution: refactor in a nicer way
if (ctx.url.startsWith('/app')) {
ctx.url = `${pathFromServerRootToHere}/${ctx.url}`;
return next();
}
if (ctx.url === '/') {
ctx.url = `${pathFromServerRootToHere}/index.html`;
return next();
}
if (ctx.url === '/results') {
ctx.body = resultFiles;
} else if (ctx.url === '/menu-data') {
// Gathers all data that are relevant to create a configuration menu
// at the top of the dashboard:
// - referenceCollections as defined in providence.conf.js
// - searchTargetCollections (aka programs) as defined in providence.conf.js
// - searchTargetDeps as found in search-target-deps-file.json
// Also do some processing on the presentation of a project, so that it can be easily
// outputted in frontend
let searchTargetCollections;
if (providenceConf.searchTargetCollections) {
searchTargetCollections = transformToProjectNames(providenceConf.searchTargetCollections);
} else {
searchTargetCollections = Object.keys(searchTargetDeps).map(d => d.split('#')[0]);
}
const menuData = {
// N.B. theoratically there can be a mismatch between basename and pkgJson.name,
// but we assume folder names and pkgJson.names to be similar
searchTargetCollections,
referenceCollections: transformToProjectNames(providenceConf.referenceCollections),
searchTargetDeps,
};
ctx.body = menuData;
} else if (ctx.url === '/providence.conf.js') {
// We need to fetch it via server, since it's CommonJS vs es modules...
// require("@babel/core").transform("code", {
// plugins: ["@babel/plugin-transform-modules-commonjs"]
// });
// Gives back categories from providence.conf
ctx.body = providenceConf.metaConfig;
} else {
await next();
}
},
],
});
(async () => {
await startServer(config);
})();

View file

@ -12,7 +12,7 @@
},
"main": "./src/program/providence.js",
"bin": {
"providence": "./src/cli/index.js"
"providence": "./src/cli/index.mjs"
},
"files": [
"dashboard/src",

View file

@ -1,5 +1,5 @@
const pathLib = require('path');
const fs = require('fs');
import pathLib from 'path';
import fs from 'fs';
// This file is read by dashboard and cli and needs to be present under process.cwd()
// It mainly serves as an example and it allows to run the dashboard locally
@ -30,7 +30,7 @@ function getAllLionScopedPackagePaths() {
const lionScopedPackagePaths = getAllLionScopedPackagePaths();
const providenceConfig = {
export default {
metaConfig: {
categoryConfig: [
{
@ -65,5 +65,3 @@ const providenceConfig = {
'@lion-references': lionScopedPackagePaths,
},
};
module.exports = providenceConfig;

View file

@ -13,11 +13,11 @@ const cliHelpers = require('./cli-helpers.js');
const extendDocsModule = require('./launch-providence-with-extend-docs.js');
const { toPosixPath } = require('../program/utils/to-posix-path.js');
const { extensionsFromCs, setQueryMethod, targetDefault, installDeps } = cliHelpers;
const { extensionsFromCs, setQueryMethod, targetDefault, installDeps, spawnProcess } = cliHelpers;
const { version } = require('../../package.json');
async function cli({ cwd } = {}) {
async function cli({ cwd, providenceConf } = {}) {
let resolveCli;
let rejectCli;
@ -35,7 +35,8 @@ async function cli({ cwd } = {}) {
/** @type {object} */
let regexSearchOptions;
const externalConfig = InputDataService.getExternalConfig();
// TODO: change back to "InputDataService.getExternalConfig();" once full package ESM
const externalConfig = providenceConf;
async function getQueryInputData(
/* eslint-disable no-shadow */
@ -153,6 +154,15 @@ async function cli({ cwd } = {}) {
}
}
async function runDashboard() {
const pathFromServerRootToDashboard = `${pathLib.relative(
process.cwd(),
pathLib.resolve(__dirname, '../../dashboard'),
)}`;
spawnProcess(`node ${pathFromServerRootToDashboard}/src/server.mjs`);
}
commander
.version(version, '-v, --version')
.option('-e, --extensions [extensions]', 'extensions like "js,html"', extensionsFromCs, [
@ -323,6 +333,16 @@ async function cli({ cwd } = {}) {
manageSearchTargets(options);
});
commander
.command('dashboard')
.description(
`Runs an interactive dashboard that shows all aggregated data from proivdence-output, configured
via providence.conf`,
)
.action(() => {
runDashboard();
});
commander.parse(process.argv);
await cliPromise;

View file

@ -1,4 +0,0 @@
#!/usr/bin/env node
const { cli } = require('./cli.js');
cli();

View file

@ -0,0 +1,9 @@
#!/usr/bin/env node
import { cli } from './cli.js';
import { getProvidenceConf } from '../program/utils/get-providence-conf.mjs';
(async () => {
// We need to provide config to cli, until whole package is rewritten as ESM.
const { providenceConf } = await getProvidenceConf();
cli({ providenceConf });
})();

View file

@ -152,13 +152,6 @@ function multiGlobSync(patterns, { keepDirs = false, root } = {}) {
return Array.from(res);
}
/**
* @typedef {Object} ProjectData
* @property {string} project project name
* @property {string} path full path to project folder
* @property {string[]} entries all file paths within project folder
*/
/**
* To be used in main program.
* It creates an instance on which the 'files' array is stored.
@ -456,12 +449,9 @@ class InputDataService {
* @desc Allows the user to provide a providence.conf.js file in its repository root
*/
static getExternalConfig() {
try {
// eslint-disable-next-line import/no-dynamic-require, global-require
return require(`${process.cwd()}/providence.conf.js`);
} catch (e) {
return null;
}
throw new Error(
`[InputDataService.getExternalConfig]: Until fully ESM: use 'src/program/utils/get-providence=conf.mjs instead`,
);
}
/**

View file

@ -0,0 +1,37 @@
import pathLib from 'path';
import fs from 'fs';
/**
* @returns {Promise<object|null>}
*/
export async function getProvidenceConf() {
const confPathWithoutExtension = `${pathLib.join(process.cwd(), 'providence.conf')}`;
let confPathFound;
try {
if (fs.existsSync(`${confPathWithoutExtension}.js`)) {
confPathFound = `${confPathWithoutExtension}.js`;
} else if (fs.existsSync(`${confPathWithoutExtension}.mjs`)) {
confPathFound = `${confPathWithoutExtension}.mjs`;
}
} catch (_) {
throw new Error(
`Please provide ${confPathWithoutExtension}.js or ${confPathWithoutExtension}.mjs`,
);
}
if (!confPathFound) {
return null;
}
const { default: providenceConf } = await import(confPathFound);
if (!providenceConf) {
throw new Error(
`providence.conf.js file should be in es module format (so it can be read by a browser).
So use "export default {}" instead of "module.exports = {}"`,
);
}
const providenceConfRaw = fs.readFileSync(confPathFound, 'utf8');
return { providenceConf, providenceConfRaw };
}

View file

@ -322,13 +322,13 @@ describe('Providence CLI', () => {
it('"-c --config"', async () => {
await runCli(`analyze mock-analyzer -c {"a":"2"}`, rootDir);
expect(qConfStub.args[0][0]).to.equal('mock-analyzer');
expect(qConfStub.args[0][1]).to.eql({ a: '2', metaConfig: undefined });
expect(qConfStub.args[0][1]).to.eql({ a: '2', metaConfig: {} });
qConfStub.resetHistory();
await runCli(`analyze mock-analyzer --config {"a":"2"}`, rootDir);
expect(qConfStub.args[0][0]).to.equal('mock-analyzer');
expect(qConfStub.args[0][1]).to.eql({ a: '2', metaConfig: undefined });
expect(qConfStub.args[0][1]).to.eql({ a: '2', metaConfig: {} });
});
it('calls "promptAnalyzerConfigMenu" without config given', async () => {