UNPKG

codereg

Version:

A CLI tool for copying and managing source code directly from GitHub repositories, perfect for modern React UI libraries and custom code management.

486 lines (473 loc) 14.7 kB
#!/usr/bin/env node "use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); // src/prompts/confirm-or-quit.ts async function confirmOrQuit(message) { const proceed = await confirm(message); if (!proceed) { process.exit(0); } } // src/prompts/file.ts var import_prompts = __toESM(require("prompts")); async function promptSelectFiles(files) { const response = await (0, import_prompts.default)({ type: "multiselect", name: "files", message: "Select files to fetch:", choices: files, hint: "- Space to select. Return to submit", suggest: async (input, choices) => { const searchTerm = input.toLowerCase(); return choices.filter( (choice) => choice.title.toLowerCase().startsWith(searchTerm) ); } }); return response.files; } // src/prompts/registry.ts var import_prompts2 = __toESM(require("prompts")); async function promptSelectRegistry(registries) { const registryResponse = await (0, import_prompts2.default)({ type: "select", name: "registry", message: "Select a registry:", choices: registries.map((r) => ({ title: r.name, value: r.name, description: r.url })) }); return registryResponse.registry; } // src/schema/index.ts var import_zod = require("zod"); var sourceSchema = import_zod.z.object({ name: import_zod.z.string(), url: import_zod.z.string() }); var registrySchema = import_zod.z.object({ name: import_zod.z.string().describe("The unique name of the registry (e.g., 'ui')"), url: import_zod.z.string().url().describe("The URL of the GitHub repository"), dirname: import_zod.z.string().describe( "Destination directory path in the local project where files from this registry will be copied" ), path: import_zod.z.string().describe( "Path from repository root to the directory containing source code" ).optional(), branch: import_zod.z.string().describe( "The branch name to use when fetching files from the repository (default: main)" ).optional() }); var configSchema = import_zod.z.object({ $schema: import_zod.z.string().url().optional(), registry: import_zod.z.array(registrySchema).describe( "List of registry entries containing repository and source definitions" ) }).strict(); // src/utils/file-icons.ts var COLORS = { reset: "\x1B[0m", yellow: "\x1B[33m", // JavaScript blue: "\x1B[34m", // TypeScript cyan: "\x1B[36m", // React magenta: "\x1B[35m", // CSS/Styling green: "\x1B[32m", // Data/Config red: "\x1B[31m", // HTML white: "\x1B[37m", // Documentation brightGreen: "\x1B[92m", // Environment brightBlue: "\x1B[94m", // Scripts brightYellow: "\x1B[93m", // Media brightMagenta: "\x1B[95m", // Images brightCyan: "\x1B[96m", // Database gray: "\x1B[90m" // Logs/Misc }; var FILE_EXTENSION_COLORS = { // JavaScript/TypeScript js: COLORS.yellow, ts: COLORS.blue, jsx: COLORS.cyan, tsx: COLORS.cyan, // Web technologies html: COLORS.red, css: COLORS.magenta, scss: COLORS.magenta, sass: COLORS.magenta, // Data & Config json: COLORS.green, yml: COLORS.green, yaml: COLORS.green, xml: COLORS.green, // Documentation md: COLORS.white, // Environment & Scripts env: COLORS.brightGreen, sh: COLORS.brightBlue, // Programming languages py: COLORS.brightGreen, java: COLORS.red, c: COLORS.blue, h: COLORS.blue, cpp: COLORS.brightBlue, rs: COLORS.red, go: COLORS.brightCyan, php: COLORS.magenta, // Database sql: COLORS.brightCyan, // Images svg: COLORS.brightMagenta, png: COLORS.brightMagenta, jpg: COLORS.brightMagenta, jpeg: COLORS.brightMagenta, gif: COLORS.brightMagenta, webp: COLORS.brightMagenta, // Media mp3: COLORS.brightYellow, wav: COLORS.brightYellow, mp4: COLORS.brightYellow, webm: COLORS.brightYellow, // Special files lock: COLORS.gray, log: COLORS.gray }; var FILE_EXTENSION_ICONS = { // JavaScript/TypeScript js: "\u{1F7E8}", ts: "\u{1F535}", jsx: "\u269B\uFE0F", tsx: "\u269B\uFE0F", // Web technologies html: "\u{1F310}", css: "\u{1F3A8}", scss: "\u{1F485}", sass: "\u{1F485}", // Data & Config json: "\u{1F4E6}", yml: "\u2699\uFE0F", yaml: "\u2699\uFE0F", xml: "\u{1F4F0}", // Documentation md: "\u{1F4DD}", // Environment & Scripts env: "\u{1F331}", sh: "\u{1F41A}", // Programming languages py: "\u{1F40D}", java: "\u2615", c: "\u{1F527}", h: "\u{1F527}", cpp: "\u{1F4A0}", rs: "\u{1F980}", go: "\u{1F300}", php: "\u{1F418}", // Database sql: "\u{1F5C3}\uFE0F", // Images svg: "\u{1F5BC}\uFE0F", png: "\u{1F5BC}\uFE0F", jpg: "\u{1F5BC}\uFE0F", jpeg: "\u{1F5BC}\uFE0F", gif: "\u{1F5BC}\uFE0F", webp: "\u{1F5BC}\uFE0F", // Media mp3: "\u{1F3B5}", wav: "\u{1F3B5}", mp4: "\u{1F39E}\uFE0F", webm: "\u{1F39E}\uFE0F", // Special files lock: "\u{1F512}", log: "\u{1F4C4}" }; var SPECIAL_FILES = { Dockerfile: { emoji: "\u{1F433}", color: COLORS.blue }, ".gitignore": { emoji: "\u{1F648}", color: COLORS.gray }, ".dockerignore": { emoji: "\u{1F648}", color: COLORS.gray } }; var SPECIAL_PATTERNS = [ { pattern: /\.test\.(ts|js|tsx|jsx)$/, emoji: "\u{1F9EA}", color: COLORS.brightGreen }, { pattern: /\.config\.(js|ts|json)$/, emoji: "\u2699\uFE0F", color: COLORS.green }, { pattern: /\.spec\.(ts|js|tsx|jsx)$/, emoji: "\u{1F9EA}", color: COLORS.brightGreen }, { pattern: /package\.json$/, emoji: "\u{1F4E6}", color: COLORS.green }, { pattern: /package-lock\.json$/, emoji: "\u{1F512}", color: COLORS.gray }, { pattern: /yarn\.lock$/, emoji: "\u{1F512}", color: COLORS.gray }, { pattern: /pnpm-lock\.yaml$/, emoji: "\u{1F512}", color: COLORS.gray } ]; var DEFAULT_ICON = "\u{1F4C4}"; var DEFAULT_COLOR = COLORS.white; function getFileIcon(fileName) { if (SPECIAL_FILES[fileName]) { return SPECIAL_FILES[fileName].emoji; } for (const { pattern, emoji } of SPECIAL_PATTERNS) { if (pattern.test(fileName)) { return emoji; } } const extension = fileName.split(".").pop()?.toLowerCase(); if (!extension) { return DEFAULT_ICON; } return FILE_EXTENSION_ICONS[extension] || DEFAULT_ICON; } function getFileColor(fileName) { if (SPECIAL_FILES[fileName]) { return SPECIAL_FILES[fileName].color; } for (const { pattern, color } of SPECIAL_PATTERNS) { if (pattern.test(fileName)) { return color; } } const extension = fileName.split(".").pop()?.toLowerCase(); if (!extension) { return DEFAULT_COLOR; } return FILE_EXTENSION_COLORS[extension] || DEFAULT_COLOR; } function getFileDisplayName(fileName) { const icon = getFileIcon(fileName); const color = getFileColor(fileName); return `${icon} ${color}${fileName}${COLORS.reset}`; } // src/services/registry.ts function getRawFileUrl(repoUrl, branch, filePath) { const repoPath = repoUrl.replace("https://github.com/", "").replace(/\.git$/, ""); return `https://raw.githubusercontent.com/${repoPath}/${branch}/${filePath}`; } async function getFilesList(repoUrl, branch, path2) { const repoPath = repoUrl.replace("https://github.com/", "").replace(/\.git$/, ""); const apiUrl = `https://api.github.com/repos/${repoPath}/contents/${path2}?ref=${branch}`; const res = await fetch(apiUrl); if (!res.ok) { throw new Error( `Failed to fetch files list: ${res.status} ${res.statusText}` ); } const data = await res.json(); return data.filter((item) => item.type === "file").map((item) => ({ title: getFileDisplayName(item.name), value: item.name, description: item.path })); } // src/utils/logger.ts var import_chalk = __toESM(require("chalk")); var logger = { error(...args) { console.error(import_chalk.default.red(...args)); }, warn(...args) { console.warn(import_chalk.default.yellow(...args)); }, info(...args) { console.info(import_chalk.default.cyan(...args)); }, success(...args) { console.info(import_chalk.default.green(...args)); } }; // src/commands/add.ts var import_commander = require("commander"); var import_promises = require("fs/promises"); var import_path = __toESM(require("path")); var CONFIG_FILE = ".codereg.config.json"; var add = new import_commander.Command().name("add").description("add a file from a registry repository to the project").option("-r, --registry <name>", "Registry name defined in config").option("-f, --file <name>", "File name to fetch").option("-b, --branch <branch>", "Branch name (default: main)", "main").option( "-p, --path <path>", "Path from repository root to the code directory" ).action(async (options) => { const rawConfig = await (0, import_promises.readFile)(CONFIG_FILE, "utf-8"); const config = configSchema.parse(JSON.parse(rawConfig)); let registryName = options.registry; if (!registryName) { registryName = await promptSelectRegistry(config.registry); } const registry = config.registry.find((r) => r.name === registryName); if (!registry) { logger.error(`Registry '${registryName}' not found in ${CONFIG_FILE}`); process.exit(1); } const branch = options.branch || registry.branch || "main"; const repoPath = options.path || registry.path || ""; let selectedFiles = []; if (!options.file) { try { const files = await getFilesList(registry.url, branch, repoPath); selectedFiles = await promptSelectFiles(files); } catch (error) { logger.error(`Failed to fetch files list: ${error}`); process.exit(1); } } else { selectedFiles = [options.file]; } for (const fileName of selectedFiles) { const filePathInRepo = repoPath ? import_path.default.posix.join(repoPath, fileName) : fileName; const rawUrl = getRawFileUrl(registry.url, branch, filePathInRepo); logger.info(`Downloading: ${rawUrl}`); const res = await fetch(rawUrl); if (!res.ok) { logger.error( `Failed to fetch file ${fileName}: ${res.status} ${res.statusText}` ); continue; } const fileContent = await res.text(); const localFilePath = import_path.default.resolve(registry.dirname, fileName); try { await (0, import_promises.readFile)(localFilePath, "utf-8"); await confirmOrQuit(`File ${localFilePath} exists. Overwrite?`); } catch (e) { } await (0, import_promises.mkdir)(import_path.default.dirname(localFilePath), { recursive: true }); await (0, import_promises.writeFile)(localFilePath, fileContent, "utf-8"); logger.success(`File saved to ${localFilePath}`); } }); // src/commands/init.ts var import_commander2 = require("commander"); var import_promises2 = require("fs/promises"); var init = new import_commander2.Command().name("init").description("initialize project with codereg").action(async () => { const minimalConfig = { $schema: "https://cdn.jsdelivr.net/npm/codereg/dist/config.schema.json", registry: [] }; try { await (0, import_promises2.readFile)(".codereg.config.json", "utf-8"); await confirmOrQuit(".codereg.config.json exists. Overwrite?"); } catch (e) { } await (0, import_promises2.writeFile)( ".codereg.config.json", JSON.stringify(minimalConfig, null, 2), "utf-8" ); logger.success("Project initialization completed."); }); // src/index.ts var import_commander3 = require("commander"); // package.json var package_default = { name: "codereg", version: "1.4.0", author: "onepercman", description: "A CLI tool for copying and managing source code directly from GitHub repositories, perfect for modern React UI libraries and custom code management.", license: "MIT", repository: { type: "git", url: "https://github.com/onepercman/codereg" }, bugs: { url: "https://github.com/onepercman/codereg/issues" }, homepage: "https://github.com/onepercman/codereg", keywords: [ "codereg", "github", "source-code", "react", "ui-libraries", "cli", "code-management", "development-tools" ], bin: { codereg: "./dist/index.js" }, files: [ "dist" ], main: "./dist/index.js", types: "./dist/index.d.ts", scripts: { build: "tsup", dev: "tsup --watch", start: "node dist/index.js", test: "jest", prepublishOnly: "npm run build" }, dependencies: { chalk: "^5.4.1", commander: "^14.0.0", "compare-versions": "^6.1.1", cosmiconfig: "^9.0.0", execa: "^9.6.0", jsonata: "^2.0.6", "lru-cache": "^11.1.0", ora: "^8.2.0", prompts: "^2.4.2", rimraf: "^6.0.1", zod: "^3.25.67" }, devDependencies: { "@types/node": "^24.0.3", "@types/prompts": "^2.4.9", prettier: "^3.4.2", "prettier-plugin-organize-imports": "^4.1.0", tsup: "^8.3.0", typescript: "^5.6.3" } }; // src/index.ts var program = new import_commander3.Command().name("codereg").version(package_default.version, "-v, --version", "display the version number").description("Code Registry CLI"); program.addCommand(init); program.addCommand(add); program.addHelpText( "after", ` Examples: $ codereg add --registry my-registry --file example.ts $ codereg add -r my-registry -f example.ts -b develop $ codereg add -r my-registry -p src/components $ codereg add --help Options for add command: -r, --registry <name> Registry name defined in config -f, --file <name> File name to fetch -b, --branch <branch> Branch name (default: main) -p, --path <path> Path from repository root to the code directory ` ); program.parse(); //# sourceMappingURL=index.js.map