qraft
Version:
A powerful CLI tool to qraft structured project setups from GitHub template repositories
243 lines ⢠12.7 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.updateCommand = updateCommand;
const chalk_1 = __importDefault(require("chalk"));
const contentComparison_1 = require("../core/contentComparison");
const directoryScanner_1 = require("../core/directoryScanner");
const manifestManager_1 = require("../core/manifestManager");
const repositoryManager_1 = require("../core/repositoryManager");
const confirmationWorkflows_1 = require("../interactive/confirmationWorkflows");
const manifestUtils_1 = require("../utils/manifestUtils");
function createUserPrompt(question) {
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
return new Promise((resolve) => {
rl.question(question, (answer) => {
rl.close();
resolve(answer.trim());
});
});
}
async function showUpdatePreview(preview) {
console.log(chalk_1.default.cyan('\nš Preview: What will be updated\n'));
console.log(chalk_1.default.cyan('Current Box:'));
console.log(chalk_1.default.gray(` š¦ Name: ${preview.boxName}`));
console.log(chalk_1.default.gray(` š Version: ${preview.currentVersion}`));
console.log(chalk_1.default.gray(` š Local Path: ${preview.localPath}`));
console.log(chalk_1.default.cyan('\nProposed Changes:'));
console.log(chalk_1.default.gray(` š New Version: ${preview.suggestedVersion}`));
console.log(chalk_1.default.gray(` š Registry: ${preview.registry}`));
if (preview.hasChanges) {
console.log(chalk_1.default.cyan('\nContent Analysis:'));
if (preview.conflictCount > 0) {
console.log(chalk_1.default.yellow(` ā ļø ${preview.conflictCount} file(s) with conflicts`));
}
if (preview.safeFileCount > 0) {
console.log(chalk_1.default.green(` ā
${preview.safeFileCount} file(s) safe to update`));
}
}
else {
console.log(chalk_1.default.green('\n⨠No content changes detected'));
}
console.log(chalk_1.default.cyan('\nā Confirmation Required'));
const answer = await createUserPrompt('Do you want to proceed with updating this box? (y/N): ');
return answer.toLowerCase() === 'y';
}
async function promptForManifestUpdates(currentManifest) {
console.log(chalk_1.default.cyan('\nš Update Box Information'));
console.log(chalk_1.default.gray('Press Enter to keep current values, or type new values:\n'));
const updates = {};
// Description
const newDescription = await createUserPrompt(`Description (${chalk_1.default.gray(currentManifest.description)}): `);
if (newDescription.trim()) {
updates.description = newDescription.trim();
}
// Author
const currentAuthor = currentManifest.author || 'Not set';
const newAuthor = await createUserPrompt(`Author (${chalk_1.default.gray(currentAuthor)}): `);
if (newAuthor.trim()) {
updates.author = newAuthor.trim();
}
// Tags
const currentTags = currentManifest.tags?.join(', ') || 'None';
const newTags = await createUserPrompt(`Tags (${chalk_1.default.gray(currentTags)}): `);
if (newTags.trim()) {
updates.tags = newTags.split(',').map(tag => tag.trim()).filter(tag => tag.length > 0);
}
// Version (with smart suggestion)
const currentVersion = currentManifest.version;
const suggestedVersion = suggestNextVersion(currentVersion);
const newVersion = await createUserPrompt(`Version (current: ${chalk_1.default.gray(currentVersion)}, suggested: ${chalk_1.default.cyan(suggestedVersion)}): `);
if (newVersion.trim()) {
updates.version = newVersion.trim();
}
else {
updates.version = suggestedVersion;
}
return updates;
}
function suggestNextVersion(currentVersion) {
// Simple semantic versioning increment
const versionParts = currentVersion.split('.');
if (versionParts.length >= 3) {
// Increment patch version
const patch = parseInt(versionParts[2]) || 0;
versionParts[2] = (patch + 1).toString();
return versionParts.join('.');
}
else if (versionParts.length === 2) {
// Increment minor version
const minor = parseInt(versionParts[1]) || 0;
versionParts[1] = (minor + 1).toString();
return versionParts.join('.');
}
else {
// Fallback: append .1
return `${currentVersion}.1`;
}
}
async function updateCommand(boxManager, localPath, options = {}) {
console.log(chalk_1.default.blue.bold('š Updating Box from Local Directory'));
if (options.interactive !== false) {
console.log(chalk_1.default.gray('šÆ Interactive mode enabled - you\'ll be guided through the process\n'));
}
try {
// Step 1: Validate that this is an existing box
console.log(chalk_1.default.cyan('š Checking for existing box...'));
if (!(await manifestUtils_1.ManifestUtils.qraftDirectoryExists(localPath))) {
throw new Error(`No .qraft directory found in ${localPath}. Use 'qraft create' for new boxes.`);
}
if (!(await manifestUtils_1.ManifestUtils.hasCompleteLocalManifest(localPath))) {
throw new Error(`Incomplete manifest found in ${localPath}. Please run 'qraft create' to recreate the box.`);
}
const manifestManager = new manifestManager_1.ManifestManager();
const localManifestEntry = await manifestManager.getLocalManifest(localPath);
if (!localManifestEntry) {
throw new Error(`Could not read local manifest from ${localPath}`);
}
const currentManifest = localManifestEntry.manifest;
console.log(chalk_1.default.green(`ā
Found existing box: ${currentManifest.name} v${currentManifest.version}\n`));
// Step 2: Get registry information
const effectiveRegistry = options.registry ||
localManifestEntry.metadata?.sourceRegistry ||
await boxManager.getConfigManager().getConfig().then(c => c.defaultRegistry);
if (!effectiveRegistry) {
throw new Error('No registry specified. Use --registry option or configure a default registry.');
}
// Step 3: Analyze current content
console.log(chalk_1.default.cyan('š§ Analyzing current content...'));
const directoryScanner = new directoryScanner_1.DirectoryScanner();
const structure = await directoryScanner.scanDirectory(localPath);
console.log(chalk_1.default.green('ā
Content analysis complete\n'));
// Step 4: Interactive manifest updates (if enabled)
let manifestUpdates = {};
if (options.interactive !== false) {
manifestUpdates = await promptForManifestUpdates(currentManifest);
}
else {
// Non-interactive: just increment version
manifestUpdates.version = suggestNextVersion(currentManifest.version);
}
// Step 5: Create updated manifest
const updatedManifest = {
...currentManifest,
...manifestUpdates
};
// Step 6: Content comparison and conflict detection
console.log(chalk_1.default.cyan('š Checking for conflicts...'));
const contentComparison = new contentComparison_1.ContentComparison();
// For update workflow, we compare current structure with itself to detect any changes
// This is a simplified approach - in a full implementation, you'd compare with the last known state
const comparisonResult = await contentComparison.compareDirectories(structure, // old structure (simplified)
structure, // new structure (same for now)
localPath);
const conflictingFiles = contentComparison.getConflictingFiles(comparisonResult);
const safeFiles = contentComparison.getSafeFiles(comparisonResult);
console.log(chalk_1.default.green('ā
Conflict analysis complete\n'));
// Step 7: Show preview and get confirmation
const preview = {
localPath,
boxName: currentManifest.name,
currentVersion: currentManifest.version,
suggestedVersion: updatedManifest.version,
registry: effectiveRegistry,
hasChanges: Object.keys(manifestUpdates).length > 1, // More than just version
conflictCount: conflictingFiles.length,
safeFileCount: safeFiles.length
};
const shouldProceed = await showUpdatePreview(preview);
if (!shouldProceed) {
console.log(chalk_1.default.yellow('ā¹ļø Update cancelled by user'));
return;
}
// Step 8: Handle conflicts if any
if (conflictingFiles.length > 0 && !options.skipConflictResolution) {
console.log(chalk_1.default.cyan('\nā ļø Resolving conflicts...'));
const confirmationWorkflows = new confirmationWorkflows_1.ConfirmationWorkflows();
// Show conflicts to user
const shouldContinue = await confirmationWorkflows.confirmConflictResolution(conflictingFiles.map(file => {
const filePath = typeof file === 'string' ? file : file.path || String(file);
return {
type: 'overwrite',
description: `File will be updated: ${filePath}`,
riskLevel: 'medium',
affectedFiles: [filePath],
recommendations: ['Review changes before proceeding']
};
}));
if (!shouldContinue) {
console.log(chalk_1.default.yellow('ā¹ļø Update cancelled due to conflicts'));
return;
}
}
// Step 9: Update the box
console.log(chalk_1.default.cyan('\nš Updating box...'));
const [registryOwner, registryRepo] = effectiveRegistry.split('/');
const repositoryManager = new repositoryManager_1.RepositoryManager(await boxManager.getGitHubToken(effectiveRegistry));
const updateResult = await repositoryManager.createBox(registryOwner, registryRepo, updatedManifest.name, localPath, updatedManifest, updatedManifest.remotePath, {
commitMessage: `feat: update ${updatedManifest.name} to v${updatedManifest.version}`,
createPR: true
});
if (!updateResult.success) {
throw new Error(`Failed to update box in repository: ${updateResult.message}`);
}
// Step 10: Update local manifest
await manifestManager.storeLocalManifest(localPath, updatedManifest, effectiveRegistry, `${effectiveRegistry}/${updatedManifest.name}`, true // isUpdate = true
);
// Success!
console.log(chalk_1.default.green('\nš Box updated successfully!'));
console.log(chalk_1.default.cyan('\nš Summary:'));
console.log(chalk_1.default.gray(` š¦ Name: ${updatedManifest.name}`));
console.log(chalk_1.default.gray(` š Version: ${currentManifest.version} ā ${updatedManifest.version}`));
console.log(chalk_1.default.gray(` š Description: ${updatedManifest.description}`));
console.log(chalk_1.default.gray(` š Registry: ${effectiveRegistry}`));
if (updateResult.commitSha) {
console.log(chalk_1.default.gray(` š Commit: ${updateResult.commitSha.substring(0, 8)}`));
}
console.log(chalk_1.default.cyan('\nšÆ Next Steps:'));
console.log(chalk_1.default.gray(` ⢠Use: qraft copy ${updatedManifest.name}`));
console.log(chalk_1.default.gray(' ⢠Share the updated box with others'));
if (updateResult.prUrl) {
console.log(chalk_1.default.cyan('\nš Pull Request:'));
console.log(chalk_1.default.gray(` ${updateResult.prUrl}`));
}
}
catch (error) {
console.error(chalk_1.default.red('\nā Error:'), error.message);
if (error.code) {
console.error(chalk_1.default.gray('Code:'), error.code);
}
console.error(chalk_1.default.cyan('\nš” Help:'));
console.error(chalk_1.default.gray(' ⢠Run: qraft update --help'));
console.error(chalk_1.default.gray(' ⢠Check that the directory contains a valid .qraft manifest'));
console.error(chalk_1.default.gray(' ⢠Use: qraft create for new boxes'));
process.exit(1);
}
}
//# sourceMappingURL=update.js.map