@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
395 lines (394 loc) • 17.6 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 });
require("core-js/es");
const ci_info_1 = __importDefault(require("ci-info"));
const clr = __importStar(require("colorette"));
const commander_1 = __importStar(require("commander"));
const inquirer_1 = __importDefault(require("inquirer"));
const is_docker_1 = __importDefault(require("is-docker"));
const upath_1 = __importDefault(require("upath"));
const analyzer_1 = require("./analyzer");
const auth_1 = require("./auth");
const config_1 = require("./config");
const error_1 = require("./error");
const logger_1 = require("./logger");
const npmUtils_1 = require("./npmUtils");
const projectUtils_1 = require("./projectUtils");
const repoUtils_1 = require("./repoUtils");
const tracking_1 = require("./tracking");
const CLI_VERSION = (0, npmUtils_1.getCliVersion)();
async function beforeExit(statusCode) {
await (0, logger_1.endLogger)();
process.exitCode = statusCode;
}
function accumulateValues(value, previous = []) {
return previous.concat(value);
}
async function getRootPackagePath(input) {
var _a;
if (!input) {
const root = (0, projectUtils_1.getDefaultRoot)();
return (_a = await (0, projectUtils_1.findParentProject)(root)) !== null && _a !== void 0 ? _a : root;
}
const absolutePath = upath_1.default.resolve(process.cwd(), input);
if (ci_info_1.default.isCI) {
return absolutePath;
}
const parent = await (0, projectUtils_1.findParentProject)(absolutePath);
if (!parent) {
return absolutePath;
}
console.log(`Looks like the package is part of the monorepo at ${clr.bold(parent)}.`);
const { continueWithParent } = (await inquirer_1.default.prompt([{
type: "confirm",
message: `Continue with the root package instead? ${clr.reset(clr.dim("(Recommended)"))}`,
prefix: clr.bold(clr.yellow("?")),
name: "continueWithParent",
default: true,
}]));
return continueWithParent ? parent : absolutePath;
}
var LoginResult;
(function (LoginResult) {
LoginResult[LoginResult["SUCCESS"] = 0] = "SUCCESS";
LoginResult[LoginResult["TERMINATED"] = 1] = "TERMINATED";
})(LoginResult || (LoginResult = {}));
async function loginPrompt(isRemote) {
const prompt = (await inquirer_1.default.prompt([{
type: "confirm",
message: "Looks like you need to login first. Do you want to continue?",
prefix: "",
name: "continueLogin",
default: true,
}]));
if (!prompt.continueLogin) {
return LoginResult.TERMINATED;
}
await (0, auth_1.login)(Number.parseInt(config_1.LOGIN_SERVER_PORT, 10), isRemote);
return LoginResult.SUCCESS;
}
function isHeadless() {
return (0, is_docker_1.default)() || isSSH();
}
function isSSH() {
return Boolean(process.env.SSH_CLIENT || process.env.SSH_TTY);
}
const program = new commander_1.default.Command();
program
.name((0, npmUtils_1.getExecutableName)())
.version(CLI_VERSION);
program.command("parse", { hidden: true })
.description("Parse JS/TS modules and extract component information")
.option("-r, --root <path>", "Path to the repository's root directory")
.option("-i, --include <glob-pattern>", "List of glob patterns for input files", accumulateValues)
.option("-c, --config <path>", "Path to the configuration file. See https://feta.omlet.dev/l/docs/cli-config for details.")
.option("--ignore <glob-pattern>", "List of ignore patterns for skipping input files", accumulateValues, [])
.option("--log-level <error|warn|info|debug|trace>", "Specify log level for the CLI (default: \"error\")", logger_1.LogLevel.Error)
.option("-v, --verbose", "Run the CLI in debug mode", false)
.option("--tsconfig-path <path>", "Path to the tsconfig.json file")
.action(async (args) => {
const logLevel = args.verbose ? logger_1.LogLevel.Debug : args.logLevel;
if (logLevel) {
(0, logger_1.setLogLevel)(logLevel);
}
const rootPackagePath = await getRootPackagePath(args.root);
const repoRoot = (0, repoUtils_1.getGitRoot)(rootPackagePath);
const configParams = {
include: args.include,
ignore: args.ignore,
tsconfigPath: args.tsconfigPath,
};
const configPath = args.config && upath_1.default.resolve(process.cwd(), args.config);
const config = await (0, config_1.loadConfig)(repoRoot, rootPackagePath, configParams, configPath);
const result = await (0, analyzer_1.parse)(rootPackagePath, config);
console.log(JSON.stringify(result, null, 2));
});
program.command("alias-config", { hidden: true })
.description("Extract alias config from the repository")
.option("-r, --root <path>", "Path to the repository's root directory")
.option("--tsconfig-path <path>", "Path to the tsconfig.json file")
.option("-c, --config <path>", "Path to the configuration file. See https://feta.omlet.dev/l/docs/cli-config for details.")
.option("--no-verify", "Skip project setup verification")
.action(async (args) => {
var _a;
try {
const rootPackagePath = await getRootPackagePath(args.root);
const repoRoot = (0, repoUtils_1.getGitRoot)(rootPackagePath);
const configParams = {
tsconfigPath: args.tsconfigPath,
};
const configPath = args.config && upath_1.default.resolve(process.cwd(), args.config);
const config = await (0, config_1.loadConfig)(repoRoot, rootPackagePath, configParams, configPath);
const resolver = await projectUtils_1.ProjectSetupResolver.create(repoRoot, rootPackagePath, config);
const projectSetup = await resolver.getProjectSetup((_a = args.verify) !== null && _a !== void 0 ? _a : true);
console.log(JSON.stringify(projectSetup, null, 2));
}
catch (e) {
const error = e;
if (error instanceof error_1.CliError) {
console.error(`${error.message}\n${error.getContextString()}`);
}
else {
console.error(`${error.message}\n${error.stack}`);
}
}
});
function promptWarnings(args) {
if (args.output) {
console.log(clr.yellow("\n-o option has been deprecated. Use --dry-run instead"));
}
if (args.core.length && args.tag.length) {
console.log(clr.yellow("\n--core and --tag options have been deprecated. To learn how to tag components, see https://feta.omlet.dev/l/docs/tag-components"));
}
else if (args.tag.length) {
console.log(clr.yellow("\n--tag option has been deprecated. To learn how to tag components, see https://feta.omlet.dev/l/docs/tag-components"));
}
else if (args.core.length) {
console.log(clr.yellow("\n--core option has been deprecated. To learn how to tag components, see https://feta.omlet.dev/l/docs/tag-components"));
}
}
function handleAuthenticationError(error) {
console.error(`${clr.red("Login failed while obtaining a token!")}\n`);
console.error(`${clr.dim(`Please try again and if the issue persists, contact us at ${config_1.OMLET_EMAIL} with the error logs:\n${(0, logger_1.getLogFilePath)()}`)}`);
(0, logger_1.logError)(error);
(0, tracking_1.trackLoginFailedError)();
}
function handleUnexpectedError(error) {
console.error(`${clr.red("Command failed with an unexpected error")}`);
console.error(`${clr.dim(`Details: ${error.message}\nPlease try again and if the issue persists, contact us at ${config_1.OMLET_EMAIL} with the error logs:\n${(0, logger_1.getLogFilePath)()}`)}`);
(0, logger_1.logError)(error);
(0, tracking_1.trackCommandFailedError)(error.message);
}
function setCommandProps(command) {
(0, tracking_1.setDefaultProps)({ command });
}
program.command("analyze")
.description("Identify components and analyze their usages across JS/TS modules")
.option("-r, --root <path>", "Path to the repository's root directory")
.option("-i, --include <glob-pattern>", "List of glob patterns to include in the analysis", accumulateValues)
.option("--ignore <glob-pattern>", "List of ignore patterns that should be omitted in the analysis", accumulateValues)
.option("-c, --config <path>", "Path to the configuration file. See https://feta.omlet.dev/l/docs/cli-config for details.")
.addOption(new commander_1.Option("-c, --core <glob-pattern>").default([]).argParser(accumulateValues).hideHelp())
.addOption(new commander_1.Option("-t, --tag <name pattern...>").default([]).argParser(accumulateValues).hideHelp())
.option("--dry-run", "Run analysis only locally and output the result in omlet.json file instead of submitting results.", false)
.addOption(new commander_1.Option("-o, --output <path|->").hideHelp())
.option("--log-level <error|warn|info|debug|trace>", "Specify log level for the CLI")
.option("-v, --verbose", "Run the CLI in debug mode", false)
.option("--tsconfig-path <path>", "Path to the tsconfig.json file")
.option("--hook-script <path>", "Path to the CLI hook script")
.option("--no-color", "Disable color highlighted output")
// .option("--no-verify", "Skip project setup verification") Setup validation and this option is disabled until we update the docs
.option("-q, --quiet", "Report errors only", false)
.option("--remote-login", "Login to Omlet remotely. Please use the option if you are running the CLI in a remote environment.", isHeadless())
.allowUnknownOption()
.action(async (args) => {
const isDryRun = !!args.output || args.dryRun;
const logLevel = args.verbose ? logger_1.LogLevel.Debug : args.logLevel;
if (logLevel) {
(0, logger_1.setLogLevel)(logLevel);
}
setCommandProps(tracking_1.Command.analyze);
try {
if (!isDryRun) {
const user = await (0, auth_1.getAuthenticatedUser)();
if (!user) {
if (ci_info_1.default.isCI) {
console.error("Authentication failed!");
console.error("Please make sure you have a working Omlet token set as the OMLET_TOKEN variable.\n");
console.error("Run `omlet login --print-token` to obtain a token.");
return beforeExit(1);
}
else {
const result = await loginPrompt(args.remoteLogin);
if (result === LoginResult.TERMINATED) {
return beforeExit(0);
}
}
}
}
}
catch (err) {
const error = err;
if (error instanceof auth_1.AuthenticationError) {
handleAuthenticationError(error);
}
else {
handleUnexpectedError(error);
}
return beforeExit(1);
}
try {
const rootPackagePath = await getRootPackagePath(args.root);
const cliParams = {
include: args.include,
ignore: args.ignore,
tsconfigPath: args.tsconfigPath,
configPath: args.config && upath_1.default.resolve(process.cwd(), args.config),
hookScript: args.hookScript,
};
await (0, analyzer_1.analyze)(rootPackagePath, {
dryRun: isDryRun,
cliVersion: CLI_VERSION,
verifySetup: config_1.OMLET_VALIDATE,
cliParams,
quiet: true, // args.quiet,
});
promptWarnings(args);
}
catch (e) {
// Error logging and output is taken care of in `analyze`
promptWarnings(args);
return beforeExit(1);
}
});
program.command("init")
.description("Start guided process to scan your repo(s) and initialize your workspace")
.option("-r, --root <path>", "Path to the repository's root directory")
.option("--log-level <error|warn|info|debug|trace>", "Specify log level for the CLI")
.option("-v, --verbose", "Run the CLI in debug mode", false)
.option("--no-color", "Disable color highlighted output")
.option("--remote-login", "Login to Omlet remotely. Please use the option if you are running the CLI in a remote environment.", isHeadless())
// .option("--no-verify", "Skip project setup verification") Setup validation and this option is disabled until we update the docs
// .option("-q, --quiet", "Report errors only", false)
.allowUnknownOption()
.action(async (args) => {
const logLevel = args.verbose ? logger_1.LogLevel.Debug : args.logLevel;
if (logLevel) {
(0, logger_1.setLogLevel)(logLevel);
}
setCommandProps(tracking_1.Command.init);
try {
if (ci_info_1.default.isCI) {
console.error("`init` command should not be used in CI environment.");
console.error("Visit https://feta.omlet.dev/l/docs/cli/init for more details.");
(0, tracking_1.trackInitInCIError)();
return beforeExit(1);
}
const user = await (0, auth_1.getAuthenticatedUser)();
if (!user) {
const result = await loginPrompt(args.remoteLogin);
if (result === LoginResult.TERMINATED) {
return beforeExit(0);
}
}
}
catch (err) {
const error = err;
if (error instanceof auth_1.AuthenticationError) {
handleAuthenticationError(error);
}
else {
handleUnexpectedError(error);
}
return beforeExit(1);
}
try {
const rootPackagePath = await getRootPackagePath(args.root);
await (0, analyzer_1.init)(rootPackagePath, {
cliVersion: CLI_VERSION,
verifySetup: config_1.OMLET_VALIDATE,
quiet: true, // args.quiet,
});
}
catch (e) {
// Error output for other error types is taken care of in `init`
return beforeExit(1);
}
});
program.command("login")
.description("Login to Omlet and get an access token")
.requiredOption("-p, --local-port <port>", "Port to be used by the local login server", config_1.LOGIN_SERVER_PORT)
.option("--print-token", "Prints token to the command line instead of saving it to the .omletrc file")
.option("--remote", "Login to Omlet remotely. Please use the option if you are running the CLI in a remote environment.", isHeadless())
.action(async (args) => {
(0, tracking_1.setDefaultProps)({
command: tracking_1.Command.login,
});
try {
const token = await (0, auth_1.login)(Number.parseInt(args.localPort, 10), args.remote);
console.log("Login successful!");
if (args.printToken) {
console.log(token);
}
}
catch (err) {
const error = err;
if (error instanceof auth_1.AuthenticationError) {
handleAuthenticationError(error);
}
else {
handleUnexpectedError(error);
}
return beforeExit(1);
}
});
program.on("command:*", () => {
program.outputHelp();
});
program.command("me")
.description("Display current logged in user")
.action(async () => {
try {
const me = await (0, auth_1.getAuthenticatedUser)();
const workspace = await (0, analyzer_1.getWorkspace)();
if (!me) {
console.error(`${clr.red("Looks like you need to login first with the 'login' command")}\n`);
return;
}
if (!workspace) {
// relevant output text is already handled in getWorkspace
return;
}
console.log(`${clr.bgGreen(clr.black("( ・ω・)☞"))} ${clr.dim("You're logged in as")} ${clr.whiteBright(me.email)} ${clr.dim("to")} ${clr.whiteBright(workspace.slug)} ${clr.dim("workspace.")}\n`);
}
catch (err) {
const error = err;
if (error) {
handleUnexpectedError(error);
}
return beforeExit(1);
}
});
// update-notifier is ESM-only, so we'll import it dynamically
(async () => {
const updateNotifierModule = await eval("import('update-notifier')");
const notifier = updateNotifierModule.default({ pkg: (0, npmUtils_1.getPackageInfo)(), updateCheckInterval: 0 });
notifier.notify();
})();
program.parseAsync(process.argv)
.then(() => beforeExit(0))
.catch(error => {
(0, logger_1.logError)(error);
return beforeExit(1);
});
function signalHandler(exitCode) {
return async (signal) => {
logger_1.logger.debug(`Received ${signal} signal. Exiting...`);
await beforeExit(exitCode);
process.exit(exitCode);
};
}
process.on("SIGINT", signalHandler(130));
process.on("SIGTERM", signalHandler(143));