sprygen 1.0.3 → 1.0.5
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 +8 -0
- package/dist/cli.js +19 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -69,6 +69,14 @@ Useful for modifying existing projects. Scaffolds Sprygen's robust JWT authentic
|
|
|
69
69
|
sprygen generate-auth
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
+
### 4. Update Sprygen
|
|
73
|
+
|
|
74
|
+
Ensure you are always running the latest version with the newest features and bug fixes by using the built-in update command.
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
sprygen update
|
|
78
|
+
```
|
|
79
|
+
|
|
72
80
|
## 🏗️ Project Architecture
|
|
73
81
|
|
|
74
82
|
Applications generated by Sprygen follow standard Spring Boot best practices:
|
package/dist/cli.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
"use strict";var
|
|
3
|
-
`):process.stdout.write(
|
|
4
|
-
`)}),console.log();let
|
|
5
|
-
`),
|
|
6
|
-
|
|
2
|
+
"use strict";var ue=Object.create;var z=Object.defineProperty;var je=Object.getOwnPropertyDescriptor;var fe=Object.getOwnPropertyNames;var he=Object.getPrototypeOf,ye=Object.prototype.hasOwnProperty;var _=(t,e)=>()=>(t&&(e=t(t=0)),e);var ve=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports),we=(t,e)=>{for(var a in e)z(t,a,{get:e[a],enumerable:!0})},be=(t,e,a,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of fe(e))!ye.call(t,o)&&o!==a&&z(t,o,{get:()=>e[o],enumerable:!(r=je(e,o))||r.enumerable});return t};var j=(t,e,a)=>(a=t!=null?ue(he(t)):{},be(e||!t||!t.__esModule?z(a,"default",{value:t,enumerable:!0}):a,t));var B=ve((Ne,Se)=>{Se.exports={name:"sprygen",version:"1.0.5",description:"A production-ready Spring Boot project generator CLI. Scaffold secure, structured Java applications with built-in JWT or session authentication, role-based access control, user management, and an optional fullstack frontend \u2014 all from a single interactive command.",main:"dist/cli.js",bin:{sprygen:"./dist/cli.js"},files:["dist","templates"],scripts:{build:"tsup src/cli.ts --format cjs --minify --out-dir dist",dev:"ts-node src/cli.ts",lint:"tsc --noEmit",prepublishOnly:"npm run build"},dependencies:{"adm-zip":"^0.5.17",axios:"^1.14.0",chalk:"4.1.2",commander:"^11.1.0",ejs:"^3.1.10","fs-extra":"^11.2.0",inquirer:"^8.2.6",ora:"5.4.1"},devDependencies:{"@types/adm-zip":"^0.5.8","@types/ejs":"^3.1.5","@types/fs-extra":"^11.0.4","@types/inquirer":"^8.2.10","@types/node":"^20.11.0","ts-node":"^10.9.2",tsup:"^8.0.2",typescript:"^5.3.3"},keywords:["spring-boot","generator","scaffold","cli","java","jwt","authentication","spring-security","fullstack","starter-kit"],author:"Sprygen",license:"MIT",repository:{type:"git",url:"git+https://github.com/wadecalvin9/Sprygen.git"},bugs:{url:"https://github.com/wadecalvin9/Sprygen/issues"},homepage:"https://github.com/wadecalvin9/Sprygen#readme",engines:{node:">=18.0.0"}}});function T(){console.log();let{version:t,repository:e}=B();xe.forEach((n,i)=>{i<2?process.stdout.write(m.default.hex(w.soft)(n)+`
|
|
3
|
+
`):process.stdout.write(m.default.hex(w.bright)(n)+`
|
|
4
|
+
`)}),console.log();let a=" Spring Boot Project Generator",r=`v${t}`;process.stdout.write(m.default.hex(w.base)(a)+m.default.dim(" \xB7 ")+m.default.dim(r)+`
|
|
5
|
+
`);let o=e?.url?.replace("git+","").replace(".git","")||"https://github.com/wadecalvin9/Sprygen";process.stdout.write(m.default.dim(` ${o}
|
|
6
|
+
`)),console.log(m.default.hex(w.soft)(" "+"\u2500".repeat(62))),console.log()}function x(t){console.log(`
|
|
7
|
+
`+m.default.hex(w.bright)(" \u25C6 ")+m.default.bold.white(t))}function Pe(){console.log(m.default.hex(w.soft)(" "+"\u2500".repeat(62)))}var m,w,xe,l,S=_(()=>{"use strict";m=j(require("chalk")),w={bright:"#3fb950",base:"#2da44e",dim:"#26913d",soft:"#156e2e"},xe=[" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557"," \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2588\u2588\u2557 \u2588\u2588\u2554\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551"," \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u255A\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551"," \u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u255A\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551"," \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551"," \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D"];l={info:t=>console.log(` ${m.default.hex(w.base)("\xB7")} ${m.default.white(t)}`),success:t=>console.log(` ${m.default.hex(w.bright)("+")} ${m.default.white(t)}`),warn:t=>console.log(` ${m.default.yellow("!")} ${m.default.yellow(t)}`),error:t=>console.error(` ${m.default.red("\xD7")} ${m.default.red(t)}`),title:t=>{console.log(),console.log(m.default.hex(w.bright)(" \u25C6 ")+m.default.bold.white(t)),console.log()},file:t=>console.log(` ${m.default.hex(w.soft)("write")} ${m.default.dim(t)}`),step:(t,e,a)=>console.log(m.default.hex(w.soft)(` [${t}/${e}]`)+" "+m.default.white(a)),done:()=>{console.log(),Pe(),console.log(` ${m.default.hex(w.bright)("+")} ${m.default.bold.white("Done.")}`),console.log()}}});var F={};we(F,{copyTemplate:()=>H,renderTemplate:()=>Z,writeGeneratedFile:()=>y});async function Z(t,e){let a=await P.default.readFile(t,"utf-8");return V.default.render(a,e,{async:!1})}async function y(t,e,a){let r=await Z(e,a);await P.default.ensureDir(E.default.dirname(t)),await P.default.writeFile(t,r,"utf-8"),l.file(t)}async function H(t,e,a){let r=await P.default.readdir(t,{withFileTypes:!0});for(let o of r){let n=E.default.join(t,o.name),i=De(o.name,a),c=i.endsWith(".ejs")?i.slice(0,-4):i,p=E.default.join(e,c);o.isDirectory()?(await P.default.ensureDir(p),await H(n,p,a)):o.name.endsWith(".ejs")?await y(p,n,a):(await P.default.copy(n,p),l.file(p))}}function De(t,e){return t.replace(/\{\{(\w+)\}\}/g,(a,r)=>String(e[r]??`{{${r}}}`))}var V,P,E,C=_(()=>{"use strict";V=j(require("ejs")),P=j(require("fs-extra")),E=j(require("path"));S()});var ge=require("commander");var R=j(require("inquirer"));var s=j(require("path")),u=j(require("fs-extra")),Y=j(require("ora")),Q=j(require("axios")),K=j(require("adm-zip"));S();var I=class{templatesDir;constructor(){let e=[s.default.resolve(__dirname,"../../templates/project"),s.default.resolve(__dirname,"../templates/project")],a=e.find(r=>u.default.existsSync(r));if(!a)throw new Error(`Could not locate templates directory. Tried:
|
|
7
8
|
${e.join(`
|
|
8
|
-
`)}`);this.templatesDir=a}async generate(e){let a=s.default.resolve(process.cwd(),e.projectName);if(await u.default.pathExists(a))throw new Error(`Directory "${e.projectName}" already exists. Please choose a different project name or remove the existing directory.`);let r=e.projectName.toLowerCase().replace(/[^a-z0-9-]/g,"-"),
|
|
9
|
+
`)}`);this.templatesDir=a}async generate(e){let a=s.default.resolve(process.cwd(),e.projectName);if(await u.default.pathExists(a))throw new Error(`Directory "${e.projectName}" already exists. Please choose a different project name or remove the existing directory.`);let r=e.projectName.toLowerCase().replace(/[^a-z0-9-]/g,"-"),o=e.packageName.split(".").slice(0,-1).join(".")||e.packageName;l.title(`Generating Spring Boot project: ${e.projectName}`);let n=(0,Y.default)({text:"Downloading from Spring Initializr\u2026",color:"cyan"}).start();try{let i="data-jpa,security,validation,lombok";i+=",web",e.database==="mysql"&&(i+=",mysql"),e.database==="postgresql"&&(i+=",postgresql"),e.database==="h2"&&(i+=",h2"),e.modules.includes("Mail")&&(i+=",mail");let c=e.authStrategy==="session"&&e.projectType==="fullstack";c&&(i+=",thymeleaf");let p=e.buildTool==="maven"?"maven-project":"gradle-project",d=new URL("https://start.spring.io/starter.zip");d.searchParams.append("type",p),d.searchParams.append("language","java"),d.searchParams.append("baseDir",e.projectName),d.searchParams.append("groupId",o),d.searchParams.append("artifactId",r),d.searchParams.append("name",e.projectName),d.searchParams.append("description",e.description),d.searchParams.append("packageName",e.packageName),d.searchParams.append("packaging","jar"),d.searchParams.append("javaVersion",e.javaVersion),d.searchParams.append("dependencies",i);let g=await(0,Q.default)({url:d.toString(),method:"GET",responseType:"arraybuffer"});n.text="Extracting project scaffold\u2026";let h=s.default.resolve(process.cwd(),`${e.projectName}-tmp.zip`);await u.default.writeFile(h,g.data),new K.default(h).extractAllTo(process.cwd(),!0),await u.default.unlink(h),n.text="Patching dependencies\u2026",await this.patchDependencies(a,e,c),n.text="Writing source files\u2026";let $=this.buildContext(e,a,r,o),k=s.default.join(a,"src/main/java",e.packagePath),N=s.default.join(a,"src/main/resources");await u.default.ensureDir(k),await u.default.ensureDir(N);let M=s.default.join(N,"application.properties");await u.default.pathExists(M)&&await u.default.unlink(M),await this.generateJavaSources(k,$,e),await this.generateResources(N,$,e),e.projectType==="fullstack"&&(n.text="Writing frontend files\u2026",await this.generateFrontend(a,$,e,c)),n.succeed(`Project "${e.projectName}" scaffolded successfully!`)}catch(i){throw n.fail("Project generation failed."),i}this.printSuccessMessage(e)}async patchDependencies(e,a,r){let o=a.modules.includes("Swagger");if(a.buildTool==="maven"){let n=s.default.join(e,"pom.xml"),i=await u.default.readFile(n,"utf8"),c=a.authStrategy==="jwt"?`
|
|
9
10
|
<!-- JWT Dependencies -->
|
|
10
11
|
<dependency>
|
|
11
12
|
<groupId>io.jsonwebtoken</groupId>
|
|
@@ -23,33 +24,33 @@ ${e.join(`
|
|
|
23
24
|
<artifactId>jjwt-jackson</artifactId>
|
|
24
25
|
<version>0.11.5</version>
|
|
25
26
|
<scope>runtime</scope>
|
|
26
|
-
</dependency>`:"",
|
|
27
|
+
</dependency>`:"",p=o?`
|
|
27
28
|
<!-- Swagger/OpenAPI -->
|
|
28
29
|
<dependency>
|
|
29
30
|
<groupId>org.springdoc</groupId>
|
|
30
31
|
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
|
31
32
|
<version>2.3.0</version>
|
|
32
|
-
</dependency>`:"",
|
|
33
|
+
</dependency>`:"",d=r?`
|
|
33
34
|
<!-- Thymeleaf Spring Security Extras -->
|
|
34
35
|
<dependency>
|
|
35
36
|
<groupId>org.thymeleaf.extras</groupId>
|
|
36
37
|
<artifactId>thymeleaf-extras-springsecurity6</artifactId>
|
|
37
|
-
</dependency>`:"";
|
|
38
|
-
</dependencies>`),await u.default.writeFile(n,
|
|
38
|
+
</dependency>`:"";i=i.replace("</dependencies>",`${c}${p}${d}
|
|
39
|
+
</dependencies>`),await u.default.writeFile(n,i,"utf8")}else{let n=s.default.join(e,"build.gradle"),i=o?`
|
|
39
40
|
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0'`:"",c=r?`
|
|
40
|
-
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'`:"",
|
|
41
|
+
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'`:"",d=`
|
|
41
42
|
dependencies {${a.authStrategy==="jwt"?`
|
|
42
43
|
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
|
|
43
44
|
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
|
|
44
|
-
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'`:""}${
|
|
45
|
+
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'`:""}${i}${c}
|
|
45
46
|
}
|
|
46
|
-
`;await u.default.appendFile(n,
|
|
47
|
+
`;await u.default.appendFile(n,d,"utf8")}}buildContext(e,a,r,o){let n=this.toPascalCase(e.projectName.replace(/[^a-zA-Z0-9]/g,""))+"Application",i=this.getDbDependencies(e.database),c=this.getDbConfig(e.database,r);return{projectName:e.projectName,projectNamePascal:n.replace("Application",""),artifactId:r,groupId:o,packageName:e.packageName,packagePath:e.packagePath,database:e.database,buildTool:e.buildTool,mainClassName:n,javaVersion:e.javaVersion,springBootVersion:e.springBootVersion,description:e.description,hasSwagger:e.modules.includes("Swagger"),hasMail:e.modules.includes("Mail"),hasLogging:e.modules.includes("Logging"),isJwtAuth:e.authStrategy==="jwt",isSessionAuth:e.authStrategy==="session",isFullstack:e.projectType==="fullstack",isApiOnly:e.projectType==="api",dbDependency:i.dependency,dbDriverClass:i.driverClass,dbConfig:c,outputDir:a,modules:e.modules,year:new Date().getFullYear()}}async generateJavaSources(e,a,r){let{writeGeneratedFile:o}=await Promise.resolve().then(()=>(C(),F)),n=s.default.join(this.templatesDir,"java"),i=a,c=[["entity/User.java","entity/User.java.ejs"],["entity/Role.java","entity/Role.java.ejs"],["repository/UserRepository.java","repository/UserRepository.java.ejs"],["service/UserService.java","service/UserService.java.ejs"],["controller/AuthController.java","controller/AuthController.java.ejs"],["controller/UserController.java","controller/UserController.java.ejs"],["controller/HomeController.java","controller/HomeController.java.ejs"],["controller/ProfileController.java","controller/ProfileController.java.ejs"],["controller/AdminController.java","controller/AdminController.java.ejs"],["config/CorsConfig.java","config/CorsConfig.java.ejs"],["dto/AuthRequest.java","dto/AuthRequest.java.ejs"],["dto/AuthResponse.java","dto/AuthResponse.java.ejs"],["dto/RegisterRequest.java","dto/RegisterRequest.java.ejs"],["dto/ProfileUpdateRequest.java","dto/ProfileUpdateRequest.java.ejs"],["dto/UserDto.java","dto/UserDto.java.ejs"]];r.authStrategy==="jwt"?c.push(["service/JwtService.java","service/JwtService.java.ejs"],["security/JwtAuthFilter.java","security/JwtAuthFilter.java.ejs"],["security/UserDetailsServiceImpl.java","security/UserDetailsServiceImpl.java.ejs"],["config/SecurityConfig.java","config/SecurityConfig.java.ejs"]):c.push(["security/UserDetailsServiceImpl.java","security/UserDetailsServiceImpl.java.ejs"],["config/SecurityConfig.java","config/SecurityConfigSession.java.ejs"]),r.modules.includes("Swagger")&&c.push(["config/SwaggerConfig.java","config/SwaggerConfig.java.ejs"]);for(let[p,d]of c){let g=s.default.join(n,d);await u.default.pathExists(g)?await o(s.default.join(e,p),g,i):l.warn(`Template not found, skipping: ${d}`)}}async generateResources(e,a,r){let{writeGeneratedFile:o}=await Promise.resolve().then(()=>(C(),F)),n=s.default.join(this.templatesDir,"resources"),i=a;await o(s.default.join(e,"application.yml"),s.default.join(n,"application.yml.ejs"),i),r.modules.includes("Logging")&&await o(s.default.join(e,"logback-spring.xml"),s.default.join(n,"logback-spring.xml.ejs"),i)}async generateFrontend(e,a,r,o){let{writeGeneratedFile:n}=await Promise.resolve().then(()=>(C(),F)),i=a;if(o){let c=s.default.join(this.templatesDir,"thymeleaf"),p=s.default.join(e,"src/main/resources/templates");await u.default.ensureDir(p),await u.default.ensureDir(s.default.join(p,"admin"));let d=[["login.html","login.html.ejs"],["register.html","register.html.ejs"],["dashboard.html","dashboard.html.ejs"],["profile.html","profile.html.ejs"],["admin/users.html","admin/users.html.ejs"]];for(let[h,b]of d)await n(s.default.join(p,h),s.default.join(c,b),i);let g=s.default.join(e,"src/main/resources/static/css");await u.default.ensureDir(g),await u.default.copyFile(s.default.join(this.templatesDir,"static/css/style.css"),s.default.join(g,"style.css"))}else{let c=s.default.join(this.templatesDir,"static"),p=s.default.join(e,"src/main/resources/static");await u.default.ensureDir(s.default.join(p,"css")),await u.default.ensureDir(s.default.join(p,"js"));let d=[["index.html","index.html.ejs"],["login.html","login.html.ejs"],["register.html","register.html.ejs"],["dashboard.html","dashboard.html.ejs"],["profile.html","profile.html.ejs"],["admin.html","admin.html.ejs"]];for(let[g,h]of d){let b=s.default.join(c,h);await u.default.pathExists(b)&&await n(s.default.join(p,g),b,i)}await n(s.default.join(p,"js/nav.js"),s.default.join(c,"js/nav.js.ejs"),i),await u.default.copyFile(s.default.join(c,"js/auth.js"),s.default.join(p,"js/auth.js")),await u.default.copyFile(s.default.join(c,"js/ui.js"),s.default.join(p,"js/ui.js")),await u.default.copyFile(s.default.join(c,"css/style.css"),s.default.join(p,"css/style.css"))}}toPascalCase(e){return e.charAt(0).toUpperCase()+e.slice(1)}getDbDependencies(e){switch(e){case"mysql":return{dependency:"mysql",driverClass:"com.mysql.cj.jdbc.Driver"};case"postgresql":return{dependency:"postgresql",driverClass:"org.postgresql.Driver"};default:return{dependency:"h2",driverClass:"org.h2.Driver"}}}getDbConfig(e,a){switch(e){case"mysql":return{url:`jdbc:mysql://localhost:3306/${a}?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true`,username:"root",password:"YOUR_PASSWORD",dialect:"org.hibernate.dialect.MySQLDialect"};case"postgresql":return{url:`jdbc:postgresql://localhost:5432/${a}`,username:"postgres",password:"YOUR_PASSWORD",dialect:"org.hibernate.dialect.PostgreSQLDialect"};default:return{url:`jdbc:h2:mem:${a}`,username:"sa",password:"",dialect:"org.hibernate.dialect.H2Dialect"}}}printSuccessMessage(e){let a=e.authStrategy==="jwt",r=e.projectType==="fullstack",o=e.buildTool==="maven"?"./mvnw spring-boot:run":"./gradlew bootRun";console.log(),l.success(`Project "${e.projectName}" is ready!
|
|
47
48
|
`),console.log(` Next steps:
|
|
48
|
-
`),console.log(` cd ${e.projectName}`),console.log(` ${
|
|
49
|
+
`),console.log(` cd ${e.projectName}`),console.log(` ${o}`),console.log(),console.log(" Endpoints:"),console.log(" Home http://localhost:8080/"),r&&console.log(` Dashboard http://localhost:8080/${a?"":"dashboard"}`),e.modules.includes("Swagger")&&console.log(" API Docs http://localhost:8080/swagger-ui/index.html"),console.log(" Health http://localhost:8080/actuator/health"),console.log(),console.log(` Auth strategy: ${a?"JWT (stateless)":"Session (form-login)"}`),console.log(` Project type: ${r?"Fullstack":"REST API only"}`),console.log()}};function X(t){return!t||t.trim()===""?"Package name cannot be empty.":/^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)*$/.test(t.trim())?!0:"Package name must be lowercase, dot-separated identifiers (e.g. com.example.myapp)."}function ee(t){return!t||t.trim()===""?"Project name cannot be empty.":/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(t.trim())?!0:"Project name must start with a letter and contain only alphanumerics, hyphens, or underscores."}function te(t){return!t||t.trim()===""?"Entity name cannot be empty.":/^[A-Za-z][A-Za-z0-9]*$/.test(t.trim())?!0:"Entity name must start with a letter and contain only alphanumerics (PascalCase recommended)."}function ae(t){return t.replace(/\./g,"/")}function re(t){return t.charAt(0).toUpperCase()+t.slice(1)}S();async function ie(t){T();let e=ee(t);typeof e=="string"&&(l.error(e),process.exit(1)),x("Configure Project");let a=await R.default.prompt([{type:"input",name:"packageName",message:"Package name:",default:`com.example.${t.toLowerCase().replace(/[^a-z0-9]/g,"")}`,validate:X},{type:"input",name:"description",message:"Project description:",default:`${t} Spring Boot Application`},{type:"list",name:"buildTool",message:"Build tool:",choices:[{name:"Maven (recommended)",value:"maven"},{name:"Gradle (Groovy DSL)",value:"gradle"}],default:"maven"},{type:"list",name:"database",message:"Database:",choices:[{name:"H2 (in-memory, great for dev/testing)",value:"h2"},{name:"MySQL ",value:"mysql"},{name:"PostgreSQL",value:"postgresql"}],default:"h2"},{type:"list",name:"javaVersion",message:"Java version:",choices:["21","17"],default:"21"}]);x("Authentication & Frontend");let r=await R.default.prompt([{type:"list",name:"authStrategy",message:"Authentication method:",choices:[{name:"JWT \u2014 stateless, token-based, ideal for REST + SPA (recommended)",value:"jwt"},{name:"Session \u2014 Spring form-login, stateful, ideal for Thymeleaf apps",value:"session"}],default:"jwt"},{type:"list",name:"projectType",message:"Project type:",choices:[{name:"REST API \u2014 JSON responses only, no frontend",value:"api"},{name:"Fullstack \u2014 REST API + frontend (dashboard, profiles, admin panel)",value:"fullstack"}],default:"api"}]);x("Select Modules");let o=await R.default.prompt([{type:"checkbox",name:"modules",message:"Optional modules (space to toggle):",choices:[{name:"Swagger / OpenAPI UI",value:"Swagger",checked:!0},{name:"Mail (Spring Mail SMTP)",value:"Mail"},{name:"Logging (Logback with file appender)",value:"Logging"}]}]),n={projectName:t,packageName:a.packageName,packagePath:ae(a.packageName),database:a.database,buildTool:a.buildTool,modules:o.modules,javaVersion:a.javaVersion,springBootVersion:"3.2.4",description:a.description,authStrategy:r.authStrategy,projectType:r.projectType},i=new I;try{x(`Generating ${t}`),console.log(),await i.generate(n),l.done()}catch(c){console.log(),c instanceof Error?l.error(c.message):l.error("An unexpected error occurred."),process.exit(1)}}var U=j(require("inquirer")),q=j(require("fs-extra")),J=j(require("path"));var f=j(require("path")),G=j(require("fs-extra")),oe=j(require("ora"));C();S();var O=class{templatesDir;constructor(){this.templatesDir=f.default.resolve(__dirname,"../templates/entity")}async generate(e){l.title(`Generating entity: ${e.entityName}`);let a=(0,oe.default)({text:"Scaffolding entity files...",color:"cyan"}).start();try{let r=f.default.join(e.projectDir,"src/main/java",e.packagePath),o=f.default.join(e.projectDir,"src/test/java",e.packagePath);if(!await G.default.pathExists(r))throw new Error(`Cannot find Java source directory at "${r}". Make sure you are running this command inside a Sprygen-generated project.`);let n=this.buildContext(e);a.text="Writing entity, repository, service, controller...",await y(f.default.join(r,"entity",`${e.entityName}.java`),f.default.join(this.templatesDir,"Entity.java.ejs"),n),await y(f.default.join(r,"repository",`${e.entityName}Repository.java`),f.default.join(this.templatesDir,"EntityRepository.java.ejs"),n),await y(f.default.join(r,"service",`${e.entityName}Service.java`),f.default.join(this.templatesDir,"EntityService.java.ejs"),n),await y(f.default.join(r,"controller",`${e.entityName}Controller.java`),f.default.join(this.templatesDir,"EntityController.java.ejs"),n),await y(f.default.join(r,"dto",`${e.entityName}Dto.java`),f.default.join(this.templatesDir,"EntityDto.java.ejs"),n),a.text="Writing entity test...",await G.default.ensureDir(f.default.join(o,"controller")),await y(f.default.join(o,"controller",`${e.entityName}ControllerTest.java`),f.default.join(this.templatesDir,"EntityControllerTest.java.ejs"),n),a.succeed(`Entity "${e.entityName}" generated successfully!`)}catch(r){throw a.fail("Entity generation failed."),r}l.success(`
|
|
49
50
|
Entity "${e.entityName}" files created.
|
|
50
|
-
`)}buildContext(e){return{entityName:e.entityName,entityNameLower:e.entityNameLower,entityNameUpper:e.entityNameUpper,packageName:e.packageName,packagePath:e.packagePath,fields:e.fields,year:new Date().getFullYear()}}};
|
|
51
|
+
`)}buildContext(e){return{entityName:e.entityName,entityNameLower:e.entityNameLower,entityNameUpper:e.entityNameUpper,packageName:e.packageName,packagePath:e.packagePath,fields:e.fields,year:new Date().getFullYear()}}};S();async function ne(t){let e=te(t);typeof e=="string"&&(l.error(e),process.exit(1));let a=process.cwd(),r="";try{let g=J.default.join(a,"src/main/java");if(await q.default.pathExists(g)){let h=await q.default.readdir(g);if(h.length>0){let b=J.default.join(g,h[0]);for(r=h[0];;){let k=(await q.default.readdir(b,{withFileTypes:!0})).filter(N=>N.isDirectory());if(k.length===1)r+="."+k[0].name,b=J.default.join(b,k[0].name);else break}}}}catch{}let o=await U.default.prompt([{type:"input",name:"packageName",message:"Base package name (e.g. com.example.myapp):",default:r||"com.example.myapp"}]),n=[],i=!0;for(l.info(`
|
|
51
52
|
Add fields to your entity (leave name empty to finish)
|
|
52
|
-
`);
|
|
53
|
+
`);i;){let g=await U.default.prompt([{type:"input",name:"name",message:"Field name (camelCase):"}]);if(!g.name||g.name.trim()===""){i=!1;break}let h=await U.default.prompt([{type:"list",name:"type",message:`Type for ${g.name}:`,choices:["String","Integer","Long","Boolean","Double","LocalDate","LocalDateTime"],default:"String"},{type:"confirm",name:"nullable",message:"Can it be null?",default:!1}]);n.push({name:g.name,type:h.type,nullable:h.nullable})}let p={entityName:re(t),entityNameLower:t.toLowerCase(),entityNameUpper:t.toUpperCase(),packageName:o.packageName,packagePath:o.packageName.replace(/\./g,"/"),fields:n,projectDir:a},d=new O;try{await d.generate(p)}catch(g){g instanceof Error?l.error(g.message):l.error("An unexpected error occurred."),process.exit(1)}}var le=j(require("inquirer")),W=j(require("fs-extra")),A=j(require("path"));var v=j(require("path")),se=j(require("fs-extra")),ce=j(require("ora"));C();S();var L=class{templatesDir;constructor(){this.templatesDir=v.default.resolve(__dirname,"../templates/auth")}async generate(e){l.title("Generating JWT Authentication");let a=(0,ce.default)({text:"Scaffolding security files...",color:"cyan"}).start();try{let r=v.default.join(e.projectDir,"src/main/java",e.packagePath);if(!await se.default.pathExists(r))throw new Error("Cannot find Java source directory. Make sure you are inside a Sprygen project directory.");let o={packageName:e.packageName,packagePath:e.packagePath,projectName:e.projectName,year:new Date().getFullYear()};a.text="Writing SecurityConfig.java...",await y(v.default.join(r,"config","SecurityConfig.java"),v.default.join(this.templatesDir,"SecurityConfig.java.ejs"),o),a.text="Writing JwtService.java...",await y(v.default.join(r,"service","JwtService.java"),v.default.join(this.templatesDir,"JwtService.java.ejs"),o),a.text="Writing JwtAuthFilter.java...",await y(v.default.join(r,"security","JwtAuthFilter.java"),v.default.join(this.templatesDir,"JwtAuthFilter.java.ejs"),o),a.text="Writing AuthController.java...",await y(v.default.join(r,"controller","AuthController.java"),v.default.join(this.templatesDir,"AuthController.java.ejs"),o),a.text="Writing UserDetailsServiceImpl.java...",await y(v.default.join(r,"security","UserDetailsServiceImpl.java"),v.default.join(this.templatesDir,"UserDetailsServiceImpl.java.ejs"),o),a.succeed("JWT authentication scaffolded successfully!")}catch(r){throw a.fail("Auth generation failed."),r}l.success(`
|
|
53
54
|
JWT auth files generated.
|
|
54
|
-
`),
|
|
55
|
-
`)}};
|
|
55
|
+
`),l.info(`Make sure your application.yml has: jwt.secret and jwt.expiration set.
|
|
56
|
+
`)}};S();async function pe(){let t=process.cwd(),e="",a=A.default.basename(t);try{let i=A.default.join(t,"src/main/java");if(await W.default.pathExists(i)){let c=await W.default.readdir(i);if(c.length>0){let p=A.default.join(i,c[0]);for(e=c[0];;){let g=(await W.default.readdir(p,{withFileTypes:!0})).filter(h=>h.isDirectory());if(g.length===1)e+="."+g[0].name,p=A.default.join(p,g[0].name);else break}}}}catch{}let r=await le.default.prompt([{type:"input",name:"packageName",message:"Base package name (e.g. com.example.myapp):",default:e||"com.example.myapp"}]),o={packageName:r.packageName,packagePath:r.packageName.replace(/\./g,"/"),projectDir:t,projectName:a},n=new L;try{await n.generate(o)}catch(i){i instanceof Error?l.error(i.message):l.error("An unexpected error occurred."),process.exit(1)}}var me=require("child_process");S();function de(){T(),x("Updating Sprygen"),l.info("Fetching latest version from npm..."),(0,me.exec)("npm install -g sprygen@latest",(t,e,a)=>{if(t){l.error("Failed to update Sprygen."),console.error(a);return}l.success("Successfully updated to the latest version!"),console.log()})}var D=new ge.Command,{version:ke}=B();D.name("sprygen").description("A production-ready Spring Boot project generator CLI").version(ke);D.command("new <project-name>").description("Generate a new Spring Boot project").action(async t=>{await ie(t)});D.command("add-entity <entity-name>").description("Generate a repository, service, controller, and test for a new entity in an existing project").action(async t=>{await ne(t)});D.command("generate-auth").description("Scaffold JWT authentication and security configuration in an existing project").action(async()=>{await pe()});D.command("update").description("Update Sprygen to the latest version").action(()=>{de()});D.parse(process.argv);process.argv.slice(2).length||D.outputHelp();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sprygen",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "A production-ready Spring Boot project generator CLI. Scaffold secure, structured Java applications with built-in JWT or session authentication, role-based access control, user management, and an optional fullstack frontend — all from a single interactive command.",
|
|
5
5
|
"main": "dist/cli.js",
|
|
6
6
|
"bin": {
|