boring-business-card-generator
Version:
A CLI tool to generate personalized business card CLI packages for npm
936 lines (873 loc) • 29 kB
JavaScript
import inquirer from "inquirer";
import fs from "fs-extra";
import path from "path";
import chalk from "chalk";
import clear from "clear";
import { execSync } from "child_process";
// Clear console
clear();
console.log(chalk.cyan.bold("Welcome to the Boring Business Card Generator!"));
console.log(
chalk.gray(
"Answer the following questions to create and publish your personalized business card CLI.\n"
)
);
// Verify inquirer is loaded
if (!inquirer.createPromptModule) {
console.error(
chalk.red.bold(
"Error: Failed to load inquirer module. Please run `npm install` to ensure dependencies are installed."
)
);
process.exit(1);
}
// Define prompts for user details
const userQuestions = [
{
type: "input",
name: "fullName",
message: "What is your full name?",
validate: (input) => (input.trim() ? true : "Full name is required."),
},
{
type: "input",
name: "handle",
message: "What is your common social media handle (e.g., @username)?",
validate: (input) => (input.trim() ? true : "Handle is required."),
},
{
type: "input",
name: "jobProfile",
message: "What is your current job profile? (e.g., Software Developer)",
validate: (input) => (input.trim() ? true : "Job profile is required."),
},
{
type: "input",
name: "github",
message:
"What is your GitHub profile URL or username? (Optional, press Enter to skip)",
default: "",
filter: (input) => {
if (!input.trim()) return "";
if (/^https?:\/\//.test(input)) return input;
return `https://github.com/${input}`;
},
},
{
type: "input",
name: "githubRepo",
message:
"What is your GitHub repository name for this project? (e.g., my-business-card, press Enter to skip)",
default: "",
validate: (input) => {
if (!input.trim()) return true;
if (/^[a-zA-Z0-9_-]+$/.test(input)) return true;
return "Repository name can only contain letters, numbers, hyphens, or underscores.";
},
},
{
type: "input",
name: "linkedin",
message:
"What is your LinkedIn profile URL or username? (e.g., https://linkedin.com/in/username or username, press Enter to skip)",
default: "",
validate: (input) => {
if (!input.trim()) return true;
if (/^[a-zA-Z0-9_-]+$/.test(input)) return true;
if (
/^https?:\/\/(www\.)?linkedin\.com\/in\/[a-zA-Z0-9_-]+\/?$/.test(input)
)
return true;
return "Please enter a valid LinkedIn URL (e.g., https://linkedin.com/in/username) or username.";
},
filter: (input) => {
if (!input.trim()) return "";
if (/^https?:\/\//.test(input)) return input;
return `https://linkedin.com/in/${input}`;
},
},
{
type: "input",
name: "portfolio",
message:
"What is your portfolio website URL? (Optional, e.g., example.com or https://example.com, press Enter to skip)",
default: "",
validate: (input) => {
if (!input.trim()) return true;
const trimmed = input.trim();
if (
/^[a-zA-Z0-9][a-zA-Z0-9-.]+\.[a-zA-Z]{2,}$/.test(trimmed) ||
/^https?:\/\/[a-zA-Z0-9][a-zA-Z0-9-.]+\.[a-zA-Z]{2,}/.test(trimmed)
) {
return true;
}
return "Please enter a valid hostname (e.g., example.com) or URL (e.g., https://example.com).";
},
filter: (input) => {
if (!input.trim()) return "";
const trimmed = input.trim();
if (/^https?:\/\//.test(trimmed)) return trimmed;
return `https://${trimmed}`;
},
},
{
type: "input",
name: "certifications",
message:
"List any certifications (e.g., AWS Certified Developer, none if none)",
default: "none",
},
{
type: "input",
name: "tagline",
message: "What is your tagline? (Optional, press Enter to skip)",
default: "",
},
{
type: "input",
name: "email",
message: "What is your email address?",
validate: (input) =>
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(input)
? true
: "Please enter a valid email.",
},
{
type: "input",
name: "packageName",
message:
"What should be the npm package name for your card? (e.g., your handle without @ or @scope/name for scoped packages)",
validate: (input) => {
if (!input.trim()) return "Package name must be non-empty.";
if (/[A-Z]/.test(input)) return "Package name must be lowercase.";
if (!/^[a-z][a-z0-9-]*$/.test(input.replace(/^@[^/]+\//, ""))) {
return "Package name must start with a letter and contain only lowercase letters, numbers, or hyphens.";
}
return true;
},
},
];
// Define prompts for npm credentials
const npmQuestions = [
{
type: "input",
name: "npmUsername",
message: "Enter your npm username:",
validate: (input) => (input.trim() ? true : "Username is required."),
},
{
type: "password",
name: "npmPassword",
message: "Enter your npm password:",
mask: "*",
validate: (input) => (input.trim() ? true : "Password is required."),
},
{
type: "input",
name: "npmEmail",
message: "Enter your npm email address:",
validate: (input) =>
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(input)
? true
: "Please enter a valid email.",
},
{
type: "input",
name: "npm2FACode",
message:
"Enter your npm two-factor authentication code (if enabled, press Enter to skip):",
default: "",
},
];
// Define prompt for publish-time 2FA OTP
const publish2FAQuestion = [
{
type: "input",
name: "publish2FACode",
message: "Enter your npm two-factor authentication code for publishing:",
validate: (input) =>
input.trim()
? true
: "2FA code is required for publishing with 2FA enabled.",
},
];
// Function to extract LinkedIn messaging ID from URL
const getLinkedInMessageId = (linkedinUrl) => {
if (!linkedinUrl) return "";
const match = linkedinUrl.match(/linkedin\.com\/in\/([^\/]+)/);
return match ? match[1] : "";
};
// Generate card.js content
const generateCardJs = ({
fullName,
handle,
jobProfile,
github,
linkedin,
portfolio,
certifications,
tagline,
email,
packageName,
}) => {
const linkedinMessage = getLinkedInMessageId(linkedin);
return `#!/usr/bin/env node
import inquirer from "inquirer";
import chalk from "chalk";
import boxen from "boxen";
import open from "open";
import clear from "clear";
// Clear console for clean presentation
clear();
// Cache prompt module for reuse
const prompt = inquirer.createPromptModule();
// Define interactive questions
const questions = [
{
type: "list",
name: "action",
message: chalk.cyan("What would you like to do?"),
choices: [
{
name: \`Send me an \${chalk.cyan.bold("email")} 📧\`,
value: async () => {
try {
await open("mailto:${email}");
console.log(chalk.green("\\nEmail client opened. Looking forward to your message!\\n"));
} catch (error) {
console.error(chalk.red("\\nFailed to open email client: " + error.message + "\\n"));
}
},
},
${
linkedinMessage
? `
{
name: \`Message me on \${chalk.blue.bold("LinkedIn")} 💬\`,
value: async () => {
try {
await open("https://www.linkedin.com/messaging/thread/new/?recipient=${linkedinMessage}");
console.log(chalk.green("\\nLinkedIn messaging opened. Let's connect!\\n"));
} catch (error) {
console.error(chalk.red("\\nFailed to open LinkedIn messaging: " + error.message + "\\n"));
}
},
},
`
: ""
}
${
portfolio
? `
{
name: \`Visit my \${chalk.green.bold("portfolio")} 🌐\`,
value: async () => {
try {
await open("${portfolio}");
console.log(chalk.green("\\nPortfolio opened. Explore my work!\\n"));
} catch (error) {
console.error(chalk.red("\\nFailed to open portfolio: " + error.message + "\\n"));
}
},
},
`
: ""
}
${
github
? `
{
name: \`Check my \${chalk.blue.bold("GitHub")} 💻\`,
value: async () => {
try {
await open("${github}");
console.log(chalk.green("\\nGitHub opened. See my projects!\\n"));
} catch (error) {
console.error(chalk.red("\\nFailed to open GitHub: " + error.message + "\\n"));
}
},
},
`
: ""
}
{
name: "Exit",
value: () => {
console.log(chalk.white("\\nThanks for stopping by!\\n"));
},
},
],
},
];
// Define professional boxen styling
const options = {
padding: 1,
margin: 1,
borderStyle: "double",
borderColor: "cyan",
backgroundColor: "black",
title: chalk.bold.cyan("${fullName}'s Business Card"),
titleAlignment: "center",
};
// Data object with user details
const data = {
name: chalk.bold.white("${fullName}"),
handle: chalk.bold.gray("${handle}"),
work: chalk.white("${jobProfile}"),
${
github
? `github: chalk.gray("https://github.com/") + chalk.white.bold("${github
.split("/")
.pop()}"),`
: ""
}
${
linkedin
? `linkedin: chalk.gray("https://linkedin.com/in/") + chalk.white.bold("${linkedin
.split("/")
.pop()}"),`
: ""
}
${portfolio ? `portfolio: chalk.cyan.bold("${portfolio}"),` : ""}
${
certifications !== "none"
? `certification: chalk.white("${certifications}"),`
: ""
}
npx: chalk.redBright("npx") + " " + chalk.white.bold("${packageName.replace(
/^@[^/]+\//,
""
)}"),
labelWork: chalk.white.bold("Work: "),
${github ? `labelGitHub: chalk.white.bold("GitHub: "),` : ""}
${linkedin ? `labelLinkedIn: chalk.white.bold("LinkedIn: "),` : ""}
${portfolio ? `labelPortfolio: chalk.white.bold("Portfolio: "),` : ""}
${
certifications !== "none"
? `labelCertification: chalk.white.bold("Certification: "),`
: ""
}
labelCard: chalk.white.bold("Card: "),
};
// Cache formatted strings to reduce runtime concatenation
const newline = "\\n";
const heading = \`\${data.name} / \${data.handle}\`;
const working = \`\${data.labelWork}\${data.work}\`;
${github ? `const githubing = \`\${data.labelGitHub}\${data.github}\`;` : ""}
${
linkedin
? `const linkedining = \`\${data.labelLinkedIn}\${data.linkedin}\`;`
: ""
}
${
portfolio
? `const porting = \`\${data.labelPortfolio}\${data.portfolio}\`;`
: ""
}
${
certifications !== "none"
? `const certing = \`\${data.labelCertification}\${data.certification}\`;`
: ""
}
const carding = \`\${data.labelCard}\${data.npx}\`;
${
tagline
? `const tagline = chalk.italic.white("${tagline}");`
: `const tagline = "";`
}
// Build output with aligned formatting
const output = [
heading,
newline,
working,
${github ? "githubing," : ""}
${linkedin ? "linkedining," : ""}
${portfolio ? "porting," : ""}
${certifications !== "none" ? "certing," : ""}
newline,
carding,
${tagline ? "newline, tagline," : ""}
].filter(Boolean).join(newline);
// Display card
console.log(boxen(output, options));
// Display tip for interactivity
console.log(
chalk.gray(\`Tip: \${chalk.cyan("cmd/ctrl + click")} on links to open them\`)
);
// Execute prompt
prompt(questions).then(({ action }) => action());
`;
};
// Generate package.json content
const generatePackageJson = ({ packageName, fullName, github, githubRepo }) => {
const repoUrl =
github && githubRepo
? `https://github.com/${github.split("/").pop()}/${githubRepo}`
: "git+https://github.com/USERNAME/REPO.git";
return {
name: packageName,
version: "1.0.0",
description: `Professional business card for ${fullName}`,
main: "card.js",
type: "module",
bin: {
[packageName.replace(/^@[^/]+\//, "")]: "card.js",
},
scripts: {
start: "node card.js",
test: 'echo "Error: no test specified" && exit 1',
prepublishOnly: "node card.js",
},
repository: {
type: "git",
url: repoUrl,
},
files: ["card.js", "package.json", "README.md", "LICENSE"],
keywords: [packageName, "businesscard", "npxcard", "npmcard", "developer"],
author: fullName,
license: "MIT",
dependencies: {
boxen: "^7.1.1",
chalk: "^5.3.0",
clear: "^0.1.0",
inquirer: "^9.2.12",
open: "^10.1.0",
},
};
};
// Generate README.md content
const generateReadme = ({
fullName,
handle,
jobProfile,
github,
githubRepo,
linkedin,
portfolio,
certifications,
tagline,
email,
packageName,
}) => {
const linkedinMessage = getLinkedInMessageId(linkedin);
// Normalize portfolio URL for README
const normalizedPortfolio =
portfolio && !/^https?:\/\//.test(portfolio)
? `https://${portfolio}`
: portfolio;
// Generate repository URL
const repoUrl =
github && githubRepo
? `https://github.com/${github.split("/").pop()}/${githubRepo}`
: "https://github.com/USERNAME/REPO";
return `
# ${fullName}'s Business Card CLI
  
A professional CLI-based business card for ${fullName} (${handle}), showcasing my skills and contact information. Run it with \`npx ${packageName}\` to explore my portfolio, GitHub, LinkedIn, or send me a message.
## About Me
- **Name**: ${fullName} (${handle})
- **Status**: ${jobProfile}
${certifications !== "none" ? `- **Certification**: ${certifications}` : ""}
${tagline ? `- **Tagline**: ${tagline}` : ""}
- **Links**:
${normalizedPortfolio ? `- [Portfolio](${normalizedPortfolio})` : ""}
${github ? `- [GitHub](${github})` : ""}
${linkedin ? `- [LinkedIn](${linkedin})` : ""}
- Email: ${email}
## Features
- Interactive CLI interface with options to:
- Send an email 📧
${linkedinMessage ? "- Message me on LinkedIn 💬" : ""}
${normalizedPortfolio ? "- Visit my portfolio 🌐" : ""}
${github ? "- Check my GitHub 💻" : ""}
- Exit
- Professional styling with a double-bordered, cyan-themed card on a black background
- Optimized for performance with cached strings and async operations
## Installation
Run the card directly via npm:
\`\`\`bash
npx ${packageName}
\`\`\`
### Local Setup
1. Clone the repository:
\`\`\`bash
git clone ${repoUrl}.git
cd ${githubRepo || "REPO"}
\`\`\`
2. Ensure Node.js v22.5.1 is installed.
3. Install dependencies:
\`\`\`bash
npm install
\`\`\`
4. Run the card:
\`\`\`bash
npm start
\`\`\`
## Dependencies
- \`boxen@7.1.1\`: Renders the card with a styled box
- \`chalk@5.3.0\`: Adds color and formatting to text
- \`inquirer@9.2.12\`: Provides interactive CLI prompts
- \`open@10.1.0\`: Opens links in the default browser or email client
- \`clear@0.1.0\`: Clears the console for a clean display
## Development
To contribute or modify the card:
1. Fork and clone the repository.
2. Install dependencies with \`npm install\`.
3. Edit \`card.js\` (uses ESM with \`"type": "module"\`).
4. Test changes with \`npm start\`.
5. Submit a pull request to [${
github && githubRepo
? `${github.split("/").pop()}/${githubRepo}`
: "USERNAME/REPO"
}](${repoUrl}).
## Publishing
To publish an updated version to npm:
\`\`\`bash
npm publish
\`\`\`
Ensure you have npm publishing rights for the \`${packageName}\` package.
## License
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
## Contact
Connect with me:
- Email: [${email}](mailto:${email})
${linkedin ? `- LinkedIn: [${linkedin.split("/").pop()}](${linkedin})` : ""}
${
normalizedPortfolio
? `- Portfolio: [${normalizedPortfolio}](${normalizedPortfolio})`
: ""
}
${github ? `- GitHub: [${github.split("/").pop()}](${github})` : ""}
Run \`npx ${packageName}\` to interact with my card and explore my work!
`;
};
// Generate LICENSE content
const generateLicense = ({ fullName }) => `
MIT License
Copyright (c) 2025 ${fullName}
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
`;
// Function to authenticate with npm
async function authenticateNpm(
{ npmUsername, npmPassword, npmEmail, npm2FACode },
prompt,
maxRetries = 2
) {
let attempts = 0;
while (attempts <= maxRetries) {
try {
console.log(
chalk.yellow("Close all browsers to avoid authentication errors.")
);
// Create temporary .npmrc file
const npmrcPath = path.join(process.cwd(), ".npmrc");
const npmrcContent = `
//registry.npmjs.org/:_authToken=\${process.env.NPM_TOKEN}
email=${npmEmail}
`;
await fs.writeFile(npmrcPath, npmrcContent);
// Attempt npm login
const loginCommand = `npm login --registry=https://registry.npmjs.org/`;
const loginInput = `${npmUsername}\n${npmPassword}\n${npmEmail}${
npm2FACode ? `\n${npm2FACode}` : ""
}\n`;
execSync(loginCommand, { input: loginInput, stdio: "inherit" });
// Read auth token from .npmrc
const globalNpmrcPath = path.join(
process.env.USERPROFILE || process.env.HOME,
".npmrc"
);
let token = "";
if (await fs.pathExists(globalNpmrcPath)) {
const npmrcContent = await fs.readFile(globalNpmrcPath, "utf8");
const tokenMatch = npmrcContent.match(
/\/\/registry\.npmjs\.org\/:_authToken=(.+)/
);
if (tokenMatch) token = tokenMatch[1];
}
// Store token in project .npmrc
await fs.writeFile(
npmrcPath,
`//registry.npmjs.org/:_authToken=${token}\nemail=${npmEmail}`
);
console.log(chalk.green.bold("Successfully authenticated with npm!"));
return { success: true, npmrcPath, token };
} catch (error) {
attempts++;
console.error(
chalk.red.bold(`Authentication attempt ${attempts} failed: `),
error.message
);
if (attempts > maxRetries) {
console.log(chalk.yellow("Maximum authentication attempts reached."));
return { success: false };
}
console.log(chalk.cyan("Retrying authentication..."));
const retryAnswers = await prompt([
{
type: "input",
name: "npm2FACode",
message:
"Enter a new npm two-factor authentication code (if enabled, press Enter to skip):",
default: "",
},
]);
npm2FACode = retryAnswers.npm2FACode;
}
}
return { success: false };
}
// Function to publish the package with retry on name conflict
async function publishPackage({ outputDir, userAnswers, prompt, npmAnswers }) {
let currentPackageName = userAnswers.packageName;
let attemptCount = 0;
const maxAttempts = 3;
const maxNpxRetries = 2;
while (attemptCount < maxAttempts) {
let npmrcPath;
try {
// Test local execution
console.log(
chalk.cyan(`Testing local execution of ${currentPackageName}...`)
);
execSync("node card.js", { cwd: outputDir, stdio: "inherit" });
// Clear npm cache
console.log(chalk.cyan("Clearing npm cache before publishing..."));
execSync("npm cache clean --force", { stdio: "inherit" });
// Ensure authentication for publishing
const authResult = await authenticateNpm(npmAnswers, prompt);
if (!authResult.success) {
throw new Error("Failed to authenticate for publishing");
}
npmrcPath = authResult.npmrcPath;
// Prompt for 2FA OTP for publishing if 2FA was used
let publish2FACode = "";
if (npmAnswers.npm2FACode) {
const publishAnswers = await prompt(publish2FAQuestion);
publish2FACode = publishAnswers.publish2FACode;
}
// Publish package
console.log(chalk.cyan(`Publishing ${currentPackageName} to npm...`));
const publishCommand = currentPackageName.startsWith("@")
? `npm publish --access=public${
publish2FACode ? ` --otp=${publish2FACode}` : ""
}`
: `npm publish${publish2FACode ? ` --otp=${publish2FACode}` : ""}`;
execSync(publishCommand, { cwd: outputDir, stdio: "inherit" });
// Wait for registry propagation
console.log(chalk.cyan("Waiting for package to propagate..."));
await new Promise((resolve) => setTimeout(resolve, 10000));
// Test npx execution with retries
let npxSuccess = false;
for (let retry = 0; retry <= maxNpxRetries; retry++) {
try {
console.log(
chalk.cyan(
`Testing npx execution of ${currentPackageName} (attempt ${
retry + 1
}/${maxNpxRetries + 1})...`
)
);
execSync(`npx ${currentPackageName}`, { stdio: "inherit" });
npxSuccess = true;
break;
} catch (npxError) {
console.warn(
chalk.yellow(
`npx test failed (attempt ${retry + 1}): ${npxError.message}`
)
);
if (retry < maxNpxRetries) {
console.log(chalk.cyan("Retrying after delay..."));
await new Promise((resolve) => setTimeout(resolve, 5000));
}
}
}
if (!npxSuccess) {
throw new Error("npx test failed after retries");
}
console.log(
chalk.green.bold(
`\nSuccess! Your business card is published as ${currentPackageName}!`
)
);
console.log(
chalk.cyan(`Run it anywhere with: npx ${currentPackageName}`)
);
console.log(
chalk.gray(
"Note: Update the repository URL in package.json and push to your GitHub repository for future updates."
)
);
return true;
} catch (error) {
const errorMessage = error.message || "";
if (
errorMessage.includes("403") ||
errorMessage.includes("ENEEDAUTH") ||
errorMessage.includes("cannot publish") ||
errorMessage.includes("Package name too")
) {
console.error(
chalk.red.bold(`Error publishing '${currentPackageName}': `),
errorMessage
);
attemptCount++;
if (attemptCount >= maxAttempts) {
console.log(
chalk.yellow(
`Maximum attempts (${maxAttempts}) reached. You can publish manually later.`
)
);
break;
}
console.log(
chalk.cyan(
`Please choose a different package name (e.g., @${userAnswers.handle.replace(
/^@/,
""
)}/${currentPackageName
.split("/")
.pop()} or ${currentPackageName}-card).`
)
);
const { newPackageName } = await prompt([
{
type: "input",
name: "newPackageName",
message:
"Enter a new package name (or press Enter to skip publishing):",
validate: (input) => {
if (!input.trim()) return true;
if (/[A-Z]/.test(input)) return "Package name must be lowercase.";
if (!/^[a-z][a-z0-9-]*$/.test(input.replace(/^@[^/]+\//, ""))) {
return "Package name must start with a letter and contain only lowercase letters, numbers, or hyphens.";
}
return true;
},
default: `@${userAnswers.handle.replace(
/^@/,
""
)}/${currentPackageName.split("/").pop()}`,
},
]);
if (!newPackageName) {
console.log(chalk.yellow("Publishing skipped."));
break;
}
currentPackageName = newPackageName;
userAnswers.packageName = currentPackageName;
await fs.writeFile(
path.join(outputDir, "package.json"),
JSON.stringify(generatePackageJson(userAnswers), null, 2)
);
await fs.writeFile(
path.join(outputDir, "card.js"),
generateCardJs(userAnswers)
);
await fs.writeFile(
path.join(outputDir, "README.md"),
generateReadme(userAnswers)
);
} else {
console.error(chalk.red.bold("Publishing failed: "), errorMessage);
break;
}
} finally {
// Clean up .npmrc
if (npmrcPath && (await fs.pathExists(npmrcPath))) {
await fs.remove(npmrcPath);
}
}
}
console.log(chalk.yellow("You can try publishing manually by running:"));
console.log(chalk.yellow(` cd ${outputDir}`));
console.log(chalk.yellow(" npm install"));
console.log(chalk.yellow(" npm login"));
console.log(
chalk.yellow(
` npm publish${
currentPackageName.startsWith("@") ? " --access=public" : ""
}`
)
);
console.log(
chalk.gray(
"Note: Update the repository URL in package.json before publishing."
)
);
return false;
}
// Main function to run the CLI
async function run() {
try {
const prompt = inquirer.createPromptModule();
// Collect user details
const userAnswers = await prompt(userQuestions);
const { fullName } = userAnswers;
const outputDir = path.join(process.cwd(), userAnswers.packageName);
// Create project directory
await fs.ensureDir(outputDir);
// Write generated files
await fs.writeFile(
path.join(outputDir, "card.js"),
generateCardJs(userAnswers)
);
await fs.writeFile(
path.join(outputDir, "package.json"),
JSON.stringify(generatePackageJson(userAnswers), null, 2)
);
await fs.writeFile(
path.join(outputDir, "README.md"),
generateReadme(userAnswers)
);
await fs.writeFile(
path.join(outputDir, "LICENSE"),
generateLicense({ fullName })
);
// Set executable permissions for card.js
await fs.chmod(path.join(outputDir, "card.js"), 0o755);
console.log(
chalk.green.bold(`\nBusiness card CLI generated in ${outputDir}\n`)
);
// Collect npm credentials
console.log(chalk.cyan.bold("Now, let's publish your card to npm."));
console.log(
chalk.gray(
"Please provide your npm credentials. Create an account at https://www.npmjs.org if you don't have one.\n"
)
);
console.log(
chalk.yellow(
"Close all browsers before proceeding to avoid authentication errors."
)
);
const npmAnswers = await prompt(npmQuestions);
// Attempt to publish
await publishPackage({ outputDir, userAnswers, prompt, npmAnswers });
} catch (error) {
console.error(
chalk.red.bold("Error generating business card: "),
error.message
);
console.log(
chalk.yellow("Please ensure Node.js v22.5.1 is installed and try again.")
);
}
}
run();