@leo-h/create-nodejs-app 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -27,8 +27,27 @@ With PNPM:
27
27
  pnpm create @leo-h/nodejs-app
28
28
  ```
29
29
 
30
+ ## CLI
31
+
32
+ You can create the project non-interactively through the CLI. Arguments or options not provided will still be asked if it is mandatory information for creation.
33
+
34
+ ```bash
35
+ Usage: @leo-h/create-nodejs-app [options] [project-directory]
36
+
37
+ Arguments:
38
+ project-directory Name of the project or relative path of the project considering where the script was called.
39
+
40
+ Options:
41
+ -pm, --package-manager <package-manager> Package manager that will be used in the project.
42
+ -t, --template <template-name> Template that will be used in the project.
43
+ -f, --framework <framework-name> Framework that will be used in the project.
44
+ -h, --help display help for command
45
+ ```
46
+
30
47
  ## Features
31
48
 
32
49
  - **Fast**: All templates use [tsx](https://tsx.is/) to run Node.js with TypeScript and [unbuild](https://github.com/unjs/unbuild) to build the application. Both tools use [esbuild](https://esbuild.github.io/), an extremely fast packager also used by tools like [Vite](https://vitejs.dev/).
33
50
 
34
51
  - **Lint and code format**: All templates use [eslint](https://eslint.org/) to identify problems in the code and [prettier](https://prettier.io/) to ensure consistent code formatting. Both are integrated with [husky](https://typicode.github.io/husky/) and [lint-staged](https://github.com/lint-staged/lint-staged) to automatically run them before every commit you make with git.
52
+
53
+ - **Tests**: To encourage the use of tests, all templates already have a pre-configured unit testing setup with [Vitest](https://vitest.dev/). The API templates also come with end-to-end usage example tests, including all utility tools like [Supertest](https://github.com/ladjs/supertest#readme) and [Faker.js](https://fakerjs.dev/).
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- const index=require("./config/index.js"),logs=require("./utils/logs.js"),onCancel=require("./utils/on-cancel.js"),fs=require("fs"),promises=require("fs/promises"),path=require("path"),picocolors=require("picocolors"),y=require("prompts"),copyTemplate_compose=require("./compose-app/copy-template.compose.js"),replaceContentInFile_compose=require("./compose-app/replace-content-in-file.compose.js"),packageName_validation=require("./validations/package-name.validation.js"),d=e=>(a,t)=>t.template==="api"?e:null,v=[{type:"text",name:"projectName",message:"What is your project name?",validate:async e=>(await promises.readdir(index.GENERATED_APP_ROOT_PATH)).includes(e)?"Already exists a folder with same name in current directory.":packageName_validation.packageNameValidation(e)},{type:"select",name:"packageManager",message:"What is your favorite package manager?",choices:[{title:"NPM",value:"npm"},{title:"Yarn",value:"yarn"},{title:"PNPM",value:"pnpm"}],initial:2},{type:"select",name:"template",message:"Select your template:",choices:[{title:"Clean",value:"clean"},{title:"API",value:"api"}]},{type:d("select"),name:"framework",message:"What is your favorite framework:",choices:[{title:"Fastify",value:"fastify"},{title:"Nest",value:"nest",disabled:!0},{title:"tRPC",value:"trpc",disabled:!0}],initial:0}];(async()=>{process.env.NODE_ENV==="development"&&!fs.existsSync(index.GENERATED_APP_ROOT_PATH)&&await promises.mkdir(index.GENERATED_APP_ROOT_PATH,{recursive:!0});const e=await y(v,{onCancel:onCancel.onCancelPrompt}),a=path.resolve(index.GENERATED_APP_ROOT_PATH,e.projectName);await copyTemplate_compose.copyTemplateCompose(a,e.framework?e.framework:"clean");const t=path.resolve(a,"package.json"),o=path.resolve(a,".env.example"),s=path.resolve(a,".husky/pre-commit");await Promise.all([replaceContentInFile_compose.replaceContentInFileCompose(t,[["app-name",e.projectName]]),replaceContentInFile_compose.replaceContentInFileCompose(o,[["app-name",e.projectName]]),replaceContentInFile_compose.replaceContentInFileCompose(s,[["pnpm",e.packageManager]])]),logs.successLog(`Success in creating new app ${picocolors.cyan(e.projectName)}!`,`> ${a}`)})();
2
+ const index=require("./src/config/index.js"),commander=require("commander"),path=require("path"),picocolors=require("picocolors"),C=require("prompts"),_package=require("./package.json.js"),copyTemplate_compose=require("./src/compose-app/copy-template.compose.js"),replaceContentInFile_compose=require("./src/compose-app/replace-content-in-file.compose.js"),logs=require("./src/utils/logs.js"),onCancel=require("./src/utils/on-cancel.js"),framework_validation=require("./src/validations/framework.validation.js"),packageManager_validation=require("./src/validations/package-manager.validation.js"),projectNameValidation=require("./src/validations/project-name-validation.js"),template_validation=require("./src/validations/template.validation.js"),g=new commander.Command().name(_package.default.name).description(_package.default.description);g.argument("[project-directory]","Name of the project or relative path of the project considering where the script was called.").option("-pm, --package-manager <package-manager>","Package manager that will be used in the project.").option("-t, --template <template-name>","Template that will be used in the project.").option("-f, --framework <framework-name>","Framework that will be used in the project.").action(async(i,o)=>{const r={projectDirectory:{value:i,validation:projectNameValidation.ProjectNameValidation.create({path:i})},packageManager:{value:o.packageManager,validation:packageManager_validation.PackageManagerValidation.create({packageManager:o.packageManager})},template:{value:o.template,validation:template_validation.TemplateValidation.create({template:o.template})},framework:{value:o.framework,validation:framework_validation.FrameworkValidation.create({framework:o.framework})}},l=Object.keys(r);for(const a of l){const t=r[a];t.value&&await t.validation.fromCli()}const{projectDirectory:e,packageManager:s,template:p,framework:n}=r,m=await C([{type:e.value?null:"text",name:"projectDirectory",message:"What is your project name?",validate:async a=>await e.validation.fromPrompt({path:a})},{type:s.value?null:"select",name:"packageManager",message:"What is your favorite package manager?",choices:packageManager_validation.packageManagers,initial:2},{type:!p.value&&!n.value?"select":null,name:"template",message:"Select your template:",choices:template_validation.templates},{type:(a,t)=>!n.value&&(p.value==="api"||t.template==="api")?"select":null,name:"framework",message:"What is your favorite framework?",choices:framework_validation.frameworks}],{onCancel:onCancel.onCancelPrompt});for(const a of l){const t=m[a];t&&(r[a].value=t)}n.value&&(r.template.value="api");const c=path.basename(e.value);e.value=path.resolve(index.GENERATED_APP_TARGET_ROOT_PATH,e.value),await copyTemplate_compose.copyTemplateCompose(e.value,n.value??"clean");const u=path.resolve(e.value,"package.json"),v=path.resolve(e.value,".env.example"),d=path.resolve(e.value,".husky/pre-commit");await Promise.all([replaceContentInFile_compose.replaceContentInFileCompose(u,[["app-name",c]]),replaceContentInFile_compose.replaceContentInFileCompose(v,[["app-name",c]]),replaceContentInFile_compose.replaceContentInFileCompose(d,[["pnpm",s.value]])]),logs.successLog(`Success in creating new app ${picocolors.cyan(c)}!`,`> ${e.value}`)});(async()=>await g.parseAsync(process.argv))();
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,"__esModule",{value:!0});const name="@leo-h/create-nodejs-app",version="1.0.3",packageManager="pnpm@9.1.1",author="Leonardo Henrique <leonardo0507.business@gmail.com>",description="Create a modern Node.js app with TypeScript using one command.",license="MIT",keywords=["node","node.js","typescript"],bin={"create-nodejs-app":"./dist/index.js"},files=["./dist","./templates"],repository={type:"git",url:"https://github.com/Leo-Henrique/create-nodejs-app"},scripts={prepare:"husky",start:"node ./dist/index.js","start:dev":"tsx ./src/index.ts","start:dev:watch":"tsx watch ./src/index.ts",typecheck:"tsc --noEmit",lint:"eslint . --ext .ts --max-warnings 0 --cache","lint:fix":"pnpm lint --fix",format:"prettier . --write --cache","test:unit":"vitest run","test:unit:watch":"vitest","test:e2e":"vitest run --config ./vitest.config.e2e.mts","test:e2e:watch":"vitest --config ./vitest.config.e2e.mts","test:coverage":"vitest run --coverage.enabled=true --coverage.all=false",template:"tsx ./scripts/template-cli.ts",prebuild:"rimraf ./dist",build:"unbuild",prepublishOnly:"pnpm build"},dependencies={commander:"12.1.0",picocolors:"1.0.1",prompts:"2.4.2","validate-npm-package-name":"5.0.1",zod:"3.23.8"},devDependencies={"@faker-js/faker":"8.4.1","@types/node":"20.12.12","@types/prompts":"2.4.9","@types/validate-npm-package-name":"4.0.2","@typescript-eslint/eslint-plugin":"7.10.0","@typescript-eslint/parser":"7.10.0","conventional-changelog-conventionalcommits":"8.0.0",eslint:"8.57.0","eslint-config-prettier":"9.1.0","eslint-plugin-vitest":"0.4.0",husky:"9.0.11","lint-staged":"15.2.2","npm-run-all":"4.1.5",prettier:"3.2.5",rimraf:"5.0.7",tsx:"4.10.5",typescript:"5.4.5",unbuild:"2.0.0","vite-tsconfig-paths":"4.3.2",vitest:"1.6.0"},f={name,version,packageManager,author,description,license,keywords,bin,files,repository,scripts,dependencies,devDependencies};exports.author=author;exports.bin=bin;exports.default=f;exports.dependencies=dependencies;exports.description=description;exports.devDependencies=devDependencies;exports.files=files;exports.keywords=keywords;exports.license=license;exports.name=name;exports.packageManager=packageManager;exports.repository=repository;exports.scripts=scripts;exports.version=version;
@@ -0,0 +1 @@
1
+ const fs=require("fs"),path=require("path");process.env.NODE_ENV||(process.argv[1].endsWith(".js")?process.env.NODE_ENV="production":process.env.NODE_ENV="development");const n=(e=__dirname)=>fs.readdirSync(e,{encoding:"utf-8"}).includes("package.json")?e:n(path.resolve(e,"..")),o=n(),GENERATED_APP_TARGET_ROOT_PATH=process.env.NODE_ENV==="production"?process.cwd():path.resolve(o,"generated-apps");process.env.NODE_ENV!=="production"&&!fs.existsSync(GENERATED_APP_TARGET_ROOT_PATH)&&fs.mkdirSync(GENERATED_APP_TARGET_ROOT_PATH);const TEMPLATES_PATH=path.resolve(o,"templates");path.resolve(TEMPLATES_PATH,"clean");exports.GENERATED_APP_TARGET_ROOT_PATH=GENERATED_APP_TARGET_ROOT_PATH;exports.TEMPLATES_PATH=TEMPLATES_PATH;
@@ -0,0 +1 @@
1
+ const left=require("../utils/left.js");var r=Object.defineProperty,e=(a,t,i)=>t in a?r(a,t,{enumerable:!0,configurable:!0,writable:!0,value:i}):a[t]=i,s=(a,t,i)=>(e(a,typeof t!="symbol"?t+"":t,i),i);class Validation{constructor(){s(this,"params")}createValidation(t){return t&&(this.params=t),this}async fromCli(t){t&&(this.params=t);const i=await this.validate();if(!i.isValid)return left.left(i.issue)}async fromPrompt(t){t&&(this.params=t);const i=await this.validate();return i.isValid?!0:i.issue}}exports.Validation=Validation;
@@ -0,0 +1 @@
1
+ function capitalizeFirstWord(i){return i[0].toUpperCase()+i.slice(1)}exports.capitalizeFirstWord=capitalizeFirstWord;
@@ -0,0 +1 @@
1
+ function getValidOptionsFromSelect(t){return t.filter(e=>!e.disabled).map(({value:e})=>e)}exports.getValidOptionsFromSelect=getValidOptionsFromSelect;
@@ -0,0 +1 @@
1
+ const logs=require("./logs.js");function left(e){logs.errorLog(e),process.exit(1)}exports.left=left;
@@ -0,0 +1 @@
1
+ const validation=require("../core/validation.js"),getValidOptionsFromPrompt=require("../utils/get-valid-options-from-prompt.js"),frameworks=[{title:"Fastify",value:"fastify"},{title:"Nest",value:"nest",disabled:!0},{title:"tRPC",value:"trpc",disabled:!0}],validFrameworks=getValidOptionsFromPrompt.getValidOptionsFromSelect(frameworks);class FrameworkValidation extends validation.Validation{static create(e){return new this().createValidation(e)}async validate(){let{framework:e}=this.params;return e=e.toLowerCase(),validFrameworks.includes(e)?{isValid:!0}:{isValid:!1,issue:`Invalid framework, consider the options: ${validFrameworks.map(t=>`"${t}"`).join(", ")}.`}}}exports.FrameworkValidation=FrameworkValidation;exports.frameworks=frameworks;exports.validFrameworks=validFrameworks;
@@ -0,0 +1 @@
1
+ const validation=require("../core/validation.js"),getValidOptionsFromPrompt=require("../utils/get-valid-options-from-prompt.js"),packageManagers=[{title:"NPM",value:"npm"},{title:"Yarn",value:"yarn"},{title:"PNPM",value:"pnpm"}],validPackageManagers=getValidOptionsFromPrompt.getValidOptionsFromSelect(packageManagers);class PackageManagerValidation extends validation.Validation{static create(a){return new this().createValidation(a)}async validate(){let{packageManager:a}=this.params;return a=a.toLowerCase(),validPackageManagers.includes(a)?{isValid:!0}:{isValid:!1,issue:`Invalid package manager, consider the options: ${validPackageManagers.map(e=>`"${e}"`).join(", ")}.`}}}exports.PackageManagerValidation=PackageManagerValidation;exports.packageManagers=packageManagers;exports.validPackageManagers=validPackageManagers;
@@ -0,0 +1 @@
1
+ const index=require("../config/index.js"),validation=require("../core/validation.js"),capitalizeFirstWord=require("../utils/capitalize-first-word.js"),fs=require("fs"),promises=require("fs/promises"),path=require("path"),c=require("validate-npm-package-name");class ProjectNameValidation extends validation.Validation{static create(e){return new this().createValidation(e)}async validate(){const{path:e}=this.params,i=path.basename(e),a=path.resolve(index.GENERATED_APP_TARGET_ROOT_PATH,e);if(fs.existsSync(a)&&(await promises.readdir(a)).includes(i))return{isValid:!1,issue:`Folder with name "${i}" already exists in project directory.`};const s=c(i);return s.validForNewPackages?{isValid:!0}:{isValid:!1,issue:capitalizeFirstWord.capitalizeFirstWord(s.errors[0])}}}exports.ProjectNameValidation=ProjectNameValidation;
@@ -0,0 +1 @@
1
+ const validation=require("../core/validation.js"),getValidOptionsFromPrompt=require("../utils/get-valid-options-from-prompt.js"),templates=[{title:"Clean",value:"clean"},{title:"API",value:"api"}],validTemplates=getValidOptionsFromPrompt.getValidOptionsFromSelect(templates);class TemplateValidation extends validation.Validation{static create(e){return new this().createValidation(e)}async validate(){let{template:e}=this.params;return e=e.toLowerCase(),validTemplates.includes(e)?{isValid:!0}:{isValid:!1,issue:`Invalid template, consider the options: ${validTemplates.map(t=>`"${t}"`).join(", ")}.`}}}exports.TemplateValidation=TemplateValidation;exports.templates=templates;exports.validTemplates=validTemplates;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leo-h/create-nodejs-app",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "packageManager": "pnpm@9.1.1",
5
5
  "author": "Leonardo Henrique <leonardo0507.business@gmail.com>",
6
6
  "description": "Create a modern Node.js app with TypeScript using one command.",
@@ -32,9 +32,10 @@
32
32
  "format": "prettier . --write --cache",
33
33
  "test:unit": "vitest run",
34
34
  "test:unit:watch": "vitest",
35
+ "test:e2e": "vitest run --config ./vitest.config.e2e.mts",
36
+ "test:e2e:watch": "vitest --config ./vitest.config.e2e.mts",
35
37
  "test:coverage": "vitest run --coverage.enabled=true --coverage.all=false",
36
- "template:create": "tsx ./scripts/create-template.script.ts",
37
- "template:open": "tsx ./scripts/open-template.script.ts",
38
+ "template": "tsx ./scripts/template-cli.ts",
38
39
  "prebuild": "rimraf ./dist",
39
40
  "build": "unbuild",
40
41
  "prepublishOnly": "pnpm build"
@@ -1 +0,0 @@
1
- const path=require("path"),t=path.basename(path.resolve(__dirname,".."));t==="dist"?process.env.NODE_ENV="production":process.env.NODE_ENV="development";const o=path.resolve(__dirname,"../.."),GENERATED_APP_ROOT_PATH=process.env.NODE_ENV==="production"?process.cwd():path.resolve(o,"generated-apps"),TEMPLATES_PATH=path.resolve(o,"templates");path.resolve(TEMPLATES_PATH,"clean");exports.GENERATED_APP_ROOT_PATH=GENERATED_APP_ROOT_PATH;exports.TEMPLATES_PATH=TEMPLATES_PATH;
@@ -1 +0,0 @@
1
- function toPascalCase(a){return a[0].toUpperCase()+a.slice(1)}exports.toPascalCase=toPascalCase;
@@ -1 +0,0 @@
1
- const toPascalCase=require("../utils/to-pascal-case.js"),e=require("validate-npm-package-name");function packageNameValidation(s){const a=e(s);return a.validForNewPackages?!0:toPascalCase.toPascalCase(a.errors[0])}exports.packageNameValidation=packageNameValidation;
File without changes
File without changes