mydata-cli
Version:
A CLI tool for interacting with MyData API and managing data. Supports login, data retrieval, and more. Built with Node.js.
368 lines (329 loc) โข 12.2 kB
JavaScript
import { Command } from "commander";
import fs from "fs-extra";
import path from "path";
import { api } from "../utils/api.js";
import { parseEnvFile } from "../utils/parseEnvFile.js";
import readline from "readline";
function detectEnvFile() {
const files = [".env.local", ".env.development", ".env", ".env.example"];
const found = files.find((f) => fs.existsSync(path.join(process.cwd(), f)));
return found ? path.join(process.cwd(), found) : null;
}
function promptYesNo(question) {
return new Promise((resolve) => {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question(`${question} (yes/no): `, (answer) => {
rl.close();
const normalized = answer.trim().toLowerCase();
resolve(normalized === "yes" || normalized === "y");
});
});
}
const project = new Command("project").description("Manage projects");
// โ
Add new project (auto + interactive)
project
.command("add")
.description("Add a new project with auto-detected info")
.option("-t, --title <title>", "Project title")
.option("-d, --description <desc>", "Project description")
.option("-r, --repo <url>", "GitHub repo link")
.option("-l, --live <url>", "Live demo URL")
.option("--tech <stack...>", "Tech stack (space-separated)")
.option("--tags <tags...>", "Tags or hashtags")
.option("-g, --groupName <name>", "Env group name")
.option("-e, --env <path>", "Path to .env file")
.action(async (opts) => {
try {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
// 1. Auto-detect package.json
const pkgPath = path.resolve(process.cwd(), "package.json");
const pkg = fs.existsSync(pkgPath)
? JSON.parse(fs.readFileSync(pkgPath, "utf8"))
: {};
const title = opts.title || pkg.name || "Untitled Project";
let description = opts.description || pkg.description || "";
// let tags = opts.tags || pkg.keywords || [];
// 2. If no description, ask the user
if (!description) {
description = await new Promise((resolve) => {
rl.question(
"๐ Description not found. Enter a short description: ",
(desc) => {
resolve(desc.trim());
}
);
});
}
const repo = opts.repo || pkg.repository?.url || "";
const live = opts.live || "";
const techStack = opts.tech || Object.keys(pkg.dependencies || {});
const tags = pkg.keywords || Object.keys(pkg.keywords || []);
// 3. Auto-detect env file
const envPath = opts.env || detectEnvFile();
const groupName = opts.groupName || "default";
const variables = envPath ? parseEnvFile(envPath) : [];
// 4. Summary
console.log("\n๐ฆ Detected Project Info:");
console.log("๐ Title:", title);
if (description) console.log("๐งพ Description:", description);
if (repo) console.log("๐ Repo:", repo);
if (live) console.log("๐ Live:", live);
if (techStack.length) console.log("๐ง Tech:", techStack.join(", "));
if (tags.length) console.log("๐ท๏ธ Tags:", tags.join(", "));
if (envPath)
console.log(
"๐ฑ Env File:",
path.basename(envPath),
`(${variables.length} vars)`
);
else console.log("โ ๏ธ No .env file found");
// 5. Confirm
const confirm = await new Promise((resolve) => {
rl.question("\nโ Do you want to continue? (yes/no): ", (answer) => {
const normalized = answer.trim().toLowerCase();
resolve(normalized === "yes" || normalized === "y");
});
});
rl.close();
if (!confirm) {
console.log("โ Cancelled by user.");
return;
}
// 6. Submit
const res = await api.post("/api/projects", {
title,
description,
repo,
live,
techStack,
tags,
envGroups: envPath
? [
{
groupName,
variables,
},
]
: [],
});
console.log("\nโ
Project added:", res.data.data.title);
} catch (err) {
console.error("โ", err.response?.data?.error || err.message);
}
});
// โ
List Projects
project
.command("list")
.description("List all projects")
.action(async () => {
try {
const res = await api.get("/api/projects");
const projects = res.data.data;
projects.forEach((p) => {
console.log(`๐ ${p.title} โ ${p.description}`);
if (p.repo) console.log(` ๐ Repo: ${p.repo}`);
if (p.live) console.log(` ๐ Live: ${p.live}`);
if (p.techStack?.length)
console.log(` ๐ง Tech: ${p.techStack.join(", ")}`);
if (p.tags?.length) console.log(` ๐ท๏ธ Tags: ${p.tags.join(", ")}`);
p.envGroups?.forEach((group) => {
console.log(` ๐ง ${group.groupName}`);
group.variables.forEach((v) =>
console.log(` ${v.key} = ${v.value}`)
);
});
console.log();
});
} catch (err) {
console.error("โ", err.response?.data?.error || err.message);
}
});
// List IDs
project
.command("li")
.description("List all projects")
.action(async () => {
try {
const res = await api.get("/api/projects");
const projects = res.data.data;
projects.forEach((p) => {
console.log(`๐ ID- ${p._id} \n ${p.title} โ ${p.description}`);
});
} catch (err) {
console.error("โ", err.response?.data?.error || err.message);
}
});
// โ
Add new Project with env group + repo/live/tech/tags
// project
// .command("add")
// .description("Add a new project")
// .requiredOption("-t, --title <title>", "Project title")
// .option("-d, --description <desc>", "Description")
// .option("-r, --repo <url>", "GitHub repo link")
// .option("-l, --live <url>", "Live demo URL")
// .option("--tech <stack...>", "Tech stack (space-separated)")
// .option("--tags <tags...>", "Tags/hashtags (space-separated)")
// .requiredOption("-g, --groupName <name>", "Env group name")
// .requiredOption("-e, --env <path>", "Path to .env file")
// .action(async (opts) => {
// try {
// const variables = parseEnvFile(opts.env);
// const res = await api.post("/api/projects", {
// title: opts.title,
// description: opts.description || "",
// repo: opts.repo,
// live: opts.live,
// techStack: opts.tech || [],
// tags: opts.tags || [],
// envGroups: [
// {
// groupName: opts.groupName,
// variables,
// },
// ],
// });
// console.log("โ
Project added:", res.data.data.title);
// } catch (err) {
// console.error("โ", err.response?.data?.error || err.message);
// }
// });
// โ
Edit Project with env group + repo/live/tech/tags
project
.command("edit")
.description("Edit a project")
.requiredOption("-i, --id <id>", "Project ID")
.option("-t, --title <title>", "New title")
.option("-d, --description <desc>", "New description")
.option("-r, --repo <url>", "New GitHub repo link")
.option("-l, --live <url>", "New live demo URL")
.option("--tech <stack...>", "New tech stack")
.option("--tags <tags...>", "New tags")
.option("-g, --groupName <name>", "Replace env group name")
.option("-e, --env <path>", "Replace env file")
.action(async (opts) => {
try {
const updates = {};
if (opts.title) updates.title = opts.title;
if (opts.description) updates.description = opts.description;
if (opts.repo) updates.repo = opts.repo;
if (opts.live) updates.live = opts.live;
if (opts.tech) updates.techStack = opts.tech;
if (opts.tags) updates.tags = opts.tags;
if (opts.groupName && opts.env) {
updates.envGroups = [
{
groupName: opts.groupName,
variables: parseEnvFile(opts.env),
},
];
}
const res = await api.patch("/api/projects", {
id: opts.id,
updates,
});
console.log("โ
Project updated:", res.data.data.title);
} catch (err) {
console.error("โ", err.response?.data?.error || err.message);
}
});
// โ
Delete Project
project
.command("delete")
.description("Delete a project")
.requiredOption("-i, --id <id>", "Project ID")
.action(async (opts) => {
try {
const res = await api.delete(`/api/projects?id=${opts.id}`);
console.log("๐๏ธ", res.data.message);
} catch (err) {
console.error("โ", err.response?.data?.error || err.message);
}
});
// โ
Add new env group
project
.command("group:add")
.description("Add a new env group to a project")
.requiredOption("-i, --id <id>", "Project ID")
.requiredOption("-g, --groupName <name>", "New group name")
.requiredOption("-e, --env <path>", "Path to .env file")
.action(async (opts) => {
try {
const projectRes = await api.get("/api/projects");
const project = projectRes.data.data.find((p) => p._id === opts.id);
if (!project) return console.error("โ Project not found");
const existing = project.envGroups || [];
const updatedGroups = [
...existing,
{
groupName: opts.groupName,
variables: parseEnvFile(opts.env),
},
];
const res = await api.patch("/api/projects", {
id: opts.id,
updates: { envGroups: updatedGroups },
});
console.log("โ
Added new env group:", opts.groupName);
} catch (err) {
console.error("โ", err.response?.data?.error || err.message);
}
});
// โ
Edit existing env group
project
.command("group:edit")
.description("Edit an existing env group")
.requiredOption("-i, --id <id>", "Project ID")
.requiredOption("-g, --groupName <name>", "Group name to update")
.requiredOption("-e, --env <path>", "New env file")
.action(async (opts) => {
try {
const projectRes = await api.get("/api/projects");
const project = projectRes.data.data.find((p) => p._id === opts.id);
if (!project) return console.error("โ Project not found");
const updatedGroups = project.envGroups.map((group) =>
group.groupName === opts.groupName
? {
groupName: group.groupName,
variables: parseEnvFile(opts.env),
}
: group
);
const res = await api.patch("/api/projects", {
id: opts.id,
updates: { envGroups: updatedGroups },
});
console.log("โ
Updated env group:", opts.groupName);
} catch (err) {
console.error("โ", err.response?.data?.error || err.message);
}
});
// โ
Delete env group
project
.command("group:delete")
.description("Delete a specific env group from a project")
.requiredOption("-i, --id <id>", "Project ID")
.requiredOption("-g, --groupName <name>", "Group name to delete")
.action(async (opts) => {
try {
const projectRes = await api.get("/api/projects");
const project = projectRes.data.data.find((p) => p._id === opts.id);
if (!project) return console.error("โ Project not found");
const filteredGroups = project.envGroups.filter(
(group) => group.groupName !== opts.groupName
);
const res = await api.patch("/api/projects", {
id: opts.id,
updates: { envGroups: filteredGroups },
});
console.log("๐๏ธ Deleted group:", opts.groupName);
} catch (err) {
console.error("โ", err.response?.data?.error || err.message);
}
});
export default project;