UNPKG

@feedinbox/cli

Version:

CLI tool for installing FeedInbox components into your project

434 lines (412 loc) 17.9 kB
#!/usr/bin/env node #!/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/cli.ts var import_commander = require("commander"); var import_chalk6 = __toESM(require("chalk")); var import_inquirer3 = __toESM(require("inquirer")); // src/commands/add.ts var import_path3 = __toESM(require("path")); var import_fs_extra3 = __toESM(require("fs-extra")); var import_chalk2 = __toESM(require("chalk")); var import_inquirer = __toESM(require("inquirer")); // src/utils/registry.ts async function getTemplateRegistry() { return { "react-vanilla": { name: "React + Vanilla CSS", description: "Clean React components with vanilla CSS styling", dependencies: { "@feedinbox/sdk": "^1.0.0" }, devDependencies: { "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0" }, files: [ "FeedbackWidget.tsx", "NewsletterWidget.tsx", "ContactWidget.tsx", "index.ts", "styles/index.css", "styles/feedback-widget.css", "styles/newsletter-widget.css", "styles/contact-widget.css" ] }, "react-tailwind": { name: "React + Tailwind CSS", description: "React components styled with Tailwind CSS utilities", dependencies: { "@feedinbox/sdk": "^1.0.0" }, devDependencies: { "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", "tailwindcss": "^3.4.0" }, files: [ "FeedbackWidget.tsx", "NewsletterWidget.tsx", "ContactWidget.tsx", "index.ts" ] }, "react-shadcn": { name: "React + shadcn/ui", description: "React components using shadcn/ui component library", dependencies: { "@feedinbox/sdk": "^1.0.0" }, devDependencies: { "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0" }, files: [ "FeedbackWidget.tsx", "NewsletterWidget.tsx", "ContactWidget.tsx", "index.ts", "ui/button.tsx", "ui/input.tsx", "ui/textarea.tsx", "ui/dialog.tsx", "ui/checkbox.tsx", "ui/radio-group.tsx" ] } }; } // src/utils/template.ts var import_path = __toESM(require("path")); var import_fs_extra = __toESM(require("fs-extra")); var import_url = require("url"); var import_meta = {}; var __filename = (0, import_url.fileURLToPath)(import_meta.url); var __dirname = import_path.default.dirname(__filename); async function copyTemplate(templateType, targetDir) { const templateDir = import_path.default.join(__dirname, "../../templates", templateType); if (!await import_fs_extra.default.pathExists(templateDir)) { throw new Error(`Template directory not found: ${templateType}`); } await import_fs_extra.default.copy(templateDir, targetDir, { overwrite: true, filter: (src) => { return !src.includes(".git") && !src.includes("node_modules"); } }); console.log(`\u{1F4C1} Template files copied from ${templateType}`); } // src/utils/package.ts var import_path2 = __toESM(require("path")); var import_fs_extra2 = __toESM(require("fs-extra")); var import_chalk = __toESM(require("chalk")); async function updatePackageJson(dependencies, devDependencies) { const packageJsonPath = import_path2.default.join(process.cwd(), "package.json"); if (!await import_fs_extra2.default.pathExists(packageJsonPath)) { console.log(import_chalk.default.yellow("\u26A0\uFE0F No package.json found. You may need to run npm init first.")); return; } const packageJson = await import_fs_extra2.default.readJson(packageJsonPath); if (Object.keys(dependencies).length > 0) { packageJson.dependencies = packageJson.dependencies || {}; Object.assign(packageJson.dependencies, dependencies); console.log(import_chalk.default.blue("\u{1F4E6} Added dependencies:"), Object.keys(dependencies).join(", ")); } if (Object.keys(devDependencies).length > 0) { packageJson.devDependencies = packageJson.devDependencies || {}; Object.assign(packageJson.devDependencies, devDependencies); console.log(import_chalk.default.blue("\u{1F527} Added dev dependencies:"), Object.keys(devDependencies).join(", ")); } await import_fs_extra2.default.writeJson(packageJsonPath, packageJson, { spaces: 2 }); console.log(import_chalk.default.green("\u2705 package.json updated")); console.log(import_chalk.default.yellow("\u{1F4A1} Run npm install to install new dependencies")); } // src/commands/add.ts async function addComponents(type, options) { const validTypes = ["react-vanilla", "react-tailwind", "react-shadcn"]; if (!validTypes.includes(type)) { throw new Error(`Invalid component type: ${type}. Valid types: ${validTypes.join(", ")}`); } const targetDir = import_path3.default.resolve(process.cwd(), options.dir); if (await import_fs_extra3.default.pathExists(targetDir)) { const files = await import_fs_extra3.default.readdir(targetDir); if (files.length > 0 && !options.force) { if (!options.yes) { const { confirm } = await import_inquirer.default.prompt([ { type: "confirm", name: "confirm", message: `Directory ${options.dir} already exists and contains files. Continue?`, default: false } ]); if (!confirm) { console.log(import_chalk2.default.yellow("Installation cancelled.")); return; } } } } console.log(import_chalk2.default.blue(`\u{1F4E6} Installing ${type} components...`)); const registry = await getTemplateRegistry(); const template = registry[type]; if (!template) { throw new Error(`Template not found for type: ${type}`); } await import_fs_extra3.default.ensureDir(targetDir); console.log(import_chalk2.default.blue("\u{1F4C4} Copying component files...")); await copyTemplate(type, targetDir); console.log(import_chalk2.default.blue("\u{1F4CB} Updating dependencies...")); await updatePackageJson(template.dependencies, template.devDependencies); await createComponentReadme(type, targetDir); console.log(import_chalk2.default.green(`\u2705 ${type} components installed to ${options.dir}`)); showNextSteps(type); } async function createComponentReadme(type, targetDir) { const readmeContent = `# FeedInbox Components This directory contains FeedInbox ${type} components installed via the CLI. ## Usage \`\`\`typescript import { FeedbackWidget, NewsletterWidget, ContactWidget } from './' import { FeedInboxSDK } from '@feedinbox/sdk' // Initialize SDK const feedinbox = new FeedInboxSDK({ apiKey: 'fb_your_api_key' }) // Use components <FeedbackWidget apiKey="fb_your_api_key" onSuccess={(response) => console.log(response)} onError={(error) => console.error(error)} /> \`\`\` ## Customization These components are copied into your project, so you can: - Modify styling and behavior - Add your own props and features - Customize the UI to match your design system - Update dependencies as needed ## Components Included - **FeedbackWidget**: Collect user feedback with priority levels - **NewsletterWidget**: Newsletter subscription with consent management - **ContactWidget**: Contact form with validation ## Documentation Visit https://feedinbox.com/docs for complete documentation. `; await import_fs_extra3.default.writeFile(import_path3.default.join(targetDir, "README.md"), readmeContent); } function showNextSteps(type) { console.log(import_chalk2.default.yellow("\n\u{1F4D6} Next steps:")); console.log("1. Install the SDK if not already installed:"); console.log(import_chalk2.default.cyan(" npm install @feedinbox/sdk")); if (type === "react-tailwind") { console.log("2. Make sure Tailwind CSS is configured in your project"); console.log("3. Import and use the components"); } else if (type === "react-shadcn") { console.log("2. Make sure shadcn/ui is configured in your project"); console.log("3. Install required shadcn components if not already installed"); console.log("4. Import and use the components"); } else { console.log("2. Import the CSS file in your app:"); console.log(import_chalk2.default.cyan(' import "./components/feedinbox/styles.css"')); console.log("3. Import and use the components"); } console.log("4. Configure your API key and start collecting feedback!"); } // src/commands/init.ts var import_path4 = __toESM(require("path")); var import_fs_extra4 = __toESM(require("fs-extra")); var import_chalk3 = __toESM(require("chalk")); var import_inquirer2 = __toESM(require("inquirer")); async function initConfig() { const configPath = import_path4.default.join(process.cwd(), "feedinbox.config.json"); if (await import_fs_extra4.default.pathExists(configPath)) { const { overwrite } = await import_inquirer2.default.prompt([ { type: "confirm", name: "overwrite", message: "FeedInbox config already exists. Overwrite?", default: false } ]); if (!overwrite) { console.log(import_chalk3.default.yellow("Configuration unchanged.")); return; } } const answers = await import_inquirer2.default.prompt([ { type: "input", name: "apiKey", message: "Enter your FeedInbox API key:", validate: (input) => { if (!input) return "API key is required"; if (!input.startsWith("fb_")) return 'API key must start with "fb_"'; return true; } }, { type: "input", name: "apiUrl", message: "Enter your API URL (optional):", default: "https://api.feedinbox.com" }, { type: "input", name: "workspaceId", message: "Enter your workspace ID (optional):" } ]); const config = { apiKey: answers.apiKey, apiUrl: answers.apiUrl, workspaceId: answers.workspaceId || void 0, version: "1.0.0", createdAt: (/* @__PURE__ */ new Date()).toISOString() }; await import_fs_extra4.default.writeJson(configPath, config, { spaces: 2 }); console.log(import_chalk3.default.green("\u2705 Configuration saved to feedinbox.config.json")); console.log(import_chalk3.default.yellow("\u26A0\uFE0F Make sure to add feedinbox.config.json to your .gitignore")); } // src/commands/list.ts var import_chalk4 = __toESM(require("chalk")); function listTemplates() { console.log(import_chalk4.default.blue("\n\u{1F4CB} Available FeedInbox component types:\n")); console.log(import_chalk4.default.green("react-vanilla")); console.log(" React components with vanilla CSS"); console.log(" \u2022 Clean, customizable styling"); console.log(" \u2022 No external CSS framework required"); console.log(" \u2022 Perfect for custom design systems\n"); console.log(import_chalk4.default.green("react-tailwind")); console.log(" React components with Tailwind CSS"); console.log(" \u2022 Utility-first CSS approach"); console.log(" \u2022 Requires Tailwind CSS in your project"); console.log(" \u2022 Highly customizable with utilities\n"); console.log(import_chalk4.default.green("react-shadcn")); console.log(" React components with shadcn/ui"); console.log(" \u2022 Modern component library"); console.log(" \u2022 Requires shadcn/ui setup"); console.log(" \u2022 Beautiful, accessible components\n"); console.log(import_chalk4.default.yellow("Usage:")); console.log(" npx @feedinbox/cli add react-vanilla"); console.log(" npx @feedinbox/cli add react-tailwind"); console.log(" npx @feedinbox/cli add react-shadcn\n"); } // src/utils/project.ts var import_path5 = __toESM(require("path")); var import_fs_extra5 = __toESM(require("fs-extra")); var import_chalk5 = __toESM(require("chalk")); async function validateProject() { var _a, _b, _c, _d, _e; const cwd = process.cwd(); const packageJsonPath = import_path5.default.join(cwd, "package.json"); if (!await import_fs_extra5.default.pathExists(packageJsonPath)) { throw new Error("No package.json found. Please run this command in a Node.js project directory."); } const packageJson = await import_fs_extra5.default.readJson(packageJsonPath); const hasReact = ((_a = packageJson.dependencies) == null ? void 0 : _a.react) || ((_b = packageJson.devDependencies) == null ? void 0 : _b.react) || ((_c = packageJson.peerDependencies) == null ? void 0 : _c.react); if (!hasReact) { console.log(import_chalk5.default.yellow("\u26A0\uFE0F React not detected in dependencies. Make sure this is a React project.")); } const hasTypeScript = ((_d = packageJson.dependencies) == null ? void 0 : _d.typescript) || ((_e = packageJson.devDependencies) == null ? void 0 : _e.typescript) || await import_fs_extra5.default.pathExists(import_path5.default.join(cwd, "tsconfig.json")); if (!hasTypeScript) { console.log(import_chalk5.default.yellow("\u26A0\uFE0F TypeScript not detected. FeedInbox components are written in TypeScript.")); } console.log(import_chalk5.default.green("\u2705 Project validation passed")); } // src/cli.ts var program = new import_commander.Command(); console.log(import_chalk6.default.blue(` \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E \u2502 \u2502 \u2502 \u{1F680} FeedInbox CLI - Component Installer \u2502 \u2502 \u2502 \u2502 Install feedback widgets directly into your project \u2502 \u2502 \u2502 \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F `)); program.name("feedinbox").description("CLI tool for installing FeedInbox components").version("1.0.0"); program.command("add").description("Add FeedInbox components to your project").argument("[type]", "Component type (react-vanilla, react-tailwind, react-shadcn)").option("-d, --dir <directory>", "Installation directory", "src/components/feedinbox").option("-f, --force", "Overwrite existing files").option("-y, --yes", "Skip confirmation prompts").action(async (type, options) => { try { console.log(import_chalk6.default.blue("\u{1F50D} Validating project...")); await validateProject(); let componentType = type; if (!componentType) { const { selected } = await import_inquirer3.default.prompt([ { type: "list", name: "selected", message: "Which component type would you like to install?", choices: [ { name: "React + Vanilla CSS - Clean, customizable styling", value: "react-vanilla" }, { name: "React + Tailwind CSS - Utility-first styling", value: "react-tailwind" }, { name: "React + shadcn/ui - Modern component library", value: "react-shadcn" } ] } ]); componentType = selected; } await addComponents(componentType, options); console.log(import_chalk6.default.green("\u2705 Components installed successfully!")); console.log(import_chalk6.default.yellow("\u{1F4D6} Next steps:")); console.log("1. Install the SDK: npm install @feedinbox/sdk"); console.log("2. Import components in your React app"); console.log("3. Configure your API key"); } catch (error) { console.error(import_chalk6.default.red("\u274C Error:"), error instanceof Error ? error.message : error); process.exit(1); } }); program.command("init").description("Initialize FeedInbox configuration").action(async () => { try { await initConfig(); console.log(import_chalk6.default.green("\u2705 Configuration initialized!")); } catch (error) { console.error(import_chalk6.default.red("\u274C Error:"), error instanceof Error ? error.message : error); process.exit(1); } }); program.command("list").description("List available component types").action(() => { listTemplates(); }); program.exitOverride(); try { program.parse(process.argv); } catch (error) { process.exit(1); } if (!process.argv.slice(2).length) { program.outputHelp(); }