UNPKG

qraft

Version:

A powerful CLI tool to qraft structured project setups from GitHub template repositories

326 lines 14.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.RepositoryManager = void 0; const rest_1 = require("@octokit/rest"); const fs = __importStar(require("fs-extra")); const path = __importStar(require("path")); const boxRegistryManager_1 = require("./boxRegistryManager"); const manifestManager_1 = require("./manifestManager"); const permissionChecker_1 = require("./permissionChecker"); const pullRequestCreator_1 = require("./pullRequestCreator"); const repositoryForker_1 = require("./repositoryForker"); class RepositoryManager { constructor(githubToken) { this.githubToken = githubToken; this.permissionChecker = new permissionChecker_1.PermissionChecker(githubToken); this.repositoryForker = new repositoryForker_1.RepositoryForker(githubToken); this.pullRequestCreator = new pullRequestCreator_1.PullRequestCreator(githubToken); this.manifestManager = new manifestManager_1.ManifestManager(); this.boxRegistryManager = new boxRegistryManager_1.BoxRegistryManager(); } /** * Create a new box in a GitHub repository */ async createBox(owner, repo, boxName, localPath, manifest, remotePath, options = {}) { try { if (!this.githubToken) { throw new Error('GitHub token required for creating boxes'); } // Check permissions const permissions = await this.permissionChecker.checkRepositoryPermissions(owner, repo); if (!permissions.permissions.canWrite) { // User doesn't have write access, fork the repository const forkResult = await this.repositoryForker.forkRepository(owner, repo); if (!forkResult.success) { return { success: false, message: `Cannot create box: ${forkResult.message}`, boxPath: '', nextSteps: forkResult.nextSteps }; } // Use the fork for creating the box return this.createBoxInRepository(forkResult.forkOwner, forkResult.forkName, boxName, localPath, manifest, { ...options, createPR: true }, remotePath); } // User has write access, create directly return this.createBoxInRepository(owner, repo, boxName, localPath, manifest, options, remotePath); } catch (error) { return { success: false, message: `Failed to create box: ${error instanceof Error ? error.message : 'Unknown error'}`, boxPath: '', nextSteps: [ 'Check your GitHub token permissions', 'Verify the repository exists and you have access', 'Try again or contact the repository owner' ] }; } } /** * Create box in a specific repository (with write access) */ async createBoxInRepository(owner, repo, boxName, localPath, manifest, options = {}, remotePath) { const octokit = new rest_1.Octokit({ auth: this.githubToken, userAgent: 'qraft-cli' }); // Use remotePath if provided, otherwise fall back to boxName const boxPath = remotePath || boxName; // Update manifest with remotePath if provided if (remotePath) { manifest.remotePath = remotePath; } try { // Get the default branch if no branch specified let branch = options.branch; if (!branch) { const { data: repoData } = await octokit.rest.repos.get({ owner, repo }); branch = repoData.default_branch; } // Get the current commit SHA of the branch const { data: refData } = await octokit.rest.git.getRef({ owner, repo, ref: `heads/${branch}` }); const baseSha = refData.object.sha; // Get the base tree const { data: baseCommit } = await octokit.rest.git.getCommit({ owner, repo, commit_sha: baseSha }); // Collect all files to upload const filesToUpload = await this.collectFiles(localPath, boxPath); // Add manifest.json filesToUpload.push({ path: `${boxPath}/manifest.json`, content: JSON.stringify(manifest, null, 2), encoding: 'utf-8' }); // Create tree with all files const tree = filesToUpload.map(file => ({ path: file.path, mode: '100644', type: 'blob', content: typeof file.content === 'string' ? file.content : file.content.toString('base64') })); const { data: newTree } = await octokit.rest.git.createTree({ owner, repo, tree, base_tree: baseCommit.tree.sha }); // Create commit const commitMessage = options.commitMessage || `feat: add ${boxName} box`; const { data: newCommit } = await octokit.rest.git.createCommit({ owner, repo, message: commitMessage, tree: newTree.sha, parents: [baseSha] }); // Update the branch reference await octokit.rest.git.updateRef({ owner, repo, ref: `heads/${branch}`, sha: newCommit.sha }); // Store local manifest copy after successful creation try { await this.storeLocalManifestCopy(localPath, manifest, `${owner}/${repo}`, boxName); } catch (manifestError) { // Log manifest storage error but don't fail the entire operation console.warn(`Warning: Failed to store local manifest copy: ${manifestError instanceof Error ? manifestError.message : 'Unknown error'}`); } // Update box registry with name → remote path mapping try { // For now, we'll use a local registry path. In a real implementation, // this would be the cloned registry directory const registryPath = process.cwd(); // Temporary - should be actual registry path await this.boxRegistryManager.registerBox(registryPath, `${owner}/${repo}`, boxName, boxPath, manifest); } catch (registryError) { // Log registry update error but don't fail the entire operation console.warn(`Warning: Failed to update box registry: ${registryError instanceof Error ? registryError.message : 'Unknown error'}`); } const result = { success: true, message: `Box '${boxName}' created successfully`, boxPath, commitSha: newCommit.sha, nextSteps: [ `Box '${boxName}' is now available in the registry`, `You can download it using: qraft copy ${boxName}`, 'Share the box with others by sharing the registry' ] }; // Create PR if requested (for forks) if (options.createPR) { const boxMetadata = { name: manifest.name, description: manifest.description, tags: manifest.tags || [], fileCount: filesToUpload.length - 1, // Exclude manifest.json size: this.calculateTotalSize(filesToUpload) }; const prOptions = { headBranch: branch }; if (options.prTitle) { prOptions.title = options.prTitle; } if (options.prDescription) { prOptions.description = options.prDescription; } const prResult = await this.pullRequestCreator.createPullRequest(owner, repo, boxMetadata, prOptions); if (prResult.success) { result.prUrl = prResult.prUrl; result.prNumber = prResult.prNumber; result.nextSteps = [ `Pull request created: ${prResult.prUrl}`, 'Wait for review and approval', 'Box will be available after PR is merged' ]; } } return result; } catch (error) { return { success: false, message: `Failed to create box in repository: ${error instanceof Error ? error.message : 'Unknown error'}`, boxPath, nextSteps: [ 'Check your GitHub token permissions', 'Verify the repository exists and you have write access', 'Ensure the branch exists', 'Try again or contact the repository owner' ] }; } } /** * Collect all files from local directory */ async collectFiles(localPath, boxPath) { const files = []; const scanDirectory = async (dirPath, relativePath = '') => { const entries = await fs.readdir(dirPath, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dirPath, entry.name); const relativeFilePath = path.join(relativePath, entry.name).replace(/\\/g, '/'); if (entry.isDirectory()) { await scanDirectory(fullPath, relativeFilePath); } else if (entry.isFile()) { const content = await fs.readFile(fullPath); const isText = this.isTextFile(fullPath); files.push({ path: `${boxPath}/${relativeFilePath}`, content: isText ? content.toString('utf-8') : content, encoding: isText ? 'utf-8' : 'base64' }); } } }; await scanDirectory(localPath); return files; } /** * Check if a file is a text file */ isTextFile(filePath) { const textExtensions = [ '.txt', '.md', '.json', '.js', '.ts', '.jsx', '.tsx', '.css', '.scss', '.sass', '.html', '.htm', '.xml', '.yaml', '.yml', '.toml', '.ini', '.cfg', '.conf', '.py', '.rb', '.php', '.java', '.c', '.cpp', '.h', '.hpp', '.cs', '.go', '.rs', '.sh', '.bash', '.zsh', '.fish', '.ps1', '.bat', '.cmd', '.dockerfile', '.gitignore', '.gitattributes', '.editorconfig', '.eslintrc', '.prettierrc' ]; const ext = path.extname(filePath).toLowerCase(); return textExtensions.includes(ext) || path.basename(filePath).startsWith('.'); } /** * Calculate total size of files */ calculateTotalSize(files) { const totalBytes = files.reduce((sum, file) => { const size = typeof file.content === 'string' ? Buffer.byteLength(file.content, 'utf-8') : file.content.length; return sum + size; }, 0); if (totalBytes < 1024) return `${totalBytes}B`; if (totalBytes < 1024 * 1024) return `${(totalBytes / 1024).toFixed(1)}KB`; return `${(totalBytes / (1024 * 1024)).toFixed(1)}MB`; } /** * Store local manifest copy after box creation * @param localPath Local path where the box was created from * @param manifest Box manifest * @param registry Registry identifier (owner/repo) * @param boxName Box name * @returns Promise<void> */ async storeLocalManifestCopy(localPath, manifest, registry, boxName) { try { // Store the manifest with source information await this.manifestManager.storeLocalManifest(localPath, manifest, registry, `${registry}/${boxName}`); } catch (error) { throw new Error(`Failed to store local manifest copy: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Get manifest manager instance * @returns ManifestManager Manifest manager instance */ getManifestManager() { return this.manifestManager; } } exports.RepositoryManager = RepositoryManager; //# sourceMappingURL=repositoryManager.js.map