@qodalis/angular-cli
Version:
A lightweight and flexible command-line interface (CLI) tool designed to streamline workflows and automate tasks for developers.
3,935 lines • 166 kB
JavaScript
import * as i0 from '@angular/core';
import { InjectionToken, Injectable, Inject, EventEmitter, Component, Input, Output, ViewChild, ViewEncapsulation, HostListener, Injector, NgModule } from '@angular/core';
import * as i1 from '@angular/common';
import { CommonModule } from '@angular/common';
import { ActivatedRoute } from '@angular/router';
import * as i2 from 'primeng/button';
import { ButtonModule } from 'primeng/button';
import { TabMenuModule } from 'primeng/tabmenu';
import { MenuModule } from 'primeng/menu';
import { SplitButtonModule } from 'primeng/splitbutton';
import * as i3 from 'primeng/tooltip';
import { TooltipModule } from 'primeng/tooltip';
import { CliForegroundColor, CliIcon, formatJson, delay, CliLogLevel, initializeBrowserEnvironment, DefaultThemes, getRightOfWord, CancellablePromise, getParameterValue, colorFirstWord, DefaultLibraryAuthor, LIBRARY_VERSION as LIBRARY_VERSION$1 } from '@qodalis/cli-core';
import { BehaviorSubject, Subject, map as map$1, combineLatest, filter, of, Subscription, firstValueFrom } from 'rxjs';
import { map, distinctUntilChanged } from 'rxjs/operators';
import { Terminal } from '@xterm/xterm';
import { FitAddon } from '@xterm/addon-fit';
import { WebLinksAddon } from '@xterm/addon-web-links';
import { Unicode11Addon } from '@xterm/addon-unicode11';
/**
* Represents a command processor token for dependency injection
*/
const CliCommandProcessor_TOKEN = new InjectionToken('cli-processors');
/**
* Represents a user session service token for dependency injection
*/
const ICliUserSessionService_TOKEN = new InjectionToken('cli-user-session-service');
/**
* Represents a user store service token for dependency injection
*/
const ICliUsersStoreService_TOKEN = new InjectionToken('cli-users-store-service');
/**
* Represents a ping server service token for dependency injection
*/
const ICliPingServerService_TOKEN = new InjectionToken('cli-ping-server-service');
/**
* Represents a logger token for dependency injection
*/
const CliLogger_TOKEN = new InjectionToken('cli-logger');
/**
* Represents a command processor registry token for dependency injection
*/
const CliProcessorsRegistry_TOKEN = new InjectionToken('cli-processors-registry');
const CliServiceProvider_TOKEN = new InjectionToken('cli-service-provider');
class CliStateStore {
constructor(services, name, initialState) {
this.services = services;
this.name = name;
this.initialState = initialState;
this.state$ = new BehaviorSubject(initialState);
this.storageKey = `store-state-${name}`;
}
getState() {
return this.state$.getValue();
}
updateState(newState) {
this.state$.next({ ...this.getState(), ...newState });
}
select(selector) {
return this.state$.asObservable().pipe(map(selector), distinctUntilChanged());
}
subscribe(callback) {
return this.state$.asObservable().subscribe(callback);
}
reset() {
this.state$.next(this.initialState);
}
async persist() {
const keyValueStore = this.services.get('cli-key-value-store');
await keyValueStore.set(this.storageKey, this.getState());
}
async initialize() {
const keyValueStore = this.services.get('cli-key-value-store');
const state = await keyValueStore.get(this.storageKey);
if (state) {
this.state$.next(state);
}
}
}
class CliStateStoreManager {
constructor(services) {
this.services = services;
this.stores = new Map();
}
getStateStore(name, defaultState) {
if (!this.stores.has(name)) {
this.stores.set(name, new CliStateStore(this.services, name, defaultState ?? {}));
}
return this.stores.get(name);
}
getProcessorStateStore(processor) {
const registry = this.services.get(CliProcessorsRegistry_TOKEN);
const rootProcessor = registry.getRootProcessor(processor);
return this.getStateStore(rootProcessor.stateConfiguration?.storeName ||
rootProcessor.command, rootProcessor.stateConfiguration?.initialState);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliStateStoreManager, deps: [{ token: CliServiceProvider_TOKEN }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliStateStoreManager }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliStateStoreManager, decorators: [{
type: Injectable
}], ctorParameters: function () { return [{ type: undefined, decorators: [{
type: Inject,
args: [CliServiceProvider_TOKEN]
}] }]; } });
class CliCommandExecutionContext {
constructor(context, processor) {
this.context = context;
this.userSession = context.userSession;
this.spinner = context.spinner;
this.progressBar = context.progressBar;
this.textAnimator = context.textAnimator;
this.onAbort = context.onAbort;
this.terminal = context.terminal;
this.writer = context.writer;
this.executor = context.executor;
this.clipboard = context.clipboard;
this.options = context.options;
this.showPrompt = context.showPrompt;
this.setContextProcessor = context.setContextProcessor;
this.process = context.process;
this.logger = context.logger;
this.services = context.services;
this.state = context.services
.get(CliStateStoreManager)
.getProcessorStateStore(processor);
}
}
class CliTerminalWriter {
constructor(terminal) {
this.terminal = terminal;
}
write(text) {
this.terminal.write(text);
}
writeln(text) {
this.terminal.writeln(text || '');
}
writeSuccess(message) {
this.writeLog(message, CliForegroundColor.Green, CliIcon.CheckIcon);
}
writeInfo(message) {
this.writeLog(message, CliForegroundColor.Cyan, CliIcon.InfoIcon);
}
writeWarning(message) {
this.writeLog(message, CliForegroundColor.Yellow, CliIcon.WarningIcon);
}
writeError(message) {
this.writeLog(message, CliForegroundColor.Red, CliIcon.CrossIcon);
}
writeLog(message, color, icon) {
this.terminal.writeln(this.wrapInColor(icon ? icon + ' ' + message : message, color));
}
writeDivider(options) {
const { color, length: oLength, char: oChar } = options || {};
let length = oLength ?? 80;
let char = oChar ?? '-';
if (this.terminal.cols < length) {
length = this.terminal.cols;
}
let text = char.repeat(length);
if (color) {
text = this.wrapInColor(text, color);
}
this.writeln(text);
}
wrapInColor(text, color) {
return color + text + CliForegroundColor.Reset;
}
wrapInBackgroundColor(text, color) {
return color + text + CliForegroundColor.Reset;
}
writeJson(json) {
this.terminal.writeln(formatJson(json));
}
writeToFile(fileName, content) {
const blob = new Blob([content], { type: 'text/plain' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
writeObjectsAsTable(objects) {
if (objects.length === 0) {
this.writeInfo('No objects to display');
return;
}
const headers = Object.keys(objects[0]);
const rows = objects.map((object) => headers.map((header) => object[header]));
this.writeTable(headers, rows);
}
writeTable(headers, rows) {
// Calculate column widths
const colWidths = headers.map((header, colIndex) => Math.max(header.length, ...rows.map((row) => row[colIndex]?.length || 0)));
// Function to pad text to a specific width
const padText = (text, width) => text?.toString()?.padEnd(width, ' ');
// Write the header
this.write(headers
.map((header, i) => this.wrapInColor(padText(header, colWidths[i]), CliForegroundColor.Yellow))
.join(' | ') + '\r\n');
this.write('-'.repeat(colWidths.reduce((sum, w) => sum + w + 3, -3)) + '\r\n');
// Write the rows
rows.forEach((row) => {
this.write(row.map((cell, i) => padText(cell, colWidths[i])).join(' | ') +
'\r\n');
});
}
}
class CliTerminalSpinner {
constructor(terminal) {
this.terminal = terminal;
this.isRunning = false;
this.text = '';
this.spinnerFrames = ['|', '/', '-', '\\'];
this.spinnerIndex = 0;
}
show(text) {
if (text) {
this.text = text;
}
this.isRunning = true;
this.spinnerInterval = setInterval(() => {
// Clear the current line
this.terminal.write('\x1b[2K\r');
// Write the spinner frame
this.terminal.write(this.spinnerFrames[this.spinnerIndex] +
(this.text.length > 0 ? ` ${this.text}` : ''));
// Update the frame index
this.spinnerIndex =
(this.spinnerIndex + 1) % this.spinnerFrames.length;
}, 100);
}
hide() {
this.isRunning = false;
if (this.spinnerInterval) {
clearInterval(this.spinnerInterval);
this.spinnerInterval = null;
}
// Clear the spinner character and reset the line
this.terminal.write('\x1b[2K\r');
this.text = '';
}
setText(text) {
this.text = text;
}
}
class CliTerminalProgressBar {
constructor(terminal) {
this.terminal = terminal;
this.isRunning = false;
this.text = '';
this.progress = 0;
this.total = 100;
this.progressText = '';
}
show(text) {
this.isRunning = true;
this.progress = 0;
this.text = text || '';
// Update progress bar every 100ms
this.progressBarInterval = setInterval(() => {
this.updateProgressBar();
// Stop when progress reaches 100%
if (this.progress > this.total) {
this.progress = this.total;
this.hide(); // Stop the progress bar
}
}, 100);
}
hide() {
this.isRunning = false;
if (this.progressBarInterval) {
clearInterval(this.progressBarInterval);
this.progressBarInterval = null;
}
this.clearCurrentLine();
this.text = '';
this.progressText = '';
}
update(progress, options) {
if (options?.type === 'increment') {
this.progress += progress;
}
else {
this.progress = progress;
}
if (this.progress > this.total) {
this.progress = this.total;
}
this.updateProgressBar();
}
complete() {
this.progress = 100;
this.hide();
}
setText(text) {
this.text = text;
this.updateProgressBar();
}
updateProgressBar() {
const totalBars = 50; // Length of the progress bar
const filledBars = Math.round((this.progress / this.total) * totalBars);
const emptyBars = totalBars - filledBars;
const progressBar = `[${'#'.repeat(filledBars)}${'.'.repeat(emptyBars)}]`;
const percentage = `${this.progress}%`.padStart(4, ' ');
const text = this.text.length > 0 ? ` ${this.text}` : '';
this.clearCurrentLine();
this.progressText = `${progressBar} ${percentage} ${text}`;
this.terminal.write(this.progressText); // Write progress bar
}
clearCurrentLine() {
const wrappedLines = Math.ceil(this.progressText.length / this.terminal.cols);
for (let i = 0; i < wrappedLines; i++) {
this.terminal.write('\x1b[2K'); // Clear the current line
this.terminal.write('\r'); // Move the cursor to the start of the line
if (i < wrappedLines - 1) {
this.terminal.write('\x1b[F'); // Move the cursor up for all but the last line
}
if (i === wrappedLines - 1) {
this.terminal.write('\r');
}
}
}
}
class CliClipboard {
constructor(context) {
this.context = context;
}
async write(text) {
try {
await navigator.clipboard.writeText(text);
}
catch (error) {
this.context.writer.writeError('Failed to write to clipboard');
}
}
async read() {
try {
return await navigator.clipboard.readText();
}
catch (error) {
this.context.writer.writeError('Failed to read from clipboard');
return '';
}
}
}
class ProcessExitedError extends Error {
constructor(code) {
super(`Process exited with code ${code}`);
this.name = 'ProcessExitedError';
this.code = code;
Object.setPrototypeOf(this, new.target.prototype);
}
}
class CliExecutionProcess {
constructor(context) {
this.context = context;
this.running = false;
}
exit(code, options) {
code = code ?? 0;
this.exited = true;
this.exitCode = code;
if (!options?.silent) {
throw new ProcessExitedError(code);
}
}
output(data) {
this.data = data;
}
start() {
this.exited = undefined;
this.exitCode = undefined;
this.data = undefined;
this.running = true;
}
end() {
this.running = false;
this.exitCode = 0;
}
}
class CliTerminalTextAnimator {
constructor(terminal) {
this.terminal = terminal;
this.isRunning = false;
this.text = '';
}
show(text) {
this.showText(text || '');
}
showText(text, options) {
const { speed, removeAfterTyping } = options || {};
if (this.isRunning) {
return; // Prevent multiple animations
}
this.isRunning = true;
this.text = text || this.text;
let index = 0;
let isTyping = true;
this.animationInterval = setInterval(() => {
if (isTyping) {
// Write text character by character
this.terminal.write(this.text[index]);
index++;
// Switch to erasing mode once typing is done
if (index === this.text.length) {
isTyping = false;
}
}
else if (removeAfterTyping) {
clearInterval(this.animationInterval);
this.animationInterval = null;
delay(1000).then(() => {
for (let i = 0; i < this.text.length; i++) {
this.terminal.write('\b \b');
}
});
this.isRunning = false;
}
else {
// Erase text character by character
this.terminal.write('\b \b'); // Backspace, overwrite with space, and backspace again
index--;
// Stop animation once all characters are erased
if (index < 0) {
clearInterval(this.animationInterval);
this.animationInterval = null;
this.isRunning = false;
}
}
}, speed || 100);
}
/**
* Hide the animation and reset the state.
*/
hide() {
if (!this.isRunning) {
return; // Prevent stopping if not running
}
this.isRunning = false;
if (this.animationInterval) {
clearInterval(this.animationInterval);
this.animationInterval = null;
}
this.text = '';
}
}
class CliExecutionContext {
constructor(injector, terminal, executor, showPrompt, cliOptions) {
this.terminal = terminal;
this.executor = executor;
this.showPrompt = showPrompt;
this.onAbort = new Subject();
//initialize services
this.services = injector.get(CliServiceProvider_TOKEN);
//initialize state store
const stateStoreManager = injector.get(CliStateStoreManager);
this.state = stateStoreManager.getStateStore('shared');
this.options = cliOptions;
this.writer = new CliTerminalWriter(terminal);
this.spinner = new CliTerminalSpinner(terminal);
this.progressBar = new CliTerminalProgressBar(terminal);
this.textAnimator = new CliTerminalTextAnimator(terminal);
this.clipboard = new CliClipboard(this);
this.process = new CliExecutionProcess(this);
//initialize logger
this.logger = injector.get(CliLogger_TOKEN);
this.logger.setCliLogLevel(cliOptions?.logLevel || CliLogLevel.ERROR);
}
setContextProcessor(processor, silent) {
if (!processor) {
this.contextProcessor = processor;
return;
}
if (!silent) {
this.writer.writeInfo('Set ' +
processor?.command +
' as context processor, press Ctrl+C to exit');
}
this.contextProcessor = processor;
}
/**
* Checks if there is a progress running
* @returns true if there is a progress running
* @returns false if there is no progress running
*/
isProgressRunning() {
return (this.progressBar.isRunning ||
this.spinner.isRunning ||
this.textAnimator.isRunning);
}
/**
* Aborts the current command
*/
abort() {
this.onAbort.next();
}
setSession(session) {
this.userSession = session;
}
}
class CliKeyValueStore {
constructor() {
this.dbName = 'CliKeyValueDB';
this.storeName = 'KeyValueStore';
}
/**
* Initializes the IndexedDB instance.
*/
initialize() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, 1);
request.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains(this.storeName)) {
db.createObjectStore(this.storeName);
}
};
request.onsuccess = (event) => {
this.db = event.target.result;
resolve();
};
request.onerror = (event) => {
console.error('Error initializing IndexedDB:', event);
reject();
};
});
}
/**
* Retrieves a value by key.
* @param key - The key to retrieve the value for.
* @returns A promise resolving to the value or undefined if not found.
*/
async get(key) {
return new Promise((resolve, reject) => {
try {
const transaction = this.db.transaction(this.storeName, 'readonly');
const store = transaction.objectStore(this.storeName);
const request = store.get(key);
request.onsuccess = () => {
resolve(request.result);
};
request.onerror = (event) => {
console.error('Error getting value:', event);
reject(undefined);
};
}
catch (e) {
console.error('Error getting value:', e);
reject(undefined);
}
});
}
/**
* Sets a key-value pair in the store.
* @param key - The key to set.
* @param value - The value to store.
* @returns A promise resolving when the value is stored.
*/
async set(key, value) {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction(this.storeName, 'readwrite');
const store = transaction.objectStore(this.storeName);
const request = store.put(value, key);
request.onsuccess = () => resolve();
request.onerror = (event) => {
console.error('Error setting value:', event);
reject();
};
});
}
/**
* Removes a key-value pair by key.
* @param key - The key to remove.
* @returns A promise resolving when the key is removed.
*/
async remove(key) {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction(this.storeName, 'readwrite');
const store = transaction.objectStore(this.storeName);
const request = store.delete(key);
request.onsuccess = () => resolve();
request.onerror = (event) => {
console.error('Error removing value:', event);
reject();
};
});
}
/**
* Clears all key-value pairs from the store.
* @returns A promise resolving when the store is cleared.
*/
async clear() {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction(this.storeName, 'readwrite');
const store = transaction.objectStore(this.storeName);
const request = store.clear();
request.onsuccess = () => resolve();
request.onerror = (event) => {
console.error('Error clearing store:', event);
reject();
};
});
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliKeyValueStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliKeyValueStore, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliKeyValueStore, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}], ctorParameters: function () { return []; } });
class CliBoot {
constructor(implementations, registry) {
this.implementations = implementations;
this.registry = registry;
this.initialized = false;
this.initializing = false;
}
async boot(context) {
context.spinner?.show(CliIcon.Rocket + ' Booting...');
if (this.initialized || this.initializing) {
await this.bootShared(context);
context.spinner?.hide();
return;
}
this.initializing = true;
await this.registerServices(context);
initializeBrowserEnvironment({
context,
handlers: [
async (module) => {
await this.registerUmdModule(module, context);
},
],
});
let processors = this.implementations;
//TODO: refactor in a better way
if (!context.options?.usersModule?.enabled) {
processors = processors.filter((p) => p.metadata?.module !== 'users');
}
processors.forEach((impl) => this.registry.registerProcessor(impl));
await this.bootShared(context);
context.spinner?.hide();
this.initialized = true;
}
async bootShared(context) {
await this.initializeProcessorsInternal(context, this.registry.processors);
await delay(300);
}
async initializeProcessorsInternal(context, processors, parent) {
try {
for (const p of processors) {
p.parent = parent;
if (p.initialize) {
const processorContext = new CliCommandExecutionContext(context, p);
await processorContext.state.initialize();
await p.initialize(processorContext);
}
if (p.processors && p.processors.length > 0) {
await this.initializeProcessorsInternal(context, p.processors, p);
}
}
}
catch (e) {
context.writer.writeError(`Error initializing processors: ${e}`);
}
}
async registerServices(context) {
context.services.set([
{
provide: 'cli-key-value-store',
useValue: context.services.get(CliKeyValueStore),
},
]);
}
async registerUmdModule(module, context) {
const { logger } = context;
if (!module) {
return;
}
if (module.processors) {
logger.info('Registering processors from module ' + module.name);
for (const processor of module.processors) {
this.registry.registerProcessor(processor);
}
await this.initializeProcessorsInternal(context, module.processors);
}
else {
logger.warn(`Module ${module.name} has no processors`);
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliBoot, deps: [{ token: CliCommandProcessor_TOKEN }, { token: CliProcessorsRegistry_TOKEN }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliBoot, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliBoot, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}], ctorParameters: function () { return [{ type: undefined, decorators: [{
type: Inject,
args: [CliCommandProcessor_TOKEN]
}] }, { type: undefined, decorators: [{
type: Inject,
args: [CliProcessorsRegistry_TOKEN]
}] }]; } });
// Automatically generated during build
const LIBRARY_VERSION = '1.0.35';
const CLi_Name_Art = `
____ _ _ _ _____ _ _____
/ __ \\ | | | (_) / ____| | |_ _|
| | | | ___ __| | __ _| |_ ___ | | | | | |
| | | |/ _ \\ / _\` |/ _\` | | / __| | | | | | |
| |__| | (_) | (_| | (_| | | \\__ \\ | |____| |____ _| |_
\\___\\_\\\\___/ \\__,_|\\__,_|_|_|___/ \\_____|______|_____|
`;
const hotkeysInfo = [
{
key: 'Ctrl + C',
description: 'Cancel the current command (if possible) or copy the current selection',
},
{
key: 'Ctrl + V',
description: 'Paste the copied text',
},
{
key: 'Ctrl + L',
description: 'Clear the screen',
},
];
/**
* A utility class for parsing command strings into command names and arguments.
*/
class CommandParser {
/**
* Parse a command string into a full command name and arguments.
* @param command - The full command string.
* @returns An object containing the full command name and arguments.
*/
parse(command) {
try {
// Match quoted strings, single-quoted strings, or unquoted words
const regex = /(?:--?([a-zA-Z0-9-_]+)(?:=("[^"]*"|'[^']*'|[^\s]+))?)|(?:[^\s]+)/g;
const matches = Array.from(command.matchAll(regex));
if (matches.length === 0) {
throw new Error('Invalid command string');
}
const commandParts = [];
const args = [];
// Process matches
matches.forEach((match) => {
if (match[1]) {
// Handle arguments
const key = match[1];
let value = match[2];
if (value) {
// Remove surrounding quotes, if any
value = value.replace(/^['"]|['"]$/g, '');
}
else {
// Flag without a value
value = true;
}
args.push({
name: key,
value: this.parseValue(value),
});
}
else if (!match[0].startsWith('--')) {
// Command name
commandParts.push(match[0]);
}
});
const commandName = commandParts.join(' ');
return {
commandName,
args,
};
}
catch (e) {
console.error('Unable to parse the command:', command, e);
return {
commandName: '',
args: [],
};
}
}
/**
* Parse individual values to their appropriate type.
* @param value - The value to parse.
* @returns The parsed value.
*/
parseValue(value) {
if (!isNaN(Number(value)))
return Number(value); // Convert numeric strings to numbers
if (value === 'true' || value === 'false')
return value === 'true'; // Convert "true"/"false" to boolean
return value; // Return as string otherwise
}
}
const resolveCliProvider = (token, provider) => ({
provide: token,
useExisting: provider,
multi: true,
});
const resolveCommandProcessorProvider = (provider) => [
provider,
resolveCliProvider(CliCommandProcessor_TOKEN, provider),
];
const openLink = (link) => {
try {
window.open(link, '_blank');
}
catch (e) {
console.error(e);
}
};
const getGreetingBasedOnTime = (date) => {
const currentHour = (date ?? new Date()).getHours();
if (currentHour >= 5 && currentHour < 12) {
return 'Good morning! Wishing you a productive day ahead!';
}
else if (currentHour >= 12 && currentHour < 18) {
return 'Good afternoon! Keep up the great work!';
}
else if (currentHour >= 18 && currentHour < 22) {
return 'Good evening! Hope you had a fantastic day!';
}
else {
return 'Good night! Rest well and see you tomorrow!';
}
};
/**
* Service that displays the welcome message to the user.
*/
class CliWelcomeMessageService {
/**
* Displays the welcome message to the user.
* @param context
* @returns void
*/
displayWelcomeMessage(context) {
const welcomeConfig = context.options?.welcomeMessage;
// Handle the 'show' property
if (welcomeConfig?.show) {
const showOption = welcomeConfig.show;
// Determine if the welcome message should be shown
if (!this.shouldDisplayWelcomeMessage(showOption)) {
context.showPrompt();
return;
}
}
if (welcomeConfig?.message) {
context.terminal.writeln(welcomeConfig?.message);
}
else {
const welcomeMessage = [
`Welcome to Web CLI [Version ${context.writer.wrapInColor(LIBRARY_VERSION, CliForegroundColor.Green)}]`,
'(c) 2024 Qodalis Solutions. All rights reserved.',
CLi_Name_Art,
'',
context.writer.wrapInColor('Documentation: ', CliForegroundColor.Green) + 'https://cli-docs.qodalis.com/',
'',
"Type 'help' to get started.",
'',
];
welcomeMessage.forEach((line, index) => {
context.terminal.write(line + '\r\n');
});
}
this.recordWelcomeMessageDisplay();
context.showPrompt();
context.textAnimator?.showText(getGreetingBasedOnTime(), {
speed: 60,
removeAfterTyping: true,
});
}
/**
* Determines if the welcome message should be displayed based on the show option.
* @param showOption - The show option from the config.
* @returns true if the message should be displayed, false otherwise.
*/
shouldDisplayWelcomeMessage(showOption) {
const lastDisplayed = this.getLastWelcomeMessageDisplayTime();
switch (showOption) {
case 'always':
return true;
case 'once':
return !lastDisplayed; // Show only if it hasn't been shown before
case 'daily':
if (!lastDisplayed)
return true; // No previous display, show it
const now = new Date();
const lastDate = new Date(lastDisplayed);
return now.toDateString() !== lastDate.toDateString(); // Compare dates
case 'never':
return false;
default:
return true;
}
}
/**
* Records the current time as the last display time for the welcome message.
*/
recordWelcomeMessageDisplay() {
localStorage.setItem('cliWelcomeMessageLastDisplayed', new Date().toISOString());
}
/**
* Retrieves the last display time of the welcome message.
* @returns The ISO string of the last display time or null if not recorded.
*/
getLastWelcomeMessageDisplayTime() {
return localStorage.getItem('cliWelcomeMessageLastDisplayed');
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliWelcomeMessageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliWelcomeMessageService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliWelcomeMessageService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}] });
const themes = {
...DefaultThemes,
};
class CliCommandHistoryService {
constructor(store) {
this.store = store;
this.storageKey = 'cli-command-history';
this.commandHistory = [];
}
async addCommand(command) {
const normalizedCommand = command.trim();
if (normalizedCommand) {
if (this.commandHistory.length > 0) {
const lastCommand = this.commandHistory[this.commandHistory.length - 1];
if (lastCommand === normalizedCommand) {
return;
}
}
this.commandHistory.push(command);
await this.saveHistory();
}
}
getLastIndex() {
return this.commandHistory.length;
}
getHistory() {
return [...this.commandHistory];
}
async clearHistory() {
this.commandHistory = [];
await this.saveHistory();
}
getCommand(index) {
return this.commandHistory[index];
}
async saveHistory() {
//save only last 500 commands
const trimmedHistory = this.commandHistory.slice(-500);
return await this.store.set(this.storageKey, trimmedHistory);
}
async loadHistory() {
const savedOldHistory = localStorage.getItem('cliCommandHistory');
if (savedOldHistory) {
localStorage.removeItem('cliCommandHistory');
await this.store.set(this.storageKey, JSON.parse(savedOldHistory));
}
const savedHistory = await this.store.get(this.storageKey);
if (savedHistory) {
this.commandHistory = savedHistory;
}
}
async initialize() {
await this.loadHistory();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliCommandHistoryService, deps: [{ token: CliKeyValueStore }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliCommandHistoryService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliCommandHistoryService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}], ctorParameters: function () { return [{ type: CliKeyValueStore }]; } });
class CliUserSessionService {
constructor(usersService) {
this.usersService = usersService;
this.userSessionSubject = new BehaviorSubject({
user: {
id: 'anonymous',
name: 'Anonymous',
email: 'anonymous',
},
});
this.usersService.getUsers().subscribe((users) => {
const user = users.find((u) => u.id === 'root');
if (user) {
this.setUserSession({
user,
});
}
});
}
async setUserSession(session) {
this.userSessionSubject.next(session);
}
getUserSession() {
return this.userSessionSubject.asObservable();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliUserSessionService, deps: [{ token: ICliUsersStoreService_TOKEN }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliUserSessionService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliUserSessionService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}], ctorParameters: function () { return [{ type: undefined, decorators: [{
type: Inject,
args: [ICliUsersStoreService_TOKEN]
}] }]; } });
class CliUsersStoreService {
constructor() {
this.localStorageKey = 'users';
this.defaultUsers = [
{
id: 'root',
name: 'root',
email: 'root@domain.com',
groups: ['admin'],
},
];
this.usersSubject = new BehaviorSubject([]);
this.initialize(this.defaultUsers);
}
initialize(defaultUsers) {
const storedUsers = this.loadUsers(defaultUsers);
this.usersSubject.next(storedUsers);
}
async createUser(user) {
const users = this.usersSubject.getValue();
if (users.some((u) => u.email === user.email || u.name === user.name)) {
return Promise.reject('User already exists');
}
const newUser = {
...user,
id: user.email,
};
users.push(newUser);
this.saveUsers(users);
this.usersSubject.next(users);
return newUser;
}
getUsers(options) {
const { query, skip, take } = options || {};
return this.usersSubject.asObservable().pipe(map$1((users) => {
if (query) {
const queryLower = query.toLowerCase();
users = users.filter((u) => u.name.toLowerCase().includes(queryLower) ||
u.email.toLowerCase().includes(queryLower));
}
if (skip) {
users = users.slice(skip);
}
if (take) {
users = users.slice(0, take);
}
return users;
}));
}
getUser(id) {
return this.usersSubject
.asObservable()
.pipe(map$1((users) => users.find((u) => u.id === id || u.name === id || u.email === id)));
}
saveUsers(users) {
localStorage.setItem(this.localStorageKey, JSON.stringify(users));
}
loadUsers(fallbackUsers) {
const data = localStorage.getItem(this.localStorageKey);
if (!data) {
return fallbackUsers;
}
try {
return JSON.parse(data);
}
catch (error) {
console.error('Failed to parse user data from localStorage', error);
return [];
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliUsersStoreService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliUsersStoreService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliUsersStoreService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}], ctorParameters: function () { return []; } });
class CliArgsParser {
static convertToRecord(args, processor) {
const result = {};
const updateParamValue = (parameter, value) => {
result[parameter.name] = value;
parameter.aliases?.forEach((alias) => {
result[alias] = value;
});
};
args.forEach((arg) => {
const parameter = processor.parameters?.find((p) => p.name === arg.name ||
p.aliases?.some((a) => a === arg.name));
if (parameter) {
let value = arg.value;
switch (parameter.type) {
case 'array':
const previousValue = Array.isArray(result[arg.name])
? result[arg.name]
: [];
value = [...previousValue, arg.value];
break;
case 'boolean':
value =
value === 'true' ||
value === '1' ||
value === 'yes' ||
value === 'y' ||
value === 1;
break;
default:
value = arg.value;
break;
}
updateParamValue(parameter, value);
}
else {
result[arg.name] = arg.value;
}
});
return result;
}
}
class CliCommandExecutorService {
constructor(registry) {
this.registry = registry;
this.commandParser = new CommandParser();
}
async executeCommand(command, context) {
// Split commands by logical operators
const parts = command.split(/(&&|\|\|)/).map((part) => part.trim());
let shouldRunNextCommand = true; // Tracks whether to execute the next command
let rootContext;
if (context instanceof CliCommandExecutionContext) {
rootContext = context.context;
}
else {
rootContext = context;
}
for (let i = 0; i < parts.length; i++) {
const current = parts[i];
// If the current part is a logical operator, adjust the shouldRunNextCommand flag
if (current === '&&') {
shouldRunNextCommand = shouldRunNextCommand && true;
continue;
}
else if (current === '||') {
shouldRunNextCommand = !shouldRunNextCommand;
continue;
}
// Skip execution based on previous command's result and operator
if (!shouldRunNextCommand) {
shouldRunNextCommand = true; // Reset for next iteration
continue;
}
// Execute the command
let commandSuccess = true;
try {
const data = context.process.data;
const command = current;
await this.executeSingleCommand(command, data, rootContext);
commandSuccess = context.process.exitCode === 0;
}
catch (e) {
commandSuccess = false;
context.writer.writeError(`Command ${current} failed: ${e}`);
}
shouldRunNextCommand = commandSuccess;
}
}
async executeSingleCommand(command, data, context) {
const process = context.process;
process.start();
const { commandName, args: parsedArgs } = this.commandParser.parse(command);
const [mainCommand, ...other] = commandName.split(' ');
const chainCommands = other.map((c) => c.toLowerCase());
const searchableProcessors = context.contextProcessor
? (context.contextProcessor.processors ?? [])
: this.registry.processors;
const processor = this.registry.findProcessorInCollection(mainCommand, chainCommands, searchableProcessors);
if (!processor) {
const aliases = this.registry.findProcessor('alias', []).aliases ?? {};
if (aliases[mainCommand]) {
const alias = aliases[mainCommand];
return await this.executeSingleCommand(alias, data, context);
}
context.writer.writeError(`Command: ${commandName} not found or not installed`);
context.writer.writeln();
context.writer.writeInfo('Type "help" for a list of available commands.');
context.writer.writeInfo('Use packages to install additional commands.');
context.process.exit(-1, {
silent: true,
});
return;
}
const args = CliArgsParser.convertToRecord(parsedArgs, processor);
const commandToProcess = {
command: commandName,
chainCommands: chainCommands,
rawCommand: command,
args: args,
data: data,
};
if (this.versionRequested(context, processor, args)) {
process.end();
return;
}
if (await this.helpRequested(commandToProcess, context)) {
process.end();
return;
}
if (this.setContextProcessorRequested(context, processor, args)) {
process.end();
return;
}
if (!this.validateBeforeExecution(context, processor, args)) {
process.end();
return;
}
const value = processor.allowUnlistedCommands || processor.valueRequired
? getRightOfWord(commandName, processor.command)
: undefined;
commandToProcess.value = value;
const missingValue = processor.valueRequired && !value;
if (missingValue) {
context.writer.writeError(`Value required for command: ${commandName} <value>`);
context.process.exit(-1);
return;
}
if (processor.validateBeforeExecution) {
const validationResult = processor.validateBeforeExecution(commandToProcess, context);
if (validationResult.valid === false) {
context.writer.writeError(validationResult?.message ||
'An error occurred while validating the command.');
context.process.exit(-1);
return;
}
}
let cancellable = null;
const commandContext = new CliCommandExecutionContext(context, processor);
try {
const hooks = processor.hooks ?? [];
for (const hook of hooks.filter((h) => h.when === 'before')) {
await hook.execute(commandContext);
}
cancellable = new CancellablePromise(async (resolve, reject) => {
processor
.processCommand(commandToProcess, commandContext)
.then(() => {
resolve();
})
.catch((e) => {
reject(e);
});
});
await cancellable.execute();
for (const hook of hooks.filter((h) => h.when === 'after')) {
await hook.execute(commandContext);
}
process.end();
}
catch (e) {
context.spinner?.hide();
if (e instanceof ProcessExitedError) {
cancellable?.cancel();
context?.abort();
if (e.code !== 0) {
context.writer.writeError(`Process exited with code ${e.code}`);
}
else {
context.writer.writeInfo('Process exited successfully with code 0');
}
}
else {
context.writer.writeError(`Error executing command: ${e}`);
context.process.exit(-1);
}
}
}
async showHelp(command, context) {
try {
await this.executeCommand('help ' + command.rawCommand, context);
}
catch (e) {
context.writer.writeError(`Error executing command: ${e}`);
}
}
versionRequested(context, processor, args) {
if (args['v'] || args['version']) {
context.writer.writeln(`${context.writer.wrapInColor(processor.version || '1.0.0', CliForegroundColor.Cyan)}`);
return true;
}
return false;
}
setContextProcessorRequested(context, processor, args) {
if (args['context']) {
context.setContextProcessor(processor);
return true;
}
return false;
}
async helpRequested(commandToProcess, context) {
if (commandToProcess.command?.startsWith('help')) {
return false;
}
if (commandToProcess.args['h'] || commandToProcess.args['help']) {
await this.showHelp(commandToProcess, context);
return true;
}
return false;
}
/**
* Validates the command arguments before execution.
* @param context The current CLI execution context.
* @param processor The processor to validate.
* @param args The command arguments.
* @returns True if the arguments are valid, false otherwise.
*/
validateBeforeExecution(context, processor, args) {
// Check for required parameters
if (processor.parameters?.some((p) => p.required)) {
const missingParams = processor.parameters?.filter((p) => p.required &&
!args[p.name] &&
!p.aliases?.some((a) => args[a]));
if (missingParams?.length) {
context.writer.writeError(`Missing required parameters: ${missingParams
.map((p) => p.name)
.join(', ')}`);
return false;
}
}
// Check for parameter validation
const parametersToValidate = processor.parameters
?.filter((x) => x.validator)
?.map((p) => ({
parameter: p,
value: getParameterValue(p, args),
}))
?.filter((p) => p.value) ?? [];
if (parametersToValidate.length > 0) {
const invalidParams = parametersToValidate
.filter((x) => !x.parameter.validator(x.value).valid)
.map((p) => ({
name: p.parameter.name,
message: p.parameter.validator(args[p.parameter.name])
.message,
}));
if (invalidParams?.length) {
context.writer.writeError('Invalid parameters:');
invalidParams.forEach((p, index) => {
context.writer.writeln(`${index + 1}. Invalid value for ${p.name}: ${args[p.name]} -> ${p.message}`);
});
return false;
}
}
return true;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliCommandExecutorService, deps: [{ token: CliProcessorsRegistry_TOKEN }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliCommandExecutorService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliCommandExecutorService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}], ctorParameters: function () { return [{ type: undefined, decorators: [{
type: Inject,
args: [CliProcessorsRegistry_TOKEN]
}] }]; } });
class CliDefaultPingServerService {
ping() {
// Simulate a server ping
return new Promise((resolve) => setTimeout(resolve, 2000));
}
}
class ScriptLoaderService {
constructor() { }
injectScript(src) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = src;
script.async = true;
script.onload = () => resolve();
script.onerror = () => reject(new Error(`Failed to load script: ${src}`));
document.head.appendChild(script);
});
}
getScript(src, options) {
const { onProgress } = options || {};
let fetchProgress = 0;
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', src, true);
xhr.onprogress = (event) => {
if (event.lengthComputable) {
const progress = Math.round((event.loaded / event.total) * 100);
fetchProgress = progress;
onProgress?.(progress);
}
else {
fetchProgress += 20;
onProgress?.(fetchProgress);
}
};
xhr.onload = () => {
if (xhr.status === 200) {
onProgress?.(100);
fetchProgress = 100;
resolve({
xhr,
content: xhr.responseText,
});
}
else {
reject(new Error(`Failed to load package: ${src}, Status: ${xhr.status}, Message: ${xhr.responseText}`));
}
};
xhr.onerror = () => {
reject(new Error(`Failed to load package: ${src}`));
};
xhr.send();
});
}
injectBodyScript(code) {
const script = document.createElement('script');
script.text = code;
document.head.appendChild(script);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ScriptLoaderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ScriptLoaderService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ScriptLoaderService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}], ctorParameters: function () { return []; } });
class OverlayAddon {
constructor() {
this.overlayNode = document.createElement('div');
this.overlayNode.style.cssText = `border-radius: 15px;
font-size: xx-large;
opacity: 0.75;
padding: 0.2em 0.5em 0.2em 0.5em;
position: absolute;
-webkit-user-select: none;
-webkit-transition: opacity 180ms ease-in;
-moz-user-select: none;
-moz-transition: opacity 180ms ease-in;`;
this.overlayNode.addEventListener('mousedown', (e) => {
e.preventDefault();
e.stopPropagation();
}, true);
}
activate(terminal) {
this.terminal = terminal;
}
dispose() { }
showOverlay(msg, timeout) {
const { terminal, overlayNode } = this;
if (!terminal.element)
return;
overlayNode.style.color = '#101010';
overlayNode.style.backgroundColor = '#f0f0f0';
overlayNode.textContent = msg;
overlayNode.style.opacity = '0.75';
if (!overlayNode.parentNode) {
terminal.element.appendChild(overlayNode);
}
const divSize = terminal.element.getBoundingClientRect();
const overlaySize = overlayNode.getBoundingClientRect();
overlayNode.style.top =
(divSize.height - overlaySize.height) / 2 + 'px';
overlayNode.style.left = (divSize.width - overlaySize.width) / 2 + 'px';
if (this.overlayTimeout)
clearTimeout(this.overlayTimeout);
if (!timeout)
return;
this.overlayTimeout = window.setTimeout(() => {
overlayNode.style.opacity = '0';
this.overlayTimeout = window.setTimeout(() => {
if (overlayNode.parentNode) {
overlayNode.parentNode.removeChild(overlayNode);
}
this.overlayTimeout = undefined;
overlayNode.style.opacity = '0.75';
}, 200);
}, timeout || 1500);
}
}
class CliTerminalComponent {
constructor() {
this.onTerminalReady = new EventEmitter();
}
ngOnInit() {
this.initializeTerminal();
}
ngAfterViewInit() {
this.handleResize();
}
initializeTerminal() {
if (!this.options) {
this.options = {
allowProposedApi: true,
};
}
this.terminal = new Terminal(this.options);
this.fitAddon = new FitAddon();
this.terminal.loadAddon(this.fitAddon);
const webLinksAddon = new WebLinksAddon();
this.terminal.loadAddon(webLinksAddon);
const overlayAddon = new OverlayAddon();
this.terminal.loadAddon(overlayAddon);
// const webGlAddon = new WebglAddon();
// webGlAddon.onContextLoss((e) => {
// webGlAddon.dispose();
// });
// this.terminal.loadAddon(webGlAddon);
const unicode11Addon = new Unicode11Addon();
this.terminal.loadAddon(unicode11Addon);
this.terminal.open(this.terminalDiv.nativeElement);
this.fitAddon.fit();
this.terminal.focus();
this.onTerminalReady.emit(this.terminal);
}
handleResize() {
window.addEventListener('resize', () => {
this.fitAddon.fit();
});
this.observeContainerSize();
}
observeContainerSize() {
this.resizeObserver = new ResizeObserver(() => {
this.fitAddon.fit();
});
this.resizeObserver.observe(this.terminalDiv.nativeElement);
}
ngOnDestroy() {
if (this.resizeObserver) {
this.resizeObserver.disconnect();
}
this.terminal?.dispose();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliTerminalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: CliTerminalComponent, selector: "cli-terminal", inputs: { options: "options", height: "height" }, outputs: { onTerminalReady: "onTerminalReady" }, viewQueries: [{ propertyName: "terminalDiv", first: true, predicate: ["terminal"], descendants: true, static: true }], ngImport: i0, template: "<div #terminal class=\"terminal-container\" [style.height]=\"height\"></div>\n", styles: [".terminal-container{background-color:#0c0c0c;padding:4px}\n"] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliTerminalComponent, decorators: [{
type: Component,
args: [{ selector: 'cli-terminal', template: "<div #terminal class=\"terminal-container\" [style.height]=\"height\"></div>\n", styles: [".terminal-container{background-color:#0c0c0c;padding:4px}\n"] }]
}], ctorParameters: function () { return []; }, propDecorators: { options: [{
type: Input
}], height: [{
type: Input
}], onTerminalReady: [{
type: Output
}], terminalDiv: [{
type: ViewChild,
args: ['terminal', { static: true }]
}] } });
class CliComponent {
constructor(injector, userManagementService, commandExecutor, commandHistoryService, store) {
this.injector = injector;
this.userManagementService = userManagementService;
this.commandExecutor = commandExecutor;
this.commandHistoryService = commandHistoryService;
this.store = store;
this.currentLine = '';
this.minDepsInitialized = new BehaviorSubject(false);
this.terminalInitialized = new BehaviorSubject(false);
this.selectionStart = null;
this.selectionEnd = null;
this.historyIndex = 0;
this.cursorPosition = 0;
this.userManagementService.getUserSession().subscribe((session) => {
this.currentUserSession = session;
this.executionContext?.setSession(session);
if (this.terminal) {
this.printPrompt({
reset: true,
});
}
});
}
ngOnInit() {
combineLatest([this.minDepsInitialized, this.terminalInitialized])
.pipe(filter(([x, y]) => x && y))
.subscribe(() => {
this.initialize();
});
this.terminalOptions = {
cursorBlink: true,
allowProposedApi: true,
fontSize: 20,
theme: themes.default,
convertEol: true,
...(this.options?.terminalOptions ?? {}),
};
this.store.initialize().then(() => {
this.minDepsInitialized.next(true);
});
}
onTerminalReady(terminal) {
this.terminal = terminal;
this.terminalInitialized.next(true);
}
initialize() {
this.commandHistoryService.initialize().then(() => {
this.historyIndex = this.commandHistoryService.getLastIndex();
});
this.addTerminalEventListeners();
this.executionContext = new CliExecutionContext(this.injector, this.terminal, this.commandExecutor, (o) => this.printPrompt(o), {
...(this.options ?? {}),
terminalOptions: this.terminalOptions,
});
this.executionContext.setSession(this.currentUserSession);
this.injector
.get(CliBoot)
.boot(this.executionContext)
.then(() => {
this.injector
.get(CliWelcomeMessageService)
.displayWelcomeMessage(this.executionContext);
});
}
getTerminalCursorPosition() {
const x = this.terminal._core.buffer.x;
const y = this.terminal._core.buffer.y;
return {
x,
y,
};
}
addTerminalEventListeners() {
// Handle user input
this.terminal.onData(async (data) => await this.handleInput(data));
this.terminal.onKey(async (event) => { });
this.terminal.attachCustomKeyEventHandler((event) => {
if (event.type === 'keydown') {
if (event.code === 'KeyC' && event.ctrlKey) {
// Handle Ctrl+C
this.executionContext?.abort();
this.executionContext?.setContextProcessor(undefined);
this.terminal.writeln('Ctrl+C');
this.printPrompt();
return false;
}
if (event.code === 'Escape') {
// Handle Escape
this.executionContext?.abort();
this.printPrompt({
newLine: true,
});
return false;
}
if (event.code === 'KeyV' && event.ctrlKey) {
//Handle Ctrl+V
return false;
}
if (event.code === 'KeyL' && event.ctrlKey) {
// Prevent the browser's default action (e.g., focusing the address bar)
event.preventDefault();
this.clearCurrentLine();
// Clear the terminal screen
this.terminal.clear();
return false;
}
if (event.shiftKey &&
(event.code === 'ArrowLeft' || event.code === 'ArrowRight')) {
if (!this.selectionStart) {
this.selectionStart = this.getTerminalCursorPosition();
}
switch (event.code) {
case 'ArrowLeft':
this.moveCursorLeft();
break;
case 'ArrowRight':
this.moveCursorRight();
break;
}
this.selectionEnd = this.getTerminalCursorPosition();
this.updateSelection();
return false;
}
else {
this.selectionStart = null;
}
}
return true;
});
}
updateSelection() {
if (this.selectionStart && this.selectionEnd) {
const startRow = Math.min(this.selectionStart.y, this.selectionEnd.y);
const endRow = Math.max(this.selectionStart.y, this.selectionEnd.y);
if (startRow === endRow) {
const startCol = Math.min(this.selectionStart.x, this.selectionEnd.x);
const endCol = Math.max(this.selectionStart.x, this.selectionEnd.x);
// Select text on the same line
this.terminal.select(startCol, startRow, Math.abs(endCol - startCol));
}
else {
// Select multiple lines
this.terminal.selectLines(startRow, endRow);
}
}
}
printPrompt(options) {
const { reset, newLine, keepCurrentLine } = options || {};
if (reset) {
this.terminal.write('\x1b[2K\r');
}
if (newLine) {
this.terminal.write('\r\n');
}
if (!keepCurrentLine) {
this.currentLine = '';
this.cursorPosition = 0;
}
let promtStartMessage = this.options?.usersModule?.hideUserName ||
!this.options?.usersModule?.enabled
? ''
: `\x1b[32m${this.currentUserSession?.user.email}\x1b[0m:`;
if (this.executionContext?.contextProcessor) {
promtStartMessage = `${this.executionContext.contextProcessor.command}`;
}
const promtEndMessage = '\x1b[34m~\x1b[0m$ ';
const prompt = `${promtStartMessage}${promtEndMessage}`;
this.terminal.write(prompt);
}
async handleInput(data) {
if (this.executionContext?.isProgressRunning()) {
return;
}
if (data === '\r') {
// Enter key: Process the current command
this.terminal.write('\r\n'); // Move to the next line
if (this.currentLine) {
await this.commandHistoryService.addCommand(this.currentLine);
this.historyIndex = this.commandHistoryService.getLastIndex();
//reset cursor position
this.cursorPosition = 0;
await this.commandExecutor.executeCommand(this.currentLine, this.executionContext);
//check if the command has subscribed to the onAbort event
if (this.executionContext?.onAbort.observed) {
this.terminal.writeln('\x1b[33m' + 'Press Ctrl+C to cancel' + '\x1b[0m');
}
}
this.printPrompt();
}
else if (data === '\u001B[A') {
// Arrow Up
this.showPreviousCommand();
}
else if (data === '\u001B[B') {
// Arrow Down
this.showNextCommand();
}
else if (data === '\u001B[D') {
// Left Arrow
this.moveCursorLeft(data);
}
else if (data === '\u001B[C') {
// Right Arrow
this.moveCursorRight(data);
}
else if (data === '\u007F') {
// Backspace key
this.handleBackspace();
}
else {
// Append character at cursor position
this.handleInputText(data);
}
}
normalizeText(text) {
//handle tab
if (text === '\u0009') {
return ' ';
}
return text.replace(/[\r\n]+/g, '');
}
handleInputText(text) {
text = this.normalizeText(text);
this.currentLine =
this.currentLine.slice(0, this.cursorPosition) +
text +
this.currentLine.slice(this.cursorPosition);
this.cursorPosition += text.length;
this.refreshCurrentLine();
}
refreshCurrentLine() {
this.terminal.write('\x1b[2K'); // Clear the current line
this.terminal.write('\r'); // Move the cursor to the start
this.printPrompt({
keepCurrentLine: true,
});
// Redraw the prompt
this.writeCurrentLine();
// Move cursor to the correct position
const cursorOffset = this.currentLine.length - this.cursorPosition;
if (cursorOffset > 0) {
this.terminal.write(`\x1b[${cursorOffset}D`);
}
}
writeCurrentLine() {
this.terminal.write(colorFirstWord(this.currentLine, (word) => this.executionContext?.writer.wrapInColor(word, CliForegroundColor.Yellow) ?? this.currentLine));
}
showPreviousCommand() {
if (this.historyIndex > 0) {
this.historyIndex--;
this.displayCommandFromHistory();
}
}
showNextCommand() {
if (this.historyIndex < this.commandHistoryService.getLastIndex() - 1) {
this.historyIndex++;
this.displayCommandFromHistory();
}
else {
this.historyIndex = this.commandHistoryService.getLastIndex();
this.clearCurrentLine();
}
}
displayCommandFromHistory() {
this.clearCurrentLine();
this.currentLine =
this.commandHistoryService.getCommand(this.historyIndex) || '';
this.writeCurrentLine();
this.cursorPosition = this.currentLine.length;
}
clearCurrentLine() {
const wrappedLines = Math.ceil(this.currentLine.length / this.terminal.cols);
for (let i = 0; i < wrappedLines; i++) {
this.terminal.write('\x1b[2K'); // Clear the current line
this.terminal.write('\r'); // Move the cursor to the start of the line
if (i < wrappedLines - 1) {
this.terminal.write('\x1b[F'); // Move the cursor up for all but the last line
}
if (i === wrappedLines - 1) {
this.terminal.write('\r');
this.printPrompt();
}
}
this.currentLine = '';
this.cursorPosition = 0;
}
moveCursorLeft(key = '\x1b[D') {
if (this.cursorPosition > 0) {
this.cursorPosition--;
this.terminal.write(key);
}
}
moveCursorRight(key = '\x1b[C') {
if (this.cursorPosition < this.currentLine.length) {
this.cursorPosition++;
this.terminal.write(key);
}
}
handleBackspace() {
if (this.cursorPosition > 0) {
this.currentLine =
this.currentLine.slice(0, this.cursorPosition - 1) +
this.currentLine.slice(this.cursorPosition);
this.cursorPosition--;
this.refreshCurrentLine();
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliComponent, deps: [{ token: i0.Injector }, { token: ICliUserSessionService_TOKEN }, { token: CliCommandExecutorService }, { token: CliCommandHistoryService }, { token: CliKeyValueStore }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: CliComponent, selector: "cli", inputs: { options: "options", height: "height" }, ngImport: i0, template: "<cli-terminal\n [options]=\"terminalOptions\"\n [height]=\"height\"\n (onTerminalReady)=\"onTerminalReady($event)\"\n/>\n", styles: [""], dependencies: [{ kind: "component", type: CliTerminalComponent, selector: "cli-terminal", inputs: ["options", "height"], outputs: ["onTerminalReady"] }], encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliComponent, decorators: [{
type: Component,
args: [{ selector: 'cli', encapsulation: ViewEncapsulation.None, template: "<cli-terminal\n [options]=\"terminalOptions\"\n [height]=\"height\"\n (onTerminalReady)=\"onTerminalReady($event)\"\n/>\n" }]
}], ctorParameters: function () { return [{ type: i0.Injector }, { type: undefined, decorators: [{
type: Inject,
args: [ICliUserSessionService_TOKEN]
}] }, { type: CliCommandExecutorService }, { type: CliCommandHistoryService }, { type: CliKeyValueStore }]; }, propDecorators: { options: [{
type: Input
}], height: [{
type: Input
}] } });
class CollapsableContentComponent {
constructor(el) {
this.el = el;
this.previousPanelHeight = 500;
this.panelHeight = 500;
this.isResizing = false;
this.startY = 0;
this.startHeight = 0;
this.visible = true;
this.isCollapsed = true;
this.isMaximized = false;
this.onToggle = new EventEmitter();
this.onContentSizeChange = new EventEmitter();
}
ngOnInit() {
this.items = [
{
label: 'Tab 1',
icon: 'pi pi-plus',
},
];
}
onActiveItemChange(event) {
this.activeItem = event;
}
toggleTerminal() {
this.isCollapsed = !this.isCollapsed;
this.onToggle.emit(this.isCollapsed);
}
closeTerminal() {
this.visible = false;
}
toggleMaximizationTerminal() {
//check next
if (!this.isMaximized) {
this.previousPanelHeight = this.panelHeight;
const windowHeight = window.innerHeight;
this.panelHeight = windowHeight;
}
else {
this.panelHeight = this.previousPanelHeight;
}
this.isMaximized = !this.isMaximized;
this.updateTerminalSize();
}
onResizeStart(event) {
this.isResizing = true;
if (this.isCollapsed) {
this.toggleTerminal();
}
this.startY = event.clientY;
this.startHeight = this.panelHeight;
event.preventDefault();
}
onMouseMove(event) {
if (this.isResizing) {
const deltaY = this.startY - event.clientY;
let nextHeight = Math.max(100, this.startHeight + deltaY);
if (nextHeight > window.innerHeight) {
nextHeight = window.innerHeight;
}
this.panelHeight = nextHeight;
this.updateTerminalSize();
}
}
onMouseUp() {
this.isResizing = false;
}
updateTerminalSize() {
this.onContentSizeChange.emit(this.panelHeight - 60 - 8);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CollapsableContentComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: CollapsableContentComponent, selector: "collapsable-content", inputs: { visible: "visible", isCollapsed: "isCollapsed", isMaximized: "isMaximized" }, outputs: { onToggle: "onToggle", onContentSizeChange: "onContentSizeChange" }, host: { listeners: { "document:mousemove": "onMouseMove($event)", "document:mouseup": "onMouseUp()" } }, ngImport: i0, template: "<div\n *ngIf=\"visible\"\n class=\"terminal-wrapper\"\n [class.collapsed]=\"isCollapsed\"\n [style]=\"{\n height: panelHeight + 'px',\n }\"\n>\n <div class=\"card terminal-header\">\n <div class=\"divider\">\n <i class=\"pi pi-minus\" (mousedown)=\"onResizeStart($event)\"></i>\n </div>\n <div class=\"header-content\">\n <p class=\"terminal-title\">\n <i class=\"pi pi-desktop\" style=\"font-size: 2rem\"></i>\n CLI\n </p>\n <div class=\"action-buttons\">\n <p-button\n [icon]=\"\n isMaximized ? 'pi pi-window-minimize' : 'pi pi-window-maximize'\n \"\n class=\"p-button-rounded p-button-text p-button-secondary\"\n [pTooltip]=\"!isMaximized ? 'Maximize' : 'Minimize'\"\n tooltipPosition=\"top\"\n [disabled]=\"isCollapsed\"\n (onClick)=\"toggleMaximizationTerminal()\"\n />\n\n <p-button\n [icon]=\"isCollapsed ? 'pi pi-chevron-up' : 'pi pi-chevron-down'\"\n class=\"p-button-rounded p-button-text p-button-secondary\"\n [pTooltip]=\"isCollapsed ? 'Expand' : 'Collapse'\"\n tooltipPosition=\"top\"\n (onClick)=\"toggleTerminal()\"\n />\n\n <p-button\n [icon]=\"'pi pi-eye-slash'\"\n class=\"p-button-rounded p-button-text p-button-secondary\"\n [pTooltip]=\"'Close'\"\n tooltipPosition=\"top\"\n (onClick)=\"closeTerminal()\"\n />\n </div>\n </div>\n </div>\n <div class=\"terminal-content\" *ngIf=\"!isCollapsed\">\n <ng-content></ng-content>\n </div>\n</div>\n", styles: [".terminal-wrapper{position:fixed;bottom:0;left:0;right:0;width:100vw;transition:transform .3s ease-in-out;transform:translateY(0);background-color:#0c0c0c;color:#fff;border-top:1px solid #444;display:flex;flex-direction:column;z-index:1000}.terminal-wrapper.collapsed{transform:translateY(calc(100% - 60px))}.terminal-header{background-color:var(--surface-a);display:flex;flex-direction:column}.terminal-header .divider{display:flex;justify-content:center;position:absolute;width:100%;top:-10px}.terminal-header .divider i{font-size:2rem;cursor:ns-resize}.terminal-header .header-content{height:60px;display:flex;flex-direction:row;gap:1rem;align-items:center;border-bottom:1px solid #444;padding:.5rem 1rem}.terminal-header .header-content .terminal-title{font-size:1rem;display:flex;gap:.5rem;justify-content:center;align-items:center}.terminal-header .header-content .action-buttons{display:flex;flex-direction:row;gap:1rem;margin-left:auto}.terminal-content{flex:1;width:calc(100% - 8px)}\n"], dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i2.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "size", "style", "styleClass", "badgeClass", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: i3.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "appendTo", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions"] }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CollapsableContentComponent, decorators: [{
type: Component,
args: [{ selector: 'collapsable-content', template: "<div\n *ngIf=\"visible\"\n class=\"terminal-wrapper\"\n [class.collapsed]=\"isCollapsed\"\n [style]=\"{\n height: panelHeight + 'px',\n }\"\n>\n <div class=\"card terminal-header\">\n <div class=\"divider\">\n <i class=\"pi pi-minus\" (mousedown)=\"onResizeStart($event)\"></i>\n </div>\n <div class=\"header-content\">\n <p class=\"terminal-title\">\n <i class=\"pi pi-desktop\" style=\"font-size: 2rem\"></i>\n CLI\n </p>\n <div class=\"action-buttons\">\n <p-button\n [icon]=\"\n isMaximized ? 'pi pi-window-minimize' : 'pi pi-window-maximize'\n \"\n class=\"p-button-rounded p-button-text p-button-secondary\"\n [pTooltip]=\"!isMaximized ? 'Maximize' : 'Minimize'\"\n tooltipPosition=\"top\"\n [disabled]=\"isCollapsed\"\n (onClick)=\"toggleMaximizationTerminal()\"\n />\n\n <p-button\n [icon]=\"isCollapsed ? 'pi pi-chevron-up' : 'pi pi-chevron-down'\"\n class=\"p-button-rounded p-button-text p-button-secondary\"\n [pTooltip]=\"isCollapsed ? 'Expand' : 'Collapse'\"\n tooltipPosition=\"top\"\n (onClick)=\"toggleTerminal()\"\n />\n\n <p-button\n [icon]=\"'pi pi-eye-slash'\"\n class=\"p-button-rounded p-button-text p-button-secondary\"\n [pTooltip]=\"'Close'\"\n tooltipPosition=\"top\"\n (onClick)=\"closeTerminal()\"\n />\n </div>\n </div>\n </div>\n <div class=\"terminal-content\" *ngIf=\"!isCollapsed\">\n <ng-content></ng-content>\n </div>\n</div>\n", styles: [".terminal-wrapper{position:fixed;bottom:0;left:0;right:0;width:100vw;transition:transform .3s ease-in-out;transform:translateY(0);background-color:#0c0c0c;color:#fff;border-top:1px solid #444;display:flex;flex-direction:column;z-index:1000}.terminal-wrapper.collapsed{transform:translateY(calc(100% - 60px))}.terminal-header{background-color:var(--surface-a);display:flex;flex-direction:column}.terminal-header .divider{display:flex;justify-content:center;position:absolute;width:100%;top:-10px}.terminal-header .divider i{font-size:2rem;cursor:ns-resize}.terminal-header .header-content{height:60px;display:flex;flex-direction:row;gap:1rem;align-items:center;border-bottom:1px solid #444;padding:.5rem 1rem}.terminal-header .header-content .terminal-title{font-size:1rem;display:flex;gap:.5rem;justify-content:center;align-items:center}.terminal-header .header-content .action-buttons{display:flex;flex-direction:row;gap:1rem;margin-left:auto}.terminal-content{flex:1;width:calc(100% - 8px)}\n"] }]
}], ctorParameters: function () { return [{ type: i0.ElementRef }]; }, propDecorators: { visible: [{
type: Input
}], isCollapsed: [{
type: Input
}], isMaximized: [{
type: Input
}], onToggle: [{
type: Output
}], onContentSizeChange: [{
type: Output
}], onMouseMove: [{
type: HostListener,
args: ['document:mousemove', ['$event']]
}], onMouseUp: [{
type: HostListener,
args: ['document:mouseup']
}] } });
class CliCanViewService {
constructor() { }
canView() {
return of(true);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliCanViewService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliCanViewService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliCanViewService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}], ctorParameters: function () { return []; } });
/**
* A component that displays the CLI on the bottom of page.
*/
class CliPanelComponent {
constructor(canView) {
this.canView = canView;
this.visible = false;
this.terminalHeight = `${450 - 8}px`;
this.initialized = false;
this.subscriptions = new Subscription();
this.subscriptions.add(this.canView.canView().subscribe((canView) => {
this.visible = canView;
}));
}
onToggle($event) {
if (!$event && !this.initialized) {
this.initialized = true;
}
}
onContentSizeChange($event) {
this.terminalHeight = `${$event}px`;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliPanelComponent, deps: [{ token: CliCanViewService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: CliPanelComponent, selector: "cli-panel", inputs: { options: "options" }, ngImport: i0, template: "<collapsable-content\n [isCollapsed]=\"options?.isCollapsed ?? true\"\n (onToggle)=\"onToggle($event)\"\n (onContentSizeChange)=\"onContentSizeChange($event)\"\n *ngIf=\"visible\"\n>\n <ng-container *ngIf=\"initialized\">\n <cli [options]=\"options\" [height]=\"terminalHeight\" />\n </ng-container>\n</collapsable-content>\n", styles: [".terminal-tabs{background-color:#1e1e1e;color:#fff;display:flex;align-items:center;padding:10px}.terminal-tabs .tab-list{list-style:none;margin:0;padding:0;display:flex}.terminal-tabs .tab-list li{padding:8px 16px;margin-right:5px;cursor:pointer;border:1px solid #444;border-radius:4px;background-color:#333;display:flex;align-items:center}.terminal-tabs .tab-list li.active{background-color:#007ad9;border-color:#005bb5}.terminal-tabs .tab-list li .close-btn{margin-left:10px;background:none;border:none;color:#fff;cursor:pointer;font-size:.8rem}.terminal-tabs .tab-list .add-tab{padding:8px 16px;margin-left:5px;cursor:pointer;background-color:#444;border:1px dashed #666;border-radius:4px;color:#aaa}.terminal-tabs .tab-list .add-tab:hover{background-color:#555}.terminal-content{background-color:#1e1e1e;color:#fff;padding:15px;border-top:1px solid #444}.terminal-instance{display:flex;flex-direction:column;height:400px}\n"], dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: CliComponent, selector: "cli", inputs: ["options", "height"] }, { kind: "component", type: CollapsableContentComponent, selector: "collapsable-content", inputs: ["visible", "isCollapsed", "isMaximized"], outputs: ["onToggle", "onContentSizeChange"] }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliPanelComponent, decorators: [{
type: Component,
args: [{ selector: 'cli-panel', template: "<collapsable-content\n [isCollapsed]=\"options?.isCollapsed ?? true\"\n (onToggle)=\"onToggle($event)\"\n (onContentSizeChange)=\"onContentSizeChange($event)\"\n *ngIf=\"visible\"\n>\n <ng-container *ngIf=\"initialized\">\n <cli [options]=\"options\" [height]=\"terminalHeight\" />\n </ng-container>\n</collapsable-content>\n", styles: [".terminal-tabs{background-color:#1e1e1e;color:#fff;display:flex;align-items:center;padding:10px}.terminal-tabs .tab-list{list-style:none;margin:0;padding:0;display:flex}.terminal-tabs .tab-list li{padding:8px 16px;margin-right:5px;cursor:pointer;border:1px solid #444;border-radius:4px;background-color:#333;display:flex;align-items:center}.terminal-tabs .tab-list li.active{background-color:#007ad9;border-color:#005bb5}.terminal-tabs .tab-list li .close-btn{margin-left:10px;background:none;border:none;color:#fff;cursor:pointer;font-size:.8rem}.terminal-tabs .tab-list .add-tab{padding:8px 16px;margin-left:5px;cursor:pointer;background-color:#444;border:1px dashed #666;border-radius:4px;color:#aaa}.terminal-tabs .tab-list .add-tab:hover{background-color:#555}.terminal-content{background-color:#1e1e1e;color:#fff;padding:15px;border-top:1px solid #444}.terminal-instance{display:flex;flex-direction:column;height:400px}\n"] }]
}], ctorParameters: function () { return [{ type: CliCanViewService }]; }, propDecorators: { options: [{
type: Input
}] } });
class CliPingCommandProcessor {
constructor(pingServerService) {
this.pingServerService = pingServerService;
this.command = 'ping';
this.description = 'Pings the server';
this.author = DefaultLibraryAuthor;
this.metadata = {
icon: '🏓',
};
}
async processCommand(_, context) {
context.spinner?.show();
context?.spinner?.setText('Pinging server...');
this.pingServerService
.ping()
.then(() => {
context.spinner?.hide();
context.writer.writeln('pong');
context.showPrompt();
})
.catch(() => {
context.spinner?.hide();
context.writer.writeError('Failed to ping the server');
context.showPrompt();
});
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliPingCommandProcessor, deps: [{ token: ICliPingServerService_TOKEN }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliPingCommandProcessor }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliPingCommandProcessor, decorators: [{
type: Injectable
}], ctorParameters: function () { return [{ type: undefined, decorators: [{
type: Inject,
args: [ICliPingServerService_TOKEN]
}] }]; } });
class CliEvalCommandProcessor {
constructor() {
this.command = 'eval';
this.description = 'Evaluate a JavaScript expression';
this.author = DefaultLibraryAuthor;
this.allowUnlistedCommands = true;
this.metadata = {
icon: '🧮',
module: 'misc',
};
}
async processCommand(command, context) {
try {
const output = eval(command.value ?? '');
if (Array.isArray(output)) {
context.writer.writeln('Output:');
context.writer.writeJson(output);
return;
}
if (typeof output === 'object') {
context.writer.writeln('Output:');
context.writer.writeJson(output);
return;
}
context.writer.writeln('Output: ' + output?.toString());
}
catch (e) {
context.writer.writeError(e.toString());
}
}
writeDescription(context) {
context.writer.writeln(this.description);
context.writer.writeln('Examples:');
context.writer.writeln(' eval 1 + 1');
context.writer.writeln(' eval "Hello, " + "World!"');
}
}
const githubUrl = 'https://github.com/qodalis-solutions/angular-web-cli';
class CliFeedbackCommandProcessor {
constructor() {
this.command = 'feedback';
this.description = 'Allows users to report bugs or request features';
this.processors = [];
this.author = DefaultLibraryAuthor;
this.metadata = {
sealed: true,
icon: CliIcon.Bug,
module: 'system',
};
this.processors?.push({
command: 'report-bug',
description: 'Reports a bug on GitHub',
allowUnlistedCommands: true,
async processCommand({ value }, context) {
openLink(`${githubUrl}/issues/new?assignees=&labels=bug&projects=&template=bug_report.md&title=${value}`);
},
writeDescription({ writer }) {
writer.writeln('Reports a bug on GitHub. Usage: feedback report-bug <description>');
},
}, {
command: 'request-feature',
description: 'Requests a new feature on GitHub',
allowUnlistedCommands: true,
async processCommand({ value }, context) {
openLink(`${githubUrl}/issues/new?assignees=&labels=feature-request&projects=&template=feature_request.md&title=${value}`);
},
writeDescription({ writer }) {
writer.writeln('Requests a new feature on GitHub. Usage: feedback request-feature <description>');
},
}, {
command: 'request-command',
description: 'Requests a new command on GitHub',
allowUnlistedCommands: true,
async processCommand({ value }, context) {
openLink(`${githubUrl}/issues/new?assignees=&labels=command-request&projects=&template=command-request.md&title=${value}`);
},
writeDescription({ writer }) {
writer.writeln('Requests a new command on GitHub. Usage: feedback request-command <description>');
},
});
}
async processCommand(_, { writer }) {
writer.writeln('Use one of the following subcommands:');
this.processors?.forEach((processor) => {
writer.writeln(`- ${writer.wrapInColor(`feedback ${processor.command}`, CliForegroundColor.Cyan)}: ${processor.description}`);
});
}
writeDescription({ writer }) {
writer.writeln('Allows users to report bugs or request features on GitHub.');
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliFeedbackCommandProcessor, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliFeedbackCommandProcessor, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliFeedbackCommandProcessor, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}], ctorParameters: function () { return []; } });
const groupBy = (list, keySelector) => {
return list.reduce((map, item) => {
const key = keySelector(item);
if (!map.has(key)) {
map.set(key, []);
}
map.get(key).push(item);
return map;
}, new Map());
};
class CliHelpCommandProcessor {
constructor(injector) {
this.injector = injector;
this.command = 'help';
this.description = 'Displays help for a command';
this.allowUnlistedCommands = true;
this.author = DefaultLibraryAuthor;
this.metadata = {
sealed: true,
icon: CliIcon.Help,
module: 'system',
};
this.registry = this.injector.get(CliProcessorsRegistry_TOKEN);
}
async processCommand(command, context) {
const { writer } = context;
const [_, ...commandsToHelp] = command.command.split(' ');
if (commandsToHelp.length === 0) {
await context.executor.executeCommand('version', context);
this.writeSeparator(context);
writer.writeln(writer.wrapInColor('Available commands:', CliForegroundColor.Yellow));
const groupedCommands = groupBy(this.registry.processors, (x) => x.metadata?.module || 'uncategorized');
groupedCommands.forEach((processors, module) => {
writer.writeln(writer.wrapInColor(module, CliForegroundColor.Yellow));
processors.forEach((processor) => {
writer.writeln(`- ${processor?.metadata?.icon ? processor.metadata.icon : CliIcon.Extension} ${writer.wrapInColor(processor.command, CliForegroundColor.Cyan)} - ${processor?.description || 'Missing description'}`);
});
this.writeSeparator(context);
});
context.writer.writeln();
await context.executor.executeCommand('hotkeys', context);
this.writeSeparator(context);
writer.writeln('\nType `help <command>` to get more information about a specific command');
}
else {
const processor = this.registry.findProcessor(commandsToHelp[0], commandsToHelp.slice(1));
if (processor) {
this.writeProcessorDescription(processor, context);
}
else {
writer.writeln(`\x1b[33mUnknown command: ${commandsToHelp[0]}`);
}
}
}
writeDescription({ writer }) {
writer.writeln('Displays help for a command');
writer.writeln('If no command is specified, it will display a list of available commands');
writer.writeln('If a command is specified, it will display information about that command');
writer.writeln('If a command is specified with a subcommand, it will display information about that subcommand');
}
writeProcessorDescription(processor, context) {
const { writer } = context;
writer.write('\x1b[33mCommand: \x1b[0m');
if (processor.metadata?.icon) {
writer.write(`${processor.metadata.icon} `);
}
writer.writeln(`${writer.wrapInColor(processor.command, CliForegroundColor.Cyan)} @${processor.version || '1.0.0'} - ${processor.description}`);
this.writeSeparator(context);
if (processor.author) {
writer.writeln(`\x1b[33mAuthor:\x1b[0m ${processor.author.name}<${processor.author.email}>`);
this.writeSeparator(context);
}
writer.write(writer.wrapInColor('Description: ', CliForegroundColor.Yellow));
if (processor.writeDescription) {
processor.writeDescription(context);
}
else if (processor.description) {
writer.writeln(`${writer.wrapInColor('Description:', CliForegroundColor.Yellow)} ${processor.description}`);
}
else {
writer.writeln(writer.wrapInColor('No description available', CliForegroundColor.Yellow));
}
this.writeSeparator(context);
if (processor.processors?.length) {
writer.writeln(writer.wrapInColor('Subcommands:', CliForegroundColor.Yellow));
processor.processors.forEach((subprocessor) => {
writer.writeln(`- ${writer.wrapInColor(subprocessor.command, CliForegroundColor.Cyan)} - ${subprocessor.description}`);
});
this.writeSeparator(context);
}
const parameters = [
...(processor.parameters || []),
...defaultParameters,
];
writer.writeln(writer.wrapInColor('Parameters:', CliForegroundColor.Yellow));
parameters.forEach((parameter) => {
writer.writeln(`--${writer.wrapInColor(parameter.name, CliForegroundColor.Cyan)} (${parameter.type}) ${parameter.aliases ? `(${parameter.aliases.join(', ')})` : ''} - ${parameter.description}${parameter.required ? ' (required)' : ''}`);
});
this.writeSeparator(context);
if (processor.metadata?.requireServer) {
writer.writeln(writer.wrapInColor('Requires server to be running', CliForegroundColor.Red));
}
}
writeSeparator({ writer }) {
writer.writeDivider({
char: '=',
});
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliHelpCommandProcessor, deps: [{ token: i0.Injector }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliHelpCommandProcessor, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliHelpCommandProcessor, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}], ctorParameters: function () { return [{ type: i0.Injector }]; } });
const defaultParameters = [
{
name: 'version',
aliases: ['v'],
type: 'boolean',
description: 'Displays the version of the command',
required: false,
},
{
name: 'main',
type: 'boolean',
description: 'Set the command as the main command',
required: false,
},
];
class CliHistoryCommandProcessor {
constructor(commandHistoryService) {
this.commandHistoryService = commandHistoryService;
this.command = 'history';
this.description = 'Prints the command history of the current session';
this.processors = [];
this.author = DefaultLibraryAuthor;
this.metadata = {
sealed: true,
icon: CliIcon.Code,
module: 'system',
};
this.processors?.push({
command: 'list',
description: this.description,
processCommand: this.processCommand.bind(this),
writeDescription: this.writeDescription.bind(this),
});
this.processors?.push({
command: 'clear',
description: 'Clears the command history',
processCommand: async (_, context) => {
await this.commandHistoryService.clearHistory();
context.writer.writeInfo('Command history cleared');
},
writeDescription: (context) => {
context.writer.writeln('Clears the command history');
},
});
}
async processCommand(_, { writer }) {
const history = this.commandHistoryService.getHistory();
if (history.length === 0) {
writer.writeln('No history available');
return;
}
else {
writer.writeln('Command history:');
history.forEach((command, index) => {
writer.writeln(`${index + 1}. ${command}`);
});
}
}
writeDescription({ writer }) {
writer.writeln('Prints the command history of the current session');
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliHistoryCommandProcessor, deps: [{ token: CliCommandHistoryService }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliHistoryCommandProcessor, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliHistoryCommandProcessor, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}], ctorParameters: function () { return [{ type: CliCommandHistoryService }]; } });
class CliHotKeysCommandProcessor {
constructor() {
this.command = 'hotkeys';
this.description = 'Displays the hotkeys information';
this.processors = [];
this.author = DefaultLibraryAuthor;
this.metadata = {
sealed: true,
icon: '🔥',
module: 'system',
};
}
async processCommand(_, context) {
context.writer.writeln(context.writer.wrapInColor('Avalaible hotkeys:', CliForegroundColor.Yellow));
hotkeysInfo.forEach((hotkey) => {
context.writer.writeln(`- ${context.writer.wrapInColor(hotkey.key, CliForegroundColor.Yellow)} - ${hotkey.description}`);
});
}
writeDescription(context) {
context.writer.writeln(this.description || 'Displays the hotkeys information');
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliHotKeysCommandProcessor, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliHotKeysCommandProcessor, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliHotKeysCommandProcessor, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}] });
class CliPackageManagerService {
constructor(store) {
this.store = store;
this.storageKey = 'cli-packages';
this.QODALIS_COMMAND_PREFIX = '@qodalis/cli-';
}
/**
* Retrieves the list of packages
* @returns {Package[]} Array of packages
*/
async getPackages() {
const oldPackages = localStorage.getItem('cliPackages');
if (oldPackages) {
localStorage.removeItem('cliPackages');
await this.store.set(this.storageKey, JSON.parse(oldPackages));
}
const packages = await this.store.get(this.storageKey);
return packages ? packages : [];
}
/**
* Retrieves a package by name from the list.
* @param packageName {string} The name of the package to retrieve
* @returns {Package | undefined} The package if found, undefined otherwise
*/
async getPackage(packageName) {
return (await this.getPackages()).find((p) => p.name === packageName ||
p.name === this.QODALIS_COMMAND_PREFIX + packageName);
}
/**
* Checks if a package with the given name exists in the list.
* @param packageName {string} The name of the package to check
* @returns {boolean} True if the package exists, false otherwise
*/
async hasPackage(packageName) {
return (await this.getPackage(packageName)) !== undefined;
}
/**
* Adds a new package to the list and saves it in storage.
* @param pkg {Package} The package to add
*/
async addPackage(pkg) {
const packages = await this.getPackages();
if (packages.find((p) => p.name === pkg.name)) {
throw new Error(`Package with name "${pkg.name}" already exists.`);
}
packages.push(pkg);
await this.savePackages(packages);
}
/**
* Removes a package by name and saves the updated list in storage
* @param packageName {string} The name of the package to remove
*/
async removePackage(packageName) {
const packages = await this.getPackages();
const packageToRemove = packages.find((p) => p.name === packageName ||
p.name === this.QODALIS_COMMAND_PREFIX + packageName);
if (!packageToRemove) {
throw new Error(`Package with name "${packageName}" not found.`);
}
const updatedPackages = packages.filter((p) => p.name !== packageToRemove.name);
if (packages.length === updatedPackages.length) {
throw new Error(`Package with name "${packageName}" not found.`);
}
this.savePackages(updatedPackages);
return packageToRemove;
}
/**
* Updates an existing package in the list and saves the updated list in storage.
* @param pkg {Package} The updated package
* @throws {Error} If the package to update is not found
* @returns {void}
* @throws {Error} If the package to update is not found
* @returns {void}
*/
async updatePackage(pkg) {
const packages = await this.getPackages();
const index = packages.findIndex((p) => p.name === pkg.name);
if (index === -1) {
throw new Error(`Package with name "${pkg.name}" not found.`);
}
packages[index] = pkg;
await this.savePackages(packages);
}
/**
* Saves the list of packages to storage.
* @param packages {Package[]} The list of packages to save
*/
async savePackages(packages) {
await this.store.set(this.storageKey, packages);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliPackageManagerService, deps: [{ token: CliKeyValueStore }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliPackageManagerService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliPackageManagerService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}], ctorParameters: function () { return [{ type: CliKeyValueStore }]; } });
class CliPackagesCommandProcessor {
constructor(scriptsLoader, packagesManager, registry) {
this.scriptsLoader = scriptsLoader;
this.packagesManager = packagesManager;
this.registry = registry;
this.command = 'packages';
this.description = 'Manage packages in the cli';
this.author = DefaultLibraryAuthor;
this.version = '1.0.2';
this.processors = [];
this.metadata = {
sealed: true,
icon: '📦',
module: 'system',
};
this.registeredDependencies = [];
const scope = this;
this.processors = [
{
command: 'ls',
description: 'List all packages in the cli',
author: DefaultLibraryAuthor,
async processCommand(_, context) {
const packages = await packagesManager.getPackages();
context.writer.writeln('Packages:');
if (packages.length === 0) {
context.writer.writeInfo('No packages installed');
context.writer.writeInfo("Use 'packages add <package>' to add a package");
return;
}
packages.forEach((pkg) => {
context.writer.writeInfo(`- ${pkg.name}@${pkg.version}`);
});
},
writeDescription(context) {
context.writer.writeln(' packages ls');
},
},
{
command: 'add',
description: 'Add a package to the cli, e.g. packages add guid.',
valueRequired: true,
async processCommand(command, context) {
const packages = command.value.split(' ');
for (const pkg of packages) {
await scope.addPackage(pkg, context);
}
},
writeDescription({ writer }) {
writer.writeInfo("Supoorts multiple packages, e.g. 'packages add guid server-logs'");
writer.writeln(' packages add <package> ' +
writer.wrapInColor('# Add a package', CliForegroundColor.Green));
writer.writeln(' packages add guid');
writer.writeln(' packages add @qodalis/cli-guid');
writer.writeln(' packages add @qodalis/cli-server-logs');
},
},
{
command: 'remove',
description: 'Remove a package from the cli',
valueRequired: true,
async processCommand(command, context) {
const packages = command.value.split(' ');
for (const pkg of packages) {
await scope.removePackage(pkg, context);
}
},
},
{
command: 'update',
description: 'Update a package in the cli to the latest version or update all packages',
allowUnlistedCommands: true,
valueRequired: false,
async processCommand(command, context) {
if (command.value) {
await scope.updatePackage(command.value, context);
}
else {
const packages = await packagesManager.getPackages();
for (const pkg of packages) {
await scope.updatePackage(pkg.name, context);
}
context.writer.writeSuccess('All packages updated');
}
},
writeDescription(context) {
context.writer.writeln(' packages update <package> ' +
context.writer.wrapInColor('# Update a specific package', CliForegroundColor.Green));
context.writer.writeln(' packages update ' +
context.writer.wrapInColor('# Update all packages', CliForegroundColor.Green));
},
},
];
}
async processCommand(command, context) {
await context.executor.showHelp(command, context);
}
writeDescription({ writer }) {
writer.writeln(this.description);
writer.writeInfo('Available packages can be found at https://www.npmjs.com/org/qodalis');
writer.writeln(' packages ls');
writer.writeln(' packages add <package>');
}
async initialize(context) {
const packages = await this.packagesManager.getPackages();
for (const pkg of packages) {
await this.registerPackageDependencies(pkg, context);
await this.scriptsLoader.injectScript(pkg.url);
}
}
async removePackage(name, context) {
context.progressBar.show();
try {
const pkg = await this.packagesManager.removePackage(name);
if (pkg) {
const module = window[pkg.name];
if (module) {
module.processors?.forEach((processor) => {
this.registry.unregisterProcessor(processor);
});
}
}
context.progressBar.complete();
context.writer.writeSuccess(`Package ${pkg.name}@${pkg.version} removed successfully`);
}
catch (e) {
context.progressBar.complete();
context.writer.writeError(e?.toString() || 'Unknown error');
}
}
async addPackage(name, context) {
const { progressBar, writer } = context;
const hasPackage = await this.packagesManager.hasPackage(name);
if (hasPackage) {
writer.writeInfo(`Package ${name} already installed`);
return;
}
progressBar.show();
progressBar.setText(`Fetching package ${name}`);
try {
const promises = [
this.scriptsLoader
.getScript(`https://unpkg.com/${name}` + '/package.json')
.catch((error) => ({
error,
content: null,
xhr: null,
}))
.finally(() => {
progressBar.update(10, {
type: 'increment',
});
}),
];
if (!name.startsWith(this.packagesManager.QODALIS_COMMAND_PREFIX)) {
promises.push(this.scriptsLoader
.getScript(`https://unpkg.com/${this.packagesManager.QODALIS_COMMAND_PREFIX}${name}` +
'/package.json')
.catch((error) => ({
error,
content: null,
xhr: null,
}))
.finally(() => {
progressBar.update(10, {
type: 'increment',
});
}));
}
progressBar.update(10);
const packages = await Promise.all(promises);
if (packages.every((p) => p.error)) {
progressBar.setText('Package not found');
throw packages[0].error;
}
const validResponses = packages
.filter((p) => p.content)
.map((p) => JSON.parse(p.content || '{}'));
progressBar.update(20, {
type: 'increment',
});
progressBar.setText("Checking package's dependencies");
const packgeInfo = validResponses.some((x) => x.name.startsWith(this.packagesManager.QODALIS_COMMAND_PREFIX))
? validResponses.find((x) => x.name.startsWith(this.packagesManager.QODALIS_COMMAND_PREFIX))
: validResponses[0];
const response = await this.scriptsLoader.getScript(`https://unpkg.com/${packgeInfo.name}`, {
onProgress: (progress) => {
context.progressBar.update(progress);
},
});
progressBar.update(20, {
type: 'increment',
});
progressBar.setText('Injecting package');
const pkg = {
name: packgeInfo.name,
version: packgeInfo.version || 'latest',
url: response.xhr.responseURL,
dependencies: packgeInfo.cliDependencies || [],
};
progressBar.update(80);
progressBar.setText('Registering dependencies');
await this.registerPackageDependencies(pkg, context);
progressBar.update(90);
progressBar.setText('Dependencies registered');
if (response.content) {
await this.scriptsLoader.injectBodyScript(response.content);
}
progressBar.update(95);
progressBar.setText('Saving package');
await this.packagesManager.addPackage(pkg);
context.progressBar.complete();
context.writer.writeSuccess(`Package ${packgeInfo.name}@${packgeInfo.version} added successfully`);
}
catch (e) {
context.progressBar.complete();
context.writer.writeError(e?.toString() || 'Unknown error');
}
}
async updatePackage(name, context) {
const { progressBar, writer } = context;
progressBar.show();
try {
progressBar.update(10, {
type: 'increment',
});
progressBar.setText(`Updating package ${name}`);
const currentPackage = await this.packagesManager.getPackage(name);
if (!currentPackage) {
progressBar.complete();
writer.writeError(`Package ${name} not found`);
return;
}
const packageUrl = `https://unpkg.com/${currentPackage.name}`;
const newPackage = await this.scriptsLoader
.getScript(packageUrl + '/package.json')
.then((response) => {
return JSON.parse(response.content || '{}');
});
progressBar.update(20, {
type: 'increment',
});
progressBar.setText(`Checking for updates for package ${currentPackage.name}`);
if (newPackage.version === currentPackage.version) {
context.progressBar.complete();
context.writer.writeInfo(`Package ${currentPackage.name} is already up to date with version ${currentPackage.version}`);
return;
}
const response = await this.scriptsLoader.getScript(packageUrl, {
onProgress: (progress) => {
context.progressBar.update(progress);
},
});
progressBar.update(20, {
type: 'increment',
});
progressBar.setText(`Updating package ${currentPackage.name}`);
const updatedPkg = {
name: newPackage.name,
version: newPackage.version || 'latest',
url: response.xhr.responseURL,
dependencies: newPackage.cliDependencies || [],
};
await this.registerPackageDependencies(updatedPkg, context);
progressBar.update(90);
progressBar.setText(`Injecting package ${currentPackage.name}`);
if (response.content) {
await this.scriptsLoader.injectBodyScript(response.content);
}
await this.packagesManager.updatePackage(updatedPkg);
progressBar.setText(`Package ${currentPackage.name} updated successfully`);
progressBar.complete();
writer.writeSuccess(`Package ${currentPackage.name} updated successfully to version ${newPackage.version} from ${currentPackage.version}`);
}
catch (e) {
progressBar.complete();
writer.writeError(e?.toString() || 'Unknown error');
}
}
async registerPackageDependencies(pkg, { logger }) {
if (pkg.dependencies && pkg.dependencies.length > 0) {
logger.info(`Injecting package ${pkg.name} dependencies`);
for (const dependency of pkg.dependencies) {
if (this.registeredDependencies.includes(dependency.name)) {
logger.info(`Dependency ${dependency.globalName} already registered`);
continue;
}
await this.scriptsLoader.injectScript(dependency.url);
if (dependency.globalName) {
const isAvailable = await this.waitForGlobal(dependency.globalName, 3000);
if (!isAvailable) {
logger.error(`Dependency ${dependency.globalName} not found in global scope within timeout`);
return;
}
else {
logger.info(`Dependency ${dependency.globalName} is available in global scope`);
}
}
this.registeredDependencies.push(dependency.name);
}
}
else {
logger.info(`Package ${pkg.name} has no dependencies`);
}
}
/**
* Waits until a global variable is available on the `window` object.
* @param globalName {string} The name of the global variable to check.
* @param timeout {number} The maximum time to wait in milliseconds.
* @returns {Promise<boolean>} Resolves to `true` if the variable is found, `false` otherwise.
*/
waitForGlobal(globalName, timeout) {
return new Promise((resolve) => {
const interval = 50; // Check every 50ms
let elapsed = 0;
const check = () => {
if (window[globalName]) {
resolve(true);
}
else if (elapsed >= timeout) {
resolve(false);
}
else {
elapsed += interval;
setTimeout(check, interval);
}
};
check();
});
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliPackagesCommandProcessor, deps: [{ token: ScriptLoaderService }, { token: CliPackageManagerService }, { token: CliProcessorsRegistry_TOKEN }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliPackagesCommandProcessor }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliPackagesCommandProcessor, decorators: [{
type: Injectable
}], ctorParameters: function () { return [{ type: ScriptLoaderService }, { type: CliPackageManagerService }, { type: undefined, decorators: [{
type: Inject,
args: [CliProcessorsRegistry_TOKEN]
}] }]; } });
class CliVersionCommandProcessor {
constructor() {
this.command = 'version';
this.description = 'Prints the version information';
this.processors = [];
this.author = DefaultLibraryAuthor;
this.metadata = {
sealed: true,
icon: CliIcon.Settings,
module: 'system',
};
}
async processCommand(_, { writer }) {
writer.writeln(`CLI Version@${writer.wrapInColor(LIBRARY_VERSION, CliForegroundColor.Green)}`);
writer.writeln(CLi_Name_Art);
writer.writeln(writer.wrapInColor('Documentation: ', CliForegroundColor.Green) +
'https://cli-docs.qodalis.com/');
writer.writeln();
}
writeDescription(context) {
context.writer.writeln('Prints the current version of the CLI');
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliVersionCommandProcessor, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliVersionCommandProcessor, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliVersionCommandProcessor, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}] });
const systemProviders = [
resolveCommandProcessorProvider(CliHelpCommandProcessor),
resolveCommandProcessorProvider(CliVersionCommandProcessor),
resolveCommandProcessorProvider(CliFeedbackCommandProcessor),
resolveCommandProcessorProvider(CliHistoryCommandProcessor),
resolveCommandProcessorProvider(CliPackagesCommandProcessor),
resolveCommandProcessorProvider(CliHotKeysCommandProcessor),
];
class CliClearCommandProcessor {
constructor() {
this.command = 'clear';
this.description = 'Clears the terminal';
this.author = DefaultLibraryAuthor;
this.metadata = {
icon: '🧹',
module: 'misc',
};
}
async processCommand(_, context) {
context.terminal.clear();
}
}
class CliUnameCommandProcessor {
constructor() {
this.command = 'uname';
this.description = 'Prints detailed system and browser information';
this.author = DefaultLibraryAuthor;
this.metadata = {
sealed: true,
icon: CliIcon.Flame,
module: 'misc',
};
}
async processCommand(_, context) {
const { writer } = context;
function writeItem(key, value) {
writer.writeln(`- ${writer.wrapInColor(key, CliForegroundColor.Cyan)}: ${value}`);
}
writer.writeInfo('CLI:');
writeItem('Core version', LIBRARY_VERSION$1);
writeItem('Cli version', LIBRARY_VERSION);
writer.writeln();
if (typeof navigator !== 'undefined') {
writer.writeInfo('Browser Information:');
writeItem('User Agent', navigator.userAgent);
writeItem('Language', navigator.language);
writeItem('Platform', navigator.platform);
// Extracting more detailed information from userAgent
const browserDetails = this.parseUserAgent(navigator.userAgent);
writeItem('Browser', browserDetails.browserName);
writeItem('Version', browserDetails.version);
writeItem('OS', browserDetails.os);
}
else {
writer.writeError('Browser information is not available in this environment.');
}
}
parseUserAgent(userAgent) {
let browserName = 'Unknown';
let version = 'Unknown';
let os = 'Unknown';
// Simple parsing logic for demonstration purposes
if (userAgent.includes('Chrome')) {
browserName = 'Chrome';
version = userAgent.match(/Chrome\/([\d.]+)/)?.[1] || 'Unknown';
}
else if (userAgent.includes('Firefox')) {
browserName = 'Firefox';
version = userAgent.match(/Firefox\/([\d.]+)/)?.[1] || 'Unknown';
}
else if (userAgent.includes('Safari') &&
!userAgent.includes('Chrome')) {
browserName = 'Safari';
version = userAgent.match(/Version\/([\d.]+)/)?.[1] || 'Unknown';
}
else if (userAgent.includes('MSIE') ||
userAgent.includes('Trident')) {
browserName = 'Internet Explorer';
version =
userAgent.match(/(?:MSIE |rv:)([\d.]+)/)?.[1] || 'Unknown';
}
if (userAgent.includes('Windows')) {
os = 'Windows';
}
else if (userAgent.includes('Mac OS')) {
os = 'Mac OS';
}
else if (userAgent.includes('Linux')) {
os = 'Linux';
}
else if (userAgent.includes('Android')) {
os = 'Android';
}
else if (userAgent.includes('iPhone') || userAgent.includes('iPad')) {
os = 'iOS';
}
return { browserName, version, os };
}
}
class CliEchoCommandProcessor {
constructor() {
this.command = 'echo';
this.description = 'Prints the specified text';
this.allowUnlistedCommands = true;
this.author = DefaultLibraryAuthor;
this.metadata = {
icon: '📢',
module: 'misc',
};
}
async processCommand(command, context) {
const text = command.value || command.data || '';
if (typeof text === 'object') {
context.writer.writeJson(text);
}
else {
context.writer.writeln(text);
}
context.process.output(text);
}
writeDescription(context) {
context.writer.writeln('echo <text>');
context.writer.writeln('Prints the specified text');
}
}
class CliAliasCommandProcessor {
constructor() {
this.command = 'alias';
this.author = DefaultLibraryAuthor;
this.description = 'Manage aliases for commands';
this.metadata = {
icon: '🔥',
module: 'misc',
sealed: true,
};
this.stateConfiguration = {
initialState: {
aliases: {},
},
storeName: 'aliases',
};
this.aliases = {};
this.processors = [
{
command: 'ls',
description: 'List all aliases',
processCommand: async (_, context) => {
const { writer } = context;
writer.writeln('Aliases:');
Object.entries(this.aliases).forEach(([alias, command]) => {
writer.writeInfo(` ${alias} -> ${command}`);
});
if (Object.keys(this.aliases).length === 0) {
writer.writeInfo(' No aliases defined');
}
},
},
];
}
async processCommand(command, context) {
const registry = context.services.get(CliProcessorsRegistry_TOKEN);
const { writer } = context;
const aliases = Object.keys(command.args);
if (aliases.length === 0) {
writer.writeError('No aliases provided');
context.process.exit(-1);
return;
}
for (const alias of aliases) {
if (registry.processors.some((p) => p.command === alias)) {
writer.writeError(`${alias} cannot be aliased to ${command.args[alias]}`);
context.process.exit(-1);
return;
}
context.writer.writeInfo(`${alias} -> ${command.args[alias]}`);
}
context.state.updateState({
aliases: {
...this.aliases,
...command.args,
},
});
await context.state.persist();
}
async initialize(context) {
context.state
.select((x) => x['aliases'])
.subscribe((aliases) => {
this.aliases = aliases ?? {};
});
}
}
class CliUnAliasCommandProcessor {
constructor() {
this.command = 'unalias';
this.author = DefaultLibraryAuthor;
this.description = 'Remove aliases for commands';
this.valueRequired = true;
this.stateConfiguration = {
initialState: {},
storeName: 'aliases',
};
this.metadata = {
icon: '🔥',
module: 'misc',
sealed: true,
};
}
async processCommand(command, context) {
const { writer } = context;
const aliasToRemove = command.value;
const { aliases } = context.state.getState();
if (!aliases[aliasToRemove]) {
writer.writeError(`Alias ${aliasToRemove} not found`);
context.process.exit(-1);
return;
}
const updated = {};
Object.keys(aliases)
.filter((alias) => alias !== aliasToRemove)
.forEach((alias) => {
updated[alias] = aliases[alias];
});
context.state.updateState({
aliases: updated,
});
await context.state.persist();
}
}
class CliSleepCommandProcessor {
constructor() {
this.command = 'sleep';
this.description = 'Sleep for a specified amount of time';
this.author = DefaultLibraryAuthor;
this.metadata = {
sealed: true,
module: 'misc',
icon: CliIcon.Timer,
};
this.valueRequired = true;
}
async processCommand(command, { writer }) {
const time = parseInt(command.value);
await delay(time);
writer.writeInfo(`Slept for ${time}ms`);
}
writeDescription(context) {
context.writer.writeln('Sleep for a specified amount of time');
context.writer.writeln('Usage: sleep <time>');
context.writer.writeln('Usage: sleep 5000 # Sleep for 5 seconds');
}
}
const miscProcessors = [
new CliClearCommandProcessor(),
new CliEchoCommandProcessor(),
new CliEvalCommandProcessor(),
new CliAliasCommandProcessor(),
new CliUnAliasCommandProcessor(),
new CliSleepCommandProcessor(),
new CliUnameCommandProcessor(),
];
class CliAddUserCommandProcessor {
constructor(usersStore) {
this.usersStore = usersStore;
this.command = 'adduser';
this.description = 'Add a new user';
this.author = DefaultLibraryAuthor;
this.metadata = {
sealed: true,
module: 'users',
icon: CliIcon.User,
};
this.stateConfiguration = {
initialState: {},
storeName: 'users',
};
this.allowUnlistedCommands = true;
this.valueRequired = true;
this.parameters = [
{
name: 'email',
description: 'The email of the user',
required: true,
type: 'email',
validator: (value) => {
//validate email format
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
if (!emailRegex.test(value)) {
return {
valid: false,
message: 'Invalid email format',
};
}
return {
valid: true,
};
},
},
{
name: 'groups',
description: 'The groups the user belongs to, separated by commas',
type: 'string',
required: false,
},
];
}
async processCommand(command, context) {
const name = command.value;
const email = command.args['email'];
const groups = command.args['groups']?.split(',') || [];
try {
await this.usersStore.createUser({
name,
email,
groups,
});
context.writer.writeInfo('User created successfully');
}
catch (e) {
console.error(e);
context.writer.writeError(e?.toString() || 'An error occurred while creating the user');
}
}
writeDescription(context) {
context.writer.writeln('Add a new user');
context.writer.writeln('Usage: adduser <name> --email=<email> --groups=<groups>');
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliAddUserCommandProcessor, deps: [{ token: ICliUsersStoreService_TOKEN }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliAddUserCommandProcessor, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliAddUserCommandProcessor, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}], ctorParameters: function () { return [{ type: undefined, decorators: [{
type: Inject,
args: [ICliUsersStoreService_TOKEN]
}] }]; } });
class CliListUsersCommandProcessor {
constructor(usersStore) {
this.usersStore = usersStore;
this.command = 'listusers';
this.description = 'List all users';
this.author = DefaultLibraryAuthor;
this.metadata = {
sealed: true,
module: 'users',
icon: CliIcon.User,
};
this.stateConfiguration = {
initialState: {},
storeName: 'users',
};
this.parameters = [
{
name: 'query',
description: 'The query to filter the users',
type: 'string',
required: false,
},
{
name: 'skip',
description: 'The number of users to skip',
type: 'number',
required: false,
},
{
name: 'take',
description: 'The maximum number of users to return',
type: 'number',
required: false,
},
];
}
async processCommand(command, { writer }) {
const users = await firstValueFrom(this.usersStore.getUsers({
query: command.args['query'],
skip: command.args['skip'],
take: command.args['take'],
}));
writer.writeln('Users:');
writer.writeObjectsAsTable(users);
}
writeDescription({ writer }) {
writer.writeln(this.description);
writer.writeln('Usage: listusers');
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliListUsersCommandProcessor, deps: [{ token: ICliUsersStoreService_TOKEN }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliListUsersCommandProcessor, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliListUsersCommandProcessor, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}], ctorParameters: function () { return [{ type: undefined, decorators: [{
type: Inject,
args: [ICliUsersStoreService_TOKEN]
}] }]; } });
class CliSwitchUserCommandProcessor {
constructor(userSessionService, usersStore) {
this.userSessionService = userSessionService;
this.usersStore = usersStore;
this.command = 'su';
this.description = 'Switch user';
this.allowUnlistedCommands = true;
this.parameters = [
{
name: 'reload',
description: 'Reload the page after switching user',
type: 'boolean',
required: false,
aliases: ['r'],
},
];
this.author = DefaultLibraryAuthor;
this.metadata = {
sealed: true,
module: 'users',
icon: CliIcon.User,
};
this.stateConfiguration = {
initialState: {},
storeName: 'users',
};
this.valueRequired = true;
}
async processCommand(command, context) {
try {
const fromUser = context.userSession?.user;
const toUser = command.value;
if (!fromUser) {
context.writer.writeError('Missing user to switch from');
context.process.exit(-1);
return;
}
if (!toUser) {
context.writer.writeError('Missing user to switch to');
context.process.exit(-1);
return;
}
context.spinner?.show(CliIcon.User + ' Switching...');
const user = await firstValueFrom(this.usersStore.getUser(toUser));
if (!user) {
context.writer.writeError(`User ${toUser} not found`);
context.spinner?.hide();
context.process.exit(-1);
return;
}
if (user.id === fromUser.id) {
context.writer.writeError('Already on the user');
context.spinner?.hide();
context.process.exit(-1);
return;
}
await this.userSessionService.setUserSession({
user,
});
context.spinner?.hide();
context.writer.writeSuccess(`Switch to ${context.writer.wrapInColor(toUser, CliForegroundColor.Cyan)} was successfully`);
const reload = command.args['reload'] ||
command.args['r'] ||
context.options?.usersModule?.reloadPageOnUserChange === true;
if (reload) {
context.writer.writeln('Reloading the page in 3 seconds...');
setTimeout(() => {
window.location.reload();
}, 3000);
}
}
catch (e) {
console.error(e);
context.spinner?.hide();
context.writer.writeError('Failed to switch user');
context.process.exit(-1);
return;
}
}
writeDescription(context) {
context.writer.writeln('Switch user command');
context.writer.writeln('Usage: su <user email>');
context.writer.writeln('Example: su user@domain.com');
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliSwitchUserCommandProcessor, deps: [{ token: ICliUserSessionService_TOKEN }, { token: ICliUsersStoreService_TOKEN }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliSwitchUserCommandProcessor, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliSwitchUserCommandProcessor, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}], ctorParameters: function () { return [{ type: undefined, decorators: [{
type: Inject,
args: [ICliUserSessionService_TOKEN]
}] }, { type: undefined, decorators: [{
type: Inject,
args: [ICliUsersStoreService_TOKEN]
}] }]; } });
class CliWhoamiCommandProcessor {
constructor(userSessionService) {
this.userSessionService = userSessionService;
this.command = 'whoami';
this.description = 'Display current user information';
this.author = DefaultLibraryAuthor;
this.metadata = {
sealed: true,
module: 'users',
icon: CliIcon.User,
};
this.stateConfiguration = {
initialState: {},
storeName: 'users',
};
this.parameters = [
{
name: 'info',
description: 'Display user information',
type: 'boolean',
required: false,
aliases: ['i'],
},
];
}
async processCommand(command, context) {
const user = await firstValueFrom(this.userSessionService.getUserSession());
if (!user) {
context.writer.writeln('No user session found');
return;
}
if (command.args['info'] || command.args['i']) {
context.writer.writeln('User information:');
context.writer.writeObjectsAsTable([user.user]);
}
else {
context.writer.writeln(user?.user.email);
}
}
writeDescription(context) {
context.writer.writeln(this.description);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliWhoamiCommandProcessor, deps: [{ token: ICliUserSessionService_TOKEN }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliWhoamiCommandProcessor, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliWhoamiCommandProcessor, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}], ctorParameters: function () { return [{ type: undefined, decorators: [{
type: Inject,
args: [ICliUserSessionService_TOKEN]
}] }]; } });
const usersProviders = [
resolveCommandProcessorProvider(CliSwitchUserCommandProcessor),
resolveCommandProcessorProvider(CliWhoamiCommandProcessor),
resolveCommandProcessorProvider(CliAddUserCommandProcessor),
resolveCommandProcessorProvider(CliListUsersCommandProcessor),
];
class CliLogger {
constructor() {
this.CliLogLevel = CliLogLevel.ERROR;
}
setCliLogLevel(level) {
this.CliLogLevel = level;
}
log(...args) {
if (this.CliLogLevel <= CliLogLevel.LOG) {
console.log(...args);
}
}
info(...args) {
if (this.CliLogLevel <= CliLogLevel.INFO) {
console.info(...args);
}
}
warn(...args) {
if (this.CliLogLevel <= CliLogLevel.WARN) {
console.warn(...args);
}
}
error(...args) {
if (this.CliLogLevel <= CliLogLevel.ERROR) {
console.error(...args);
}
}
debug(...args) {
if (this.CliLogLevel <= CliLogLevel.DEBUG) {
console.debug(...args);
}
}
}
class CliCommandProcessorRegistry {
constructor() {
this.processors = [...miscProcessors];
}
registerProcessor(processor) {
const existingProcessor = this.getProcessorByName(processor.command);
if (existingProcessor) {
if (existingProcessor.metadata?.sealed) {
console.warn(`Processor with command: ${processor.command} is sealed and cannot be replaced.`);
return;
}
const existingIndex = this.processors.findIndex((p) => p.command === processor.command);
// Replace the existing processor
this.processors[existingIndex] = processor;
}
else {
this.processors.push(processor);
}
}
unregisterProcessor(processor) {
const existingProcessor = this.getProcessorByName(processor.command);
if (existingProcessor) {
if (existingProcessor.metadata?.sealed) {
console.warn(`Processor with command: ${processor.command} is sealed and cannot be removed.`);
return;
}
}
const index = this.processors.findIndex((p) => p.command === processor.command);
if (index !== -1) {
this.processors.splice(index, 1);
}
}
findProcessor(mainCommand, chainCommands) {
return this.findProcessorInCollection(mainCommand, chainCommands, this.processors);
}
/**
* Recursively searches for a processor matching the given command.
* @param mainCommand The main command name.
* @param chainCommands The remaining chain commands (if any).
* @param processors The list of available processors.
* @returns The matching processor or undefined if not found.
*/
findProcessorInCollection(mainCommand, chainCommands, processors) {
const processor = processors.find((p) => p.command.toLowerCase() === mainCommand.toLowerCase());
if (!processor) {
return undefined;
}
if (chainCommands.length === 0) {
return processor;
}
if (processor.processors && processor.processors.length > 0) {
return this.findProcessorInCollection(chainCommands[0], chainCommands.slice(1), processor.processors);
}
else if (processor.allowUnlistedCommands || processor.valueRequired) {
return processor;
}
return undefined;
}
getRootProcessor(child) {
return child.parent ? this.getRootProcessor(child.parent) : child;
}
getProcessorByName(name) {
return this.processors.find((p) => p.command.toLowerCase() === name.toLowerCase());
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliCommandProcessorRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliCommandProcessorRegistry }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliCommandProcessorRegistry, decorators: [{
type: Injectable
}], ctorParameters: function () { return []; } });
class CliServiceProvider {
constructor(injector) {
this.providers = [];
this.currentInjector = injector;
}
get(service) {
try {
return this.currentInjector.get(service);
}
catch (e) {
throw new Error(`Service ${service} not found`);
}
}
set(definition) {
const definitions = Array.isArray(definition)
? definition
: [definition];
const newProviders = [];
for (const def of definitions) {
let newProvider = {
provide: def.provide,
};
if (def.hasOwnProperty('useClass')) {
const classProvider = {
provide: def.provide,
useClass: def.useClass,
multi: def.multi,
};
newProvider = classProvider;
}
else if (def.hasOwnProperty('useValue')) {
const valueProvider = {
provide: def.provide,
useValue: def.useValue,
multi: def.multi,
};
newProvider = valueProvider;
}
else if (def.hasOwnProperty('useFactory')) {
const valueProvider = {
provide: def.provide,
useFactory: def.useFactory,
multi: def.multi,
};
newProvider = valueProvider;
}
newProviders.push(newProvider);
}
this.providers.push(...newProviders);
this.currentInjector = Injector.create({
providers: newProviders,
parent: this.currentInjector,
});
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliServiceProvider, deps: [{ token: i0.Injector }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliServiceProvider }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliServiceProvider, decorators: [{
type: Injectable
}], ctorParameters: function () { return [{ type: i0.Injector }]; } });
class CliThemeCommandProcessor {
constructor() {
this.command = 'theme';
this.description = 'Interact with the theme';
this.author = DefaultLibraryAuthor;
this.version = '1.0.1';
this.processors = [];
this.metadata = {
sealed: true,
icon: '🎨',
module: 'system',
};
this.stateConfiguration = {
initialState: {
selectedTheme: 'default',
customOptions: null,
},
};
this.themeOptions = Object.keys(themeOptions);
this.processors = [
{
command: 'list',
description: 'List available themes',
processCommand: async (command, context) => {
context.writer.writeln('Available themes:');
Object.keys(themes).forEach((theme) => {
context.writer.writeln(context.writer.wrapInColor(theme, CliForegroundColor.Cyan));
});
},
},
{
command: 'current',
description: 'Show the current theme',
processCommand: async (_, context) => {
const state = context.state.getState();
const theme = state.selectedTheme || 'custom';
context.writer.writeln(`Current theme: ${theme}`);
context.writer.writeln('Theme options:');
Object.keys(context.terminal.options.theme).forEach((key) => {
context.writer.writeln(`${key}: ${context.terminal.options.theme[key]}`);
});
},
},
{
command: 'apply',
description: 'Apply a theme',
valueRequired: true,
processCommand: async (command, context) => {
const theme = command.value;
if (!themes[theme]) {
context.writer.writeError(`Theme ${theme} not found`);
return;
}
context.terminal.options.theme = themes[theme];
context.state.updateState({
selectedTheme: theme,
customOptions: null,
});
await context.state.persist();
this.applyStyles(context);
context.writer.writeSuccess(`Theme ${theme} applied`);
},
},
{
command: 'set',
allowUnlistedCommands: true,
description: 'Set a theme variable',
parameters: [
{
name: 'save',
description: 'Save the theme settings',
type: 'boolean',
required: false,
},
],
processCommand: async (command, context) => {
const [key, value] = command.command.split(' ').slice(2);
if (!this.themeOptions.includes(key)) {
context.writer.writeError(`Unsupported key: ${key}, supported keys: ${this.themeOptions.join(', ')}`);
return;
}
context.terminal.options.theme = {
...context.terminal.options.theme,
[key]: value,
};
context.state.updateState({
selectedTheme: null,
customOptions: context.terminal.options.theme,
});
context.writer.writeSuccess(`Set ${key} to ${value}`);
this.applyStyles(context);
if (command.args['save']) {
await this.saveTheme(context);
}
},
},
{
command: 'save',
description: 'Save the current theme settings',
processCommand: async (command, context) => {
context.terminal.options.theme = {};
context.state.updateState({
selectedTheme: null,
customOptions: context.terminal.options.theme,
});
await this.saveTheme(context);
},
},
{
command: 'reset',
description: 'Reset the theme to the default',
processCommand: async (command, context) => {
context.terminal.options.theme = { ...this.defaultTheme };
context.state.reset();
await context.state.persist();
context.writer.writeSuccess('Theme reset to default');
},
},
];
}
async initialize(context) {
this.defaultTheme = context.terminal.options.theme;
const state = context.state.getState();
if (state.selectedTheme && state.selectedTheme !== 'default') {
context.terminal.options.theme = themes[state.selectedTheme];
}
else if (state.customOptions) {
context.terminal.options.theme = state.customOptions;
}
this.applyStyles(context);
}
async processCommand(command, context) {
context.executor.showHelp(command, context);
}
applyStyles(context) {
const parents = document.getElementsByClassName('terminal-container');
for (const parent of Array.from(parents)) {
parent.style.background =
context.terminal.options.theme?.background ||
this.defaultTheme.background;
}
}
async saveTheme(context) {
await context.state.persist();
context.writer.writeSuccess('Theme saved');
}
writeDescription(context) {
context.writer.writeln('Interact with the theme');
context.writer.writeln('Examples:');
context.writer.writeln(context.writer.wrapInColor('theme apply dracula', CliForegroundColor.Magenta));
context.writer.writeln(context.writer.wrapInColor('theme reset', CliForegroundColor.Magenta));
context.writer.writeln(context.writer.wrapInColor('theme set background red', CliForegroundColor.Magenta));
context.writer.writeln(context.writer.wrapInColor('theme set foreground red', CliForegroundColor.Magenta));
context.writer.write('Available theme options: ');
context.writer.writeln(context.writer.wrapInColor(this.themeOptions.join(', ') ?? '', CliForegroundColor.Blue));
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliThemeCommandProcessor, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliThemeCommandProcessor, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliThemeCommandProcessor, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}], ctorParameters: function () { return []; } });
const themeOptions = {
background: '',
foreground: '',
black: '',
blue: '',
brightBlack: '',
brightBlue: '',
brightCyan: '',
brightGreen: '',
brightMagenta: '',
brightRed: '',
brightWhite: '',
brightYellow: '',
cyan: '',
green: '',
magenta: '',
red: '',
white: '',
yellow: '',
cursor: '',
cursorAccent: '',
selectionBackground: '',
selectionForeground: '',
selectionInactiveBackground: '',
};
const resolveCliProviders = () => {
return [
ScriptLoaderService,
CliCanViewService,
CliStateStoreManager,
{
useClass: CliUserSessionService,
provide: ICliUserSessionService_TOKEN,
},
{
useClass: CliUsersStoreService,
provide: ICliUsersStoreService_TOKEN,
},
{
useClass: CliDefaultPingServerService,
provide: ICliPingServerService_TOKEN,
},
{
useClass: CliLogger,
provide: CliLogger_TOKEN,
},
{
useClass: CliCommandProcessorRegistry,
provide: CliProcessorsRegistry_TOKEN,
},
{
useClass: CliServiceProvider,
provide: CliServiceProvider_TOKEN,
},
...systemProviders,
...usersProviders,
resolveCommandProcessorProvider(CliThemeCommandProcessor),
resolveCommandProcessorProvider(CliPingCommandProcessor),
];
};
class CliModule {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "16.2.12", ngImport: i0, type: CliModule, declarations: [CliComponent,
CollapsableContentComponent,
CliPanelComponent,
CliTerminalComponent], imports: [CommonModule,
//PrimeNG
ButtonModule,
TabMenuModule,
MenuModule,
SplitButtonModule,
TooltipModule], exports: [CliPanelComponent, CliComponent] }); }
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliModule, providers: [
resolveCliProviders(),
{ provide: ActivatedRoute, useValue: {} },
], imports: [CommonModule,
//PrimeNG
ButtonModule,
TabMenuModule,
MenuModule,
SplitButtonModule,
TooltipModule] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliModule, decorators: [{
type: NgModule,
args: [{
declarations: [
CliComponent,
CollapsableContentComponent,
CliPanelComponent,
CliTerminalComponent,
],
imports: [
CommonModule,
//PrimeNG
ButtonModule,
TabMenuModule,
MenuModule,
SplitButtonModule,
TooltipModule,
],
providers: [
resolveCliProviders(),
{ provide: ActivatedRoute, useValue: {} },
],
exports: [CliPanelComponent, CliComponent],
}]
}] });
/*
* Public API Surface of cli
*/
/**
* Generated bundle index. Do not edit.
*/
export { CliCanViewService, CliCommandExecutionContext, CliCommandExecutorService, CliCommandHistoryService, CliCommandProcessor_TOKEN, CliComponent, CliDefaultPingServerService, CliExecutionContext, CliExecutionProcess, CliLogger_TOKEN, CliModule, CliPanelComponent, CliProcessorsRegistry_TOKEN, CliServiceProvider_TOKEN, CliUserSessionService, CliUsersStoreService, CommandParser, ICliPingServerService_TOKEN, ICliUserSessionService_TOKEN, ICliUsersStoreService_TOKEN, ScriptLoaderService, getGreetingBasedOnTime, openLink, resolveCliProvider, resolveCommandProcessorProvider };
//# sourceMappingURL=qodalis-angular-cli.mjs.map