Skip to content

Commit

Permalink
feat: add info command and make some corrections (#8)
Browse files Browse the repository at this point in the history
* feat: add info command and make some corrections

* fix: general adjustment

* fix: update cli examples

* fix: parameters in any position

* fix: ui improvements

* fix: removed extra consoles

* fix: project name, error msg, param dependency

* fix: remove pack

---------

Co-authored-by: Richard Zampieri <[email protected]>
  • Loading branch information
juliano-soares and rsaz committed May 25, 2023
1 parent bd02a80 commit acb5d3f
Show file tree
Hide file tree
Showing 12 changed files with 197 additions and 51 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,5 @@ package-lock.json

*.log
expressots.config.ts

*.tgz
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@expressots/cli",
"version": "1.2.2",
"version": "1.3.0-dev",
"description": "Expressots CLI - modern, fast, lightweight nodejs web framework (@cli)",
"author": "Richard Zampieri",
"license": "MIT",
Expand Down
25 changes: 21 additions & 4 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,30 @@
import yargs from "yargs";
import { hideBin } from "yargs/helpers";
import { generateProject } from "./generate";
import { infoProject } from "./info";
import { createProject } from "./new";

export const CLI_VERSION = "1.2.2";

console.log(`\n[🐎 Expressots]\n`);

yargs(hideBin(process.argv))
.example("$0 new <project-name>", "Create a new Expresso TS project")
.scriptName("expressots")
.command(createProject())
.command(generateProject())
.command(infoProject())
.example("$0 new expressots-demo", "Create interactively")
.example("$0 new expressots-demo -d ./", "Create interactively with path")
.example("$0 new expressots-demo -p yarn -t opinionated", "Create silently")
.example("$0 new expressots-demo -p yarn -t opinionated -d ./", "Create silently with path")
.example("$0 generate service user-create", "Scaffold a service")
.example("$0 info", "Show CLI details")
.demandCommand(1, "You need at least one command before moving on")
.epilog("For more information, visit https://expresso-ts.com")
.help()
.parse();
.epilog("For more information: \n" +
"🌐 visit:\t https://expresso-ts.com\n" +
"💖 Sponsor:\t https://github.com/sponsors/expressots")
.help("help", "Show command help")
.alias("h", "help")
.version(false)
.wrap(140)
.parse();
2 changes: 1 addition & 1 deletion src/generate/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ type CommandModuleArgs = {};
const generateProject = (): CommandModule<CommandModuleArgs, any> => {
return {
command: "generate [schematic] [path]",
describe: "Generate a schematic",
describe: "Scaffold a new resource",
aliases: ["g"],
builder: (yargs: Argv): Argv => {
yargs.positional("schematic", {
Expand Down
18 changes: 18 additions & 0 deletions src/info/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { CommandModule } from "yargs";
import { infoForm } from "./form";

// eslint-disable-next-line @typescript-eslint/ban-types
type CommandModuleArgs = {};

const infoProject = (): CommandModule<CommandModuleArgs, any> => {
return {
command: "info",
describe: "Displays project details",
aliases: ["i"],
handler: async () => {
await infoForm();
},
};
};

export { infoProject };
39 changes: 39 additions & 0 deletions src/info/form.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import chalk from "chalk";
import path from "path";
import fs from "fs";
import os from "os";
import { CLI_VERSION } from "../cli";
import { printError } from "../utils/cli-ui";

function getInfosFromPackage() {
try {
// Get the absolute path of the input directory parameter
const absDirPath = path.resolve();
// Load the package.json file
const packageJsonPath = path.join(absDirPath, "package.json");

const fileContents = fs.readFileSync(packageJsonPath, "utf-8");
const packageJson = JSON.parse(fileContents);

console.log(chalk.green("ExpressoTS Project:"));
console.log(chalk.bold(`\tName: ${packageJson.name}`));
console.log(chalk.bold(`\tDescription: ${packageJson.description}`));
console.log(chalk.bold(`\tVersion: ${packageJson.version}`));
console.log(chalk.bold(`\tAuthor: ${packageJson.author}`));
} catch (error) {
printError("No project information available.", "package.json not found!")
}
}

const infoForm = async (): Promise<void> => {
console.log(chalk.green("System informations:"));
console.log(chalk.bold(`\tOS Version: ${os.version()}`));
console.log(chalk.bold(`\tNodeJS version: ${process.version}`));

console.log(chalk.green("CLI Version:"));
console.log(chalk.bold(`\tCurrent version: v${CLI_VERSION}`));

getInfosFromPackage();
};

export { infoForm };
1 change: 1 addition & 0 deletions src/info/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./cli"
22 changes: 15 additions & 7 deletions src/new/cli.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { CommandModule, Argv } from "yargs";
import { Argv, CommandModule } from "yargs";
import { projectForm } from "./form";

type CommandModuleArgs = {};

const createProject = (): CommandModule<CommandModuleArgs, any> => {
return {
command: "new <project-name> [package-manager] [template]",
describe: "Create a new Expresso TS project",
command: "new <project-name> [package-manager] [template] [directory]",
describe: "Create a new project",
builder: (yargs: Argv): Argv => {
yargs
.positional("project-name", {
Expand All @@ -24,14 +24,22 @@ const createProject = (): CommandModule<CommandModuleArgs, any> => {
type: "string",
choices: ["npm", "yarn", "pnpm"],
alias: "p",
});
})
.option("directory", {
describe: "The directory for new project",
type: "string",
alias: "d",
})
.implies("package-manager", "template")
.implies("template", "package-manager")

return yargs;
},
handler: async ({projectName, packageManager, template}) => {
return await projectForm(projectName, packageManager, template);
handler: async ({projectName, packageManager , template, directory}) => {
return await projectForm(projectName, [packageManager, template, directory]);
},
};
};

export { createProject };

107 changes: 77 additions & 30 deletions src/new/form.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import inquirer from "inquirer";
import chalk from "chalk";
import degit from "degit";
import { spawn, execSync } from "child_process";
import { execSync, spawn } from "child_process";
import { Presets, SingleBar } from "cli-progress";
import degit from "degit";
import inquirer from "inquirer";
import fs from "node:fs";
import path from "node:path";
import { centerText } from "../utils/center-text";
import { printError } from "../utils/cli-ui";

async function packageManagerInstall({
packageManager,
Expand Down Expand Up @@ -35,7 +37,7 @@ async function packageManagerInstall({
if (code === 0) {
resolve("Installation Done!");
} else {
reject(new Error(`npm install exited with code ${code}`));
reject(new Error(`${packageManager} install exited with code ${code}`));
}
});
});
Expand All @@ -45,12 +47,12 @@ async function checkIfPackageManagerExists(packageManager: string) {
try {
execSync(`${packageManager} --version`);
return true;
} catch (_) {
throw new Error(`Package manager ${packageManager} is not installed`);
} catch (error) {
printError("Package manager not found!", packageManager);
process.exit(1);
}
}

// Change the package.json name to the user's project name
function changePackageName({
directory,
name,
Expand Down Expand Up @@ -79,13 +81,31 @@ enum Template {
}

const enum PackageManager {
npm,
yarn,
pnpm,
npm = "npm",
yarn = "yarn",
pnpm = "pnpm"
}

const projectForm = async (projectName: string, packageManager: PackageManager, template: keyof typeof Template): Promise<void> => {
const projectForm = async (projectName: string, args: any[]): Promise<void> => {
let answer: any;
const projName: string = projectName;
let packageManager: PackageManager | undefined;
let template: keyof typeof Template | undefined;
let directory: string | undefined;

// Resolving the argument order problem
for (const arg of args) {
if (args.length >= 3) {
if (arg === "npm" || arg === "yarn" || arg === "pnpm") {
packageManager = arg as PackageManager;
} else if (arg === "non-opinionated" || arg === "opinionated") {
template = arg as keyof typeof Template;
} else {
directory = arg;
}
}
}

if (packageManager && template) {
answer = {
name: projectName,
Expand All @@ -94,7 +114,6 @@ const projectForm = async (projectName: string, packageManager: PackageManager,
confirm: true,
};
} else {

answer = await inquirer.prompt([
{
type: "input",
Expand Down Expand Up @@ -128,19 +147,25 @@ const projectForm = async (projectName: string, packageManager: PackageManager,
},
]);
}

if (directory) {
if(!fs.existsSync(path.join(directory, answer.name))) {
answer.name = path.join(directory, answer.name);
} else {
printError("Directory already exists", directory);
process.exit(1);
}
}

// Hashmap of templates and their directories
const templates: Record<string, unknown> = {
"Non-Opinionated": "non_opinionated",
Opinionated: "opinionated",
};

if (answer.confirm) {
// Check if the package manager exists
await checkIfPackageManagerExists(answer.packageManager).catch((err) => {
console.log(chalk.red(err.message));
process.exit(1);
});

await checkIfPackageManagerExists(answer.packageManager);
console.log("\n");
const progressBar = new SingleBar(
{
format:
Expand All @@ -156,38 +181,60 @@ const projectForm = async (projectName: string, packageManager: PackageManager,

const [_, template] = answer.template.match(/(.*) ::/) as Array<string>;

const emitter = degit(
`expressots/expressots/templates/${templates[template]}`,
);

await emitter.clone(answer.name);

try {
const emitter = degit(
`expressots/expressots/templates/${templates[template]}`,
);

await emitter.clone(answer.name);
} catch (err: any) {
printError("Project already exists or Folder is not empty", answer.name);
process.exit(1);
}

progressBar.update(50, {
doing: "Installing dependencies",
});

// Run the package manager install in the directory
await packageManagerInstall({
packageManager: answer.packageManager,
directory: answer.name,
progressBar,
});


progressBar.update(90);

changePackageName({
directory: answer.name,
name: answer.name,
name: projName,
});

progressBar.update(100);

progressBar.stop();

console.log(chalk.green("Project created successfully!"));
console.log("Run the following commands to start the project:");
console.log(chalk.bold(`cd ${answer.name}`));
console.log(chalk.bold(`${answer.packageManager} start`));
console.log("\n");
console.log("🐎 Project ",chalk.green(projName), "created successfully!");
console.log("🤙 Run the following commands to start the project:\n");

console.log(chalk.bold.gray(`$ cd ${answer.name}`));
switch (answer.packageManager) {
case "npm":
console.log(chalk.bold.gray("$ npm run dev"));
break;
case "yarn":
console.log(chalk.bold.gray("$ yarn dev"));
break;
case "pnpm":
console.log(chalk.bold.gray("$ pnpm run dev"));
break;
}

console.log("\n");
console.log(chalk.bold.green(centerText("Happy coding!")));
console.log(chalk.bold.gray(centerText("Please consider donating to support the project.\n")));
console.log(chalk.bold.white(centerText("💖 Sponsor: https://github.com/sponsors/expressots")));
}
};

Expand Down
9 changes: 9 additions & 0 deletions src/utils/center-text.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
function centerText(text: string): string {
const terminalWidth = process.stdout.columns;
const padding = Math.floor((terminalWidth - text.length) / 2);
const centeredText = ' '.repeat(padding) + text;

return centeredText;
}

export { centerText };
5 changes: 5 additions & 0 deletions src/utils/cli-ui.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import chalk from "chalk";

export function printError(message: string, component: string): void {
console.error(chalk.red(`\n\n😞 ${message}:`,chalk.white(`[${component}]`)));
}
16 changes: 8 additions & 8 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{
"compilerOptions": {
"moduleDetection": "force",
"module": "commonjs",
"declaration": true,
"esModuleInterop": true,
"moduleDetection": "force",
"module": "commonjs",
"declaration": true,
"esModuleInterop": true,
"moduleResolution": "node",
"resolveJsonModule": false,
"target": "ES2017",
"lib": ["ES2017"],
"outDir": "bin",
"resolveJsonModule": false,
"target": "ES2017",
"lib": ["ES2017"],
"outDir": "bin",
"sourceMap": false,
"strictNullChecks": false,
"strict": true,
Expand Down

0 comments on commit acb5d3f

Please sign in to comment.