@feedinbox/cli
Version:
CLI tool for installing FeedInbox components into your project
434 lines (412 loc) • 17.9 kB
JavaScript
#!/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();
}