@omlet/cli
Version:
Omlet (https://omlet.dev) is a component analytics tool that uses a CLI to scan your codebase to detect components and their usage. Get real usage insights from customizable charts to measure adoption across all projects and identify opportunities to impr
272 lines (271 loc) • 10.1 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadConfig = exports.DEFAULT_IGNORE_PARAM = exports.DEFAULT_INCLUDE_PARAM = exports.ConfigValidationError = exports.ConfigError = void 0;
const ajv_1 = __importStar(require("ajv"));
const cosmiconfig_1 = require("cosmiconfig");
const promises_1 = __importDefault(require("fs/promises"));
const upath_1 = __importDefault(require("upath"));
const stringUtils_1 = require("../stringUtils");
const ajv = new ajv_1.default();
// Copied from https://github.com/SchemaStore/schemastore/blob/d407be00d8784dadaa14a6df30b824c1499a9742/src/schemas/json/omletrc.json
// Please update the original schema if you need to make changes.
const userSchema = {
"$async": true,
"type": "object",
"properties": {
"$schema": {
"type": "string",
"description": "The schema that the configuration file uses.",
"const": "https://json.schemastore.org/omletrc.json",
},
"include": {
"type": "array",
"description": "Filenames or glob patterns that will be included in the scan.",
"items": { "type": "string" },
"minItems": 1,
},
"ignore": {
"type": "array",
"description": "Filenames or glob patterns that will be excluded from the scan.",
"items": { "type": "string" },
},
"tsconfigPath": { "$ref": "#/definitions/tsconfigPath" },
"aliases": { "$ref": "#/definitions/aliases" },
"exports": { "$ref": "#/definitions/exports" },
"workspaces": {
"type": "object",
"description": "Package-specific configurations if you have a monorepo.",
"additionalProperties": {
"type": "object",
"description": "Package-specific configuration.",
"properties": {
"tsconfigPath": { "$ref": "#/definitions/tsconfigPath" },
"aliases": { "$ref": "#/definitions/aliases" },
"exports": { "$ref": "#/definitions/exports" },
},
"additionalProperties": false,
},
},
"hookScript": { "type": "string" },
},
"additionalProperties": false,
"definitions": {
"tsconfigPath": {
"type": "string",
"description": "Path to your tsconfig file.",
},
"aliases": {
"type": "object",
"additionalProperties": {
"oneOf": [
{
"type": "string",
},
{
"type": "array",
"items": {
"type": "string",
},
},
],
},
},
"exports": {
"oneOf": [
{
"type": "string",
},
{
"type": "array",
"items": {
"type": "string",
},
},
{
"type": "object",
"additionalProperties": {
"oneOf": [
{
"type": "string",
},
{
"type": "array",
"items": {
"type": "string",
},
},
],
},
},
],
},
},
};
function transformAliases(aliases) {
if (aliases === undefined) {
return aliases;
}
return Object.fromEntries(Object.entries(aliases).map(([name, paths]) => [name, Array.isArray(paths) ? paths : [paths]]));
}
function transformExportsMap(exports) {
if (exports === undefined) {
return;
}
if (typeof exports === "string") {
return {
".": [exports],
};
}
else if (Array.isArray(exports)) {
return {
".": exports,
};
}
return Object.fromEntries(Object.entries(exports).map(([name, paths]) => [name, Array.isArray(paths) ? paths : [paths]]));
}
function transformPackageConfig(config) {
const { tsconfigPath, aliases, exports, } = config;
return {
tsconfigPath,
aliases: transformAliases(aliases),
exports: transformExportsMap(exports),
};
}
function transformConfig(userConfig, configPath) {
const { include = exports.DEFAULT_INCLUDE_PARAM, ignore = exports.DEFAULT_IGNORE_PARAM, workspaces = {}, hookScript, ...rootPackageConfig } = userConfig;
return {
configPath: configPath,
include: [...new Set(include)],
ignore: [...new Set(ignore.concat(exports.DEFAULT_IGNORE_PARAM))],
hookScript,
...transformPackageConfig(rootPackageConfig),
workspaces: Object.fromEntries(Object.entries(workspaces).map(([key, packageConfig]) => [key, transformPackageConfig(packageConfig)])),
};
}
class ConfigError extends Error {
constructor(message, { reason } = {}) {
super(message);
this.name = this.constructor.name;
this.reason = reason;
}
}
exports.ConfigError = ConfigError;
function formatFieldPath(path) {
return path.replace(/^\//, "");
}
function getExpectedTypes(errors) {
const types = errors.map((error) => {
if (error.keyword === "type") {
return `"${error.params.type}"`;
}
return undefined;
}).filter((type) => type !== undefined);
if (types.length < 3) {
return types.join(" or ");
}
return `${types.slice(0, -1).join(", ")}, or ${types[types.length - 1]}`;
}
class ConfigValidationError extends Error {
constructor(reason) {
super(ConfigValidationError.getErrorMessage(reason));
this.name = this.constructor.name;
this.reason = reason;
}
static getErrorMessage(reason) {
if (!(reason instanceof ajv_1.ValidationError) || !reason.errors[0]) {
return "Unexpected error while validating config file";
}
const definedErrors = reason.errors;
const firstError = definedErrors[0];
switch (firstError.keyword) {
case "additionalProperties":
return `The field "${formatFieldPath(`${firstError.instancePath}/${firstError.params.additionalProperty}`)}" is unknown.`;
case "minItems":
return `The field "${formatFieldPath(firstError.instancePath)}" needs at least ${(0, stringUtils_1.pluralize)("item", firstError.params.limit)}.`;
case "oneOf":
case "type":
return `The field "${formatFieldPath(firstError.instancePath)}" must be of type ${getExpectedTypes(definedErrors)}.`;
default:
return "Unexpected error while validating config file";
}
}
}
exports.ConfigValidationError = ConfigValidationError;
async function readConfigFile(repoRoot, projectRoot, configPath) {
const explorer = (0, cosmiconfig_1.cosmiconfig)("omlet", { stopDir: repoRoot });
let explorerResult;
if (configPath) {
let isFile = false;
try {
isFile = (await promises_1.default.lstat(configPath)).isFile();
}
catch (error) {
throw new ConfigError(`Cannot locate config file at ${configPath}`, { reason: error });
}
if (!isFile) {
throw new ConfigError(`Config file path is a directory: ${configPath}`);
}
explorerResult = await explorer.load(configPath);
}
else {
explorerResult = await explorer.search(projectRoot);
}
if (!explorerResult) {
return null;
}
const { config, filepath } = explorerResult;
const validate = ajv.compile(userSchema);
try {
await validate(config);
}
catch (error) {
throw new ConfigValidationError(error);
}
return { config, path: filepath };
}
exports.DEFAULT_INCLUDE_PARAM = [
"**/*.{js,jsx,ts,tsx}",
];
exports.DEFAULT_IGNORE_PARAM = [
"**/node_modules/**",
"**/*.d.ts",
"**/stories/**/*",
"**/.storybook/**/*",
"**/*.stories.{jsx,tsx,js,ts}",
"**/*.{spec,test}.{jsx,tsx,js,ts}",
"**/{__test__,tests}/**/*.{jsx,tsx,js,ts}",
];
async function loadConfig(repoRoot, projectRoot, cliParams, configPath) {
let config = Object.fromEntries(Object.entries(cliParams).filter(([, value]) => value !== undefined));
let userConfigPath;
const result = await readConfigFile(repoRoot, projectRoot, configPath);
if (result) {
// Override user-defined config with CLI parameters
config = Object.assign(result.config, config);
userConfigPath = upath_1.default.relative(projectRoot, result.path);
}
return transformConfig(config, userConfigPath);
}
exports.loadConfig = loadConfig;