DEV Community

pjdev2d
pjdev2d

Posted on

np

ChatGPT - Publish NPM Package Steps

Shared via ChatGPT

favicon chatgpt.com

#!/usr/bin/env node

const { Command } = require("commander");
const fs = require("fs-extra");
const path = require("path");
const babel = require("@babel/core");
const presetTypescript = require("@babel/preset-typescript");
const { execSync } = require("child_process");
const registry = require("../registry.json");

const program = new Command();

/* -----------------------------
   INQUIRER (FIXED SAFE WRAPPER)
------------------------------*/
const prompt = async (questions) => {
  const inquirer = await import("inquirer");
  return inquirer.default.prompt(questions);
};

/* =============================
   ADD COMMAND
============================= */
program
  .command("add <name>")
  .description("Add components or utilities")
  .action(async (name) => {
    try {
      const cwd = process.cwd();
      console.log("\nπŸš€ Starting installation...\n");

      const isTsProject = fs.existsSync(path.join(cwd, "tsconfig.json"));

      let templatePath = "";
      let outputDir = "";
      let extension = "tsx";

      /* ---------------- COMPONENTS ---------------- */
      if (!name.startsWith("utils/")) {
        templatePath = path.join(__dirname, `../templates/${name}.tsx`);
        outputDir = path.join(cwd, "src/components/pejay-ui");
        extension = "tsx";
      } else {

      /* ---------------- UTILITIES ---------------- */
        const utilName = name.split("/")[1];
        templatePath = path.join(__dirname, `../utils/${utilName}.ts`);
        outputDir = path.join(cwd, "src/utils/pejay-ui");
        extension = "ts";
      }

      if (!fs.existsSync(templatePath)) {
        console.log(`❌ "${name}" not found`);
        return;
      }

      const code = await fs.readFile(templatePath, "utf8");

      let finalCode = code;
      let outputExt = extension;

      /* ---------------- TRANSFORM ---------------- */
      if (!isTsProject) {
        const transformed = babel.transformSync(code, {
          presets: [presetTypescript],
          filename: `file.${extension}`,
        });

        if (!transformed?.code) {
          throw new Error("Babel transform failed");
        }

        finalCode = transformed.code;
        outputExt = extension === "tsx" ? "jsx" : "js";
      }

      await fs.ensureDir(outputDir);

      const itemName = name.includes("/") ? name.split("/")[1] : name;
      const outputPath = path.join(outputDir, `${itemName}.${outputExt}`);

      await fs.writeFile(outputPath, finalCode);
      console.log(`βœ… Created ${itemName}.${outputExt}`);

      /* ---------------- DEPENDENCIES ---------------- */
      const itemRegistry = registry[name];

      if (itemRegistry?.dependencies?.length) {
        for (const dependency of itemRegistry.dependencies) {
          const depName = dependency.includes("/")
            ? dependency.split("/")[1]
            : dependency;

          const depPath = path.join(__dirname, `../utils/${depName}.ts`);

          if (!fs.existsSync(depPath)) continue;

          let depCode = await fs.readFile(depPath, "utf8");

          if (!isTsProject) {
            const transformed = babel.transformSync(depCode, {
              presets: [presetTypescript],
              filename: `${depName}.ts`,
            });

            depCode = transformed.code;
          }

          const utilDir = path.join(cwd, "src/utils/pejay-ui");
          await fs.ensureDir(utilDir);

          const ext = isTsProject ? "ts" : "js";
          const outPath = path.join(utilDir, `${depName}.${ext}`);

          if (!fs.existsSync(outPath)) {
            await fs.writeFile(outPath, depCode);
            console.log(`πŸ“¦ Added dependency ${depName}`);
          }
        }
      }

      /* ---------------- STATE SAVE ---------------- */
      const statePath = path.join(cwd, ".pejay-ui.json");

      let state = {};
      if (fs.existsSync(statePath)) {
        state = JSON.parse(await fs.readFile(statePath, "utf8"));
      }

      state.installed = state.installed || {};

      state.installed[name] = {
        files: [`src/components/pejay-ui/${itemName}.${outputExt}`],
        uses:
          itemRegistry?.dependencies?.map((d) =>
            d.includes("/") ? d.split("/")[1] : d,
          ) || [],
      };

      await fs.writeFile(statePath, JSON.stringify(state, null, 2));

      console.log(`\nπŸŽ‰ ${name} installed successfully\n`);
    } catch (err) {
      console.error("\n❌ Add failed\n", err);
    }
  });

/* =============================
   REMOVE COMMAND (SAFE VERSION)
============================= */
program
  .command("remove <name>")
  .description("Remove components or utilities safely")
  .action(async (name) => {
    try {
      const cwd = process.cwd();
      console.log("\n🧹 Starting removal...\n");

      const itemRegistry = registry[name];

      if (!itemRegistry) {
        console.log(`❌ "${name}" not found in registry`);
        return;
      }

      const isTsProject = fs.existsSync(path.join(cwd, "tsconfig.json"));
      const itemName = name.includes("/") ? name.split("/")[1] : name;

      const componentDir = path.join(cwd, "src/components/pejay-ui");
      const utilsDir = path.join(cwd, "src/utils/pejay-ui");

      let removedAny = false;

      /* ---------------- REMOVE COMPONENT ---------------- */
      const exts = isTsProject ? ["tsx", "ts"] : ["jsx", "js"];

      for (const ext of exts) {
        const file = path.join(componentDir, `${itemName}.${ext}`);

        if (fs.existsSync(file)) {
          await fs.remove(file);
          console.log(`πŸ—‘οΈ Removed ${file}`);
          removedAny = true;
        }
      }

      /* ---------------- LOAD STATE ---------------- */
      const statePath = path.join(cwd, ".pejay-ui.json");

      let state = {};
      if (fs.existsSync(statePath)) {
        state = JSON.parse(await fs.readFile(statePath, "utf8"));
      }

      state.installed = state.installed || {};

      /* ---------------- BUILD USAGE MAP ---------------- */
      const usageMap = new Map();

      for (const comp of Object.values(state.installed)) {
        for (const u of comp.uses || []) {
          usageMap.set(u, (usageMap.get(u) || 0) + 1);
        }
      }

      /* ---------------- SAFE UTILITY REMOVE ---------------- */
      if (itemRegistry.dependencies?.length) {
        for (const dep of itemRegistry.dependencies) {
          const utilName = dep.includes("/") ? dep.split("/")[1] : dep;

          const usage = usageMap.get(utilName) || 0;

          if (usage > 1) {
            console.log(`β›” Skipping ${utilName} (used by other components)`);
            continue;
          }

          const { confirm } = await prompt([
            {
              type: "confirm",
              name: "confirm",
              message: `Remove utility "${utilName}"?`,
              default: false,
            },
          ]);

          if (!confirm) continue;

          for (const ext of exts) {
            const file = path.join(utilsDir, `${utilName}.${ext}`);

            if (fs.existsSync(file)) {
              await fs.remove(file);
              console.log(`πŸ—‘οΈ Removed ${file}`);
              removedAny = true;
            }
          }
        }
      }

      /* ---------------- REMOVE PACKAGES ---------------- */
      // Only collect packages from deps that NO other component still uses
      const pkgs = [];

      for (const dep of itemRegistry.dependencies || []) {
        const utilName = dep.includes("/") ? dep.split("/")[1] : dep;
        const usage = usageMap.get(utilName) || 0;

        // usage > 1 means another component still needs this dep β†’ skip its packages
        if (usage > 1) {
          console.log(`β›” Keeping packages for ${utilName} (used by other components)`);
          continue;
        }

        const reg = registry[dep];
        if (reg?.packages) pkgs.push(...reg.packages);
      }

      if (pkgs.length) {
        const unique = [...new Set(pkgs)];

        const { ok } = await prompt([
          {
            type: "confirm",
            name: "ok",
            message: `Uninstall packages: ${unique.join(", ")}?`,
            default: false,
          },
        ]);

        if (ok) {
          execSync(`npm uninstall ${unique.join(" ")}`, {
            cwd,
            stdio: "inherit",
          });
        }
      }

      /* ---------------- CLEAN STATE ---------------- */
      if (state.installed[name]) {
        delete state.installed[name];
        await fs.writeFile(statePath, JSON.stringify(state, null, 2));
      }

      console.log(`\nπŸŽ‰ ${name} removed successfully\n`);
    } catch (err) {
      console.error("\n❌ Remove failed\n", err);
    }
  });

program.parse();

Enter fullscreen mode Exit fullscreen mode

{
  "name": "demo-dev2d-cli",
  "version": "1.0.0",
  "description": "CLI to install reusable React components like shadcn/ui",
  "main": "bin/index.js",
  "bin": {
    "demo-dev2d-cli": "./bin/index.js"
  },
  "files": [
    "bin",
    "templates",
    "utils",
    "registry.json"
  ],
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "demo-auth",
  "license": "ISC",
  "dependencies": {
    "@babel/core": "^7.29.0",
    "@babel/preset-react": "^7.28.5",
    "@babel/preset-typescript": "^7.28.5",
    "@types/react": "^19.2.14",
    "clsx": "^2.1.1",
    "commander": "^14.0.3",
    "fs-extra": "^11.3.5",
    "inquirer": "^13.4.3",
    "react": "^19.2.6",
    "tailwind-merge": "^3.6.0"
  }
}
Enter fullscreen mode Exit fullscreen mode

{
  "input": {
    "dependencies": ["utils/cn"]
  },

  "utils/cn": {
    "packages": ["clsx", "tailwind-merge"]
  }
}

Enter fullscreen mode Exit fullscreen mode

Top comments (0)