qraft
Version:
A powerful CLI tool to qraft structured project setups from GitHub template repositories
286 lines ⢠12 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.InteractiveBoxSelector = void 0;
const chalk_1 = __importDefault(require("chalk"));
const inquirer_1 = __importDefault(require("inquirer"));
const prompts_1 = require("./prompts");
/**
* Interactive box selector with search, filtering, and preview capabilities
*/
class InteractiveBoxSelector {
constructor(boxManager) {
this.boxManager = boxManager;
this.prompts = new prompts_1.InteractivePrompts();
}
/**
* Interactive box selection with full features
* @param registryName Optional specific registry to search
* @returns Promise<{box: BoxInfo, registry: string} | null> Selected box and registry or null if cancelled
*/
async selectBox(registryName) {
try {
// Step 1: Select registry if not specified
let selectedRegistry = registryName;
if (!selectedRegistry) {
const registries = await this.boxManager.listRegistries();
if (registries.length === 0) {
console.log(chalk_1.default.red('ā No registries configured.'));
console.log(chalk_1.default.gray('Add a registry first:'));
console.log(chalk_1.default.cyan(' qraft config add-registry <name> <repository>'));
return null;
}
if (registries.length === 1) {
selectedRegistry = registries[0].name;
console.log(chalk_1.default.gray(`Using registry: ${chalk_1.default.cyan(selectedRegistry)}`));
}
else {
const registrySelection = await this.prompts.selectRegistry(registries);
if (!registrySelection) {
return null; // User cancelled
}
selectedRegistry = registrySelection;
}
}
// Step 2: Load boxes from selected registry
console.log(chalk_1.default.blue(`\nš¦ Loading boxes from ${chalk_1.default.cyan(selectedRegistry)}...`));
let boxes;
try {
const boxList = await this.boxManager.listBoxes(selectedRegistry);
boxes = await Promise.all(boxList.map(async (boxSummary) => {
const boxRef = await this.boxManager.parseBoxReference(boxSummary.name, selectedRegistry);
const boxInfo = await this.boxManager.getBoxInfo(boxRef);
return boxInfo;
}));
}
catch (error) {
console.error(chalk_1.default.red('ā Failed to load boxes:'), error instanceof Error ? error.message : 'Unknown error');
return null;
}
if (boxes.length === 0) {
console.log(chalk_1.default.yellow('No boxes found in this registry.'));
return null;
}
// Step 3: Interactive selection with search and preview
return await this.interactiveSelection(boxes, selectedRegistry);
}
catch (error) {
console.error(chalk_1.default.red('ā Error during box selection:'), error instanceof Error ? error.message : 'Unknown error');
return null;
}
}
/**
* Interactive selection with search, filter, and preview
* @param boxes Available boxes
* @param registryName Registry name
* @returns Promise<{box: BoxInfo, registry: string} | null> Selected box or null
*/
async interactiveSelection(boxes, registryName) {
let filteredBoxes = boxes;
let searchQuery = '';
while (true) {
// Show current search status
if (searchQuery) {
console.log(chalk_1.default.gray(`\nFiltered by: "${searchQuery}" (${filteredBoxes.length}/${boxes.length} boxes)`));
}
else {
console.log(chalk_1.default.gray(`\nShowing all boxes (${boxes.length} total)`));
}
// Create menu choices
const choices = [];
// Search option
choices.push({
name: searchQuery
? `${chalk_1.default.blue('š Search again')} ${chalk_1.default.gray(`(current: "${searchQuery}")`)}`
: chalk_1.default.blue('š Search boxes'),
value: 'search',
short: 'Search'
});
// Clear search if active
if (searchQuery) {
choices.push({
name: chalk_1.default.yellow('šļø Clear search'),
value: 'clear-search',
short: 'Clear search'
});
}
// Separator
if (filteredBoxes.length > 0) {
choices.push(new inquirer_1.default.Separator(chalk_1.default.gray('ā Available Boxes ā')));
// Box choices
filteredBoxes.forEach(box => {
const tags = box.manifest.tags ? ` ${chalk_1.default.gray(`[${box.manifest.tags.join(', ')}]`)}` : '';
choices.push({
name: `${chalk_1.default.cyan(box.manifest.name)} ${chalk_1.default.gray(`(${box.manifest.version})`)}${tags}\n ${chalk_1.default.gray(box.manifest.description)}`,
value: { type: 'box', box },
short: box.manifest.name
});
});
}
else {
choices.push({
name: chalk_1.default.gray('No boxes match your search'),
value: 'no-results',
disabled: true
});
}
// Separator and actions
choices.push(new inquirer_1.default.Separator());
choices.push({
name: chalk_1.default.gray('Cancel'),
value: 'cancel',
short: 'Cancel'
});
// Show selection prompt
const { selection } = await inquirer_1.default.prompt([
{
type: 'list',
name: 'selection',
message: `Select a box from ${chalk_1.default.cyan(registryName)}:`,
choices,
pageSize: 15
}
]);
// Handle selection
if (selection === 'cancel') {
return null;
}
if (selection === 'search') {
searchQuery = await this.prompts.promptSearch('Enter search terms (name, description, tags):');
filteredBoxes = this.filterBoxes(boxes, searchQuery);
continue;
}
if (selection === 'clear-search') {
searchQuery = '';
filteredBoxes = boxes;
continue;
}
if (selection === 'no-results') {
continue;
}
if (selection.type === 'box') {
// Show preview and confirm
const confirmed = await this.previewAndConfirm(selection.box);
if (confirmed) {
return { box: selection.box, registry: registryName };
}
// If not confirmed, continue the loop to show selection again
continue;
}
}
}
/**
* Filter boxes based on search query
* @param boxes All available boxes
* @param query Search query
* @returns Filtered boxes
*/
filterBoxes(boxes, query) {
if (!query.trim()) {
return boxes;
}
const searchTerms = query.toLowerCase().split(' ').filter(term => term.length > 0);
return boxes.filter(box => {
const searchableText = [
box.manifest.name,
box.manifest.description,
box.manifest.author || '',
...(box.manifest.tags || [])
].join(' ').toLowerCase();
return searchTerms.every(term => searchableText.includes(term));
});
}
/**
* Show box preview and get confirmation
* @param box Box to preview
* @returns Promise<boolean> Whether user confirmed selection
*/
async previewAndConfirm(box) {
// Show preview
await this.prompts.previewBox(box);
// Get confirmation
const choices = [
{
name: chalk_1.default.green('ā
Select this box'),
value: true,
short: 'Select'
},
{
name: chalk_1.default.yellow('š Show file list'),
value: 'show-files',
short: 'Show files'
},
{
name: chalk_1.default.gray('ā Back to selection'),
value: false,
short: 'Back'
}
];
const { action } = await inquirer_1.default.prompt([
{
type: 'list',
name: 'action',
message: 'What would you like to do?',
choices
}
]);
if (action === 'show-files') {
// Show detailed file list
console.log(chalk_1.default.blue.bold(`\nš Files in ${box.manifest.name}:\n`));
box.files.forEach((file, index) => {
console.log(` ${chalk_1.default.gray(`${(index + 1).toString().padStart(3)}.`)} ${file}`);
});
console.log(chalk_1.default.gray(`\nTotal: ${box.files.length} files\n`));
// Ask again after showing files
return this.prompts.confirm('Select this box?', true);
}
return action;
}
/**
* Quick box selection without full interactive features
* @param registryName Optional registry name
* @returns Promise<{box: BoxInfo, registry: string} | null> Selected box or null
*/
async quickSelect(registryName) {
try {
// Get registry
let selectedRegistry = registryName;
if (!selectedRegistry) {
const registries = await this.boxManager.listRegistries();
if (registries.length === 1) {
selectedRegistry = registries[0].name;
}
else {
const registrySelection = await this.prompts.selectRegistry(registries);
if (!registrySelection)
return null;
selectedRegistry = registrySelection;
}
}
// Get boxes
const boxList = await this.boxManager.listBoxes(selectedRegistry);
const boxes = await Promise.all(boxList.map(async (boxSummary) => {
const boxRef = await this.boxManager.parseBoxReference(boxSummary.name, selectedRegistry);
return await this.boxManager.getBoxInfo(boxRef);
}));
const validBoxes = boxes.filter(box => box !== null);
if (validBoxes.length === 0) {
console.log(chalk_1.default.yellow('No boxes available.'));
return null;
}
// Simple selection
const selectedBox = await this.prompts.selectBox(validBoxes, selectedRegistry);
if (!selectedBox)
return null;
return { box: selectedBox, registry: selectedRegistry };
}
catch (error) {
console.error(chalk_1.default.red('ā Error during box selection:'), error instanceof Error ? error.message : 'Unknown error');
return null;
}
}
}
exports.InteractiveBoxSelector = InteractiveBoxSelector;
//# sourceMappingURL=boxSelector.js.map