create-next-java 1.0.0
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/bin/index.js +304 -0
- package/package.json +21 -0
package/bin/index.js
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
const banner = `
|
|
7
|
+
_ _ _ _
|
|
8
|
+
| \\ | | | | | |
|
|
9
|
+
| \\| | _____ _| |_ | | __ ___ ____ _
|
|
10
|
+
| . \` |/ _ \\ \\/ / __| _ | |/ _\` \\ \\ / / _\` |
|
|
11
|
+
| |\\ | __/> <| |_ | |__| | (_| |\\ V / (_| |
|
|
12
|
+
|_| \\_|\\___/_/\\_\\\\__| \\____/ \\__,_| \\_/ \\__,_|
|
|
13
|
+
|
|
14
|
+
š Next.java Project Generator (v1.0.0)
|
|
15
|
+
`;
|
|
16
|
+
|
|
17
|
+
console.log(banner);
|
|
18
|
+
|
|
19
|
+
const args = process.argv.slice(2);
|
|
20
|
+
const projectName = args[0] || 'my-nextjava-app';
|
|
21
|
+
const targetDir = path.resolve(process.cwd(), projectName);
|
|
22
|
+
|
|
23
|
+
if (fs.existsSync(targetDir)) {
|
|
24
|
+
console.error(`ā Directory '${projectName}' already exists. Please choose another name or empty the directory.`);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
console.log(`Creating a new Next.java full-stack application in ${targetDir}...\n`);
|
|
29
|
+
|
|
30
|
+
// Create directory tree
|
|
31
|
+
const dirs = [
|
|
32
|
+
'app',
|
|
33
|
+
'app/api/health',
|
|
34
|
+
'components',
|
|
35
|
+
'middleware',
|
|
36
|
+
'models',
|
|
37
|
+
'database/migrations',
|
|
38
|
+
'database/seed',
|
|
39
|
+
'jobs',
|
|
40
|
+
'test',
|
|
41
|
+
'wasm'
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
dirs.forEach(d => fs.mkdirSync(path.join(targetDir, d), { recursive: true }));
|
|
45
|
+
|
|
46
|
+
// 1. app/layout.java
|
|
47
|
+
fs.writeFileSync(path.join(targetDir, 'app', 'layout.java'), `package app;
|
|
48
|
+
|
|
49
|
+
import com.nextjava.ui.Layout;
|
|
50
|
+
import com.nextjava.ui.UI;
|
|
51
|
+
import static com.nextjava.ui.Components.*;
|
|
52
|
+
|
|
53
|
+
public class layout implements Layout {
|
|
54
|
+
@Override
|
|
55
|
+
public UI render(UI children) {
|
|
56
|
+
return Html(
|
|
57
|
+
Head(
|
|
58
|
+
Title("Next.java App"),
|
|
59
|
+
Meta("viewport", "width=device-width, initial-scale=1")
|
|
60
|
+
),
|
|
61
|
+
Body(
|
|
62
|
+
Navbar(
|
|
63
|
+
Heading(2, "Next.java").style("color: #0070f3;"),
|
|
64
|
+
Div(Link("/", "Home"))
|
|
65
|
+
),
|
|
66
|
+
Main(children),
|
|
67
|
+
Footer(Paragraph("Powered by Next.java v1.0"))
|
|
68
|
+
)
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
`);
|
|
73
|
+
|
|
74
|
+
// 2. app/page.java & app/page.jsx (In-Process React SSR)
|
|
75
|
+
fs.writeFileSync(path.join(targetDir, 'app', 'page.java'), `package app;
|
|
76
|
+
|
|
77
|
+
import com.nextjava.ui.Page;
|
|
78
|
+
import com.nextjava.ui.UI;
|
|
79
|
+
import static com.nextjava.ui.Components.*;
|
|
80
|
+
|
|
81
|
+
public class page implements Page {
|
|
82
|
+
@Override
|
|
83
|
+
public UI render() {
|
|
84
|
+
return Column(
|
|
85
|
+
Heading(1, "Welcome to Next.java š"),
|
|
86
|
+
Paragraph("The Full-Stack Java Framework with in-process React SSR and Virtual Threads.")
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
`);
|
|
91
|
+
|
|
92
|
+
fs.writeFileSync(path.join(targetDir, 'app', 'page.jsx'), `export default function HomePage({ title }) {
|
|
93
|
+
return (
|
|
94
|
+
<section style={{ padding: '2rem 0' }}>
|
|
95
|
+
<h1>Welcome to Next.java š</h1>
|
|
96
|
+
<p>The Full-Stack Java Framework with in-process React SSR and Virtual Threads.</p>
|
|
97
|
+
</section>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
`);
|
|
101
|
+
|
|
102
|
+
// 3. app/api/health/route.java
|
|
103
|
+
fs.writeFileSync(path.join(targetDir, 'app', 'api', 'health', 'route.java'), `package app.api.health;
|
|
104
|
+
|
|
105
|
+
import com.nextjava.http.Response;
|
|
106
|
+
import com.nextjava.observability.HealthIndicator;
|
|
107
|
+
import com.nextjava.server.annotation.GET;
|
|
108
|
+
|
|
109
|
+
public class route {
|
|
110
|
+
@GET
|
|
111
|
+
public Response checkHealth() {
|
|
112
|
+
return Response.json(HealthIndicator.toJson());
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
`);
|
|
116
|
+
|
|
117
|
+
// 4. middleware/middleware.java
|
|
118
|
+
fs.writeFileSync(path.join(targetDir, 'middleware', 'middleware.java'), `package middleware;
|
|
119
|
+
|
|
120
|
+
import com.nextjava.http.Request;
|
|
121
|
+
import com.nextjava.http.Response;
|
|
122
|
+
|
|
123
|
+
public class middleware {
|
|
124
|
+
public Response handle(Request request) {
|
|
125
|
+
return null; // Proceed to route handler
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
`);
|
|
129
|
+
|
|
130
|
+
// 5. models/Product.java
|
|
131
|
+
fs.writeFileSync(path.join(targetDir, 'models', 'Product.java'), `package models;
|
|
132
|
+
|
|
133
|
+
import com.nextjava.data.annotation.Entity;
|
|
134
|
+
import com.nextjava.data.annotation.Id;
|
|
135
|
+
import com.nextjava.data.annotation.Table;
|
|
136
|
+
|
|
137
|
+
@Entity
|
|
138
|
+
@Table(name = "products")
|
|
139
|
+
public record Product(
|
|
140
|
+
@Id String id,
|
|
141
|
+
String name,
|
|
142
|
+
double price
|
|
143
|
+
) {}
|
|
144
|
+
`);
|
|
145
|
+
|
|
146
|
+
// 6. database/migrations/001_init.sql & database/seed/seed.sql
|
|
147
|
+
fs.writeFileSync(path.join(targetDir, 'database', 'migrations', '001_init.sql'), `-- Initial Migration
|
|
148
|
+
CREATE TABLE IF NOT EXISTS products (
|
|
149
|
+
id VARCHAR(64) PRIMARY KEY,
|
|
150
|
+
name VARCHAR(255) NOT NULL,
|
|
151
|
+
price DOUBLE NOT NULL,
|
|
152
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
153
|
+
);
|
|
154
|
+
`);
|
|
155
|
+
|
|
156
|
+
fs.writeFileSync(path.join(targetDir, 'database', 'seed', 'seed.sql'), `-- Seed Data
|
|
157
|
+
INSERT INTO products (id, name, price) VALUES ('p1', 'Enterprise Server License', 499.00);
|
|
158
|
+
INSERT INTO products (id, name, price) VALUES ('p2', 'Developer Toolkit', 99.00);
|
|
159
|
+
`);
|
|
160
|
+
|
|
161
|
+
// 7. jobs/MetricsJob.java
|
|
162
|
+
fs.writeFileSync(path.join(targetDir, 'jobs', 'MetricsJob.java'), `package jobs;
|
|
163
|
+
|
|
164
|
+
import com.nextjava.jobs.annotation.Scheduled;
|
|
165
|
+
|
|
166
|
+
public class MetricsJob {
|
|
167
|
+
@Scheduled(fixedRate = 30000, initialDelay = 5000)
|
|
168
|
+
public void reportSystemMetrics() {
|
|
169
|
+
// Runs on Java 21 Virtual Threads
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
`);
|
|
173
|
+
|
|
174
|
+
// 8. test/AppTest.java
|
|
175
|
+
fs.writeFileSync(path.join(targetDir, 'test', 'AppTest.java'), `package test;
|
|
176
|
+
|
|
177
|
+
import com.nextjava.testing.TestClient;
|
|
178
|
+
import com.nextjava.testing.annotation.NextTest;
|
|
179
|
+
import com.nextjava.testing.annotation.Test;
|
|
180
|
+
|
|
181
|
+
@NextTest("Application Health Suite")
|
|
182
|
+
public class AppTest {
|
|
183
|
+
private final TestClient client = new TestClient("http://localhost:3000");
|
|
184
|
+
|
|
185
|
+
@Test("Health probe is UP")
|
|
186
|
+
public void testHealth() {
|
|
187
|
+
client.get("/api/health")
|
|
188
|
+
.expectStatus(200)
|
|
189
|
+
.expectBodyContains("UP");
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
`);
|
|
193
|
+
|
|
194
|
+
// 9. nextjava.config.java
|
|
195
|
+
fs.writeFileSync(path.join(targetDir, 'nextjava.config.java'), `import com.nextjava.core.Config;
|
|
196
|
+
|
|
197
|
+
class NextConfigSetup {
|
|
198
|
+
public static Config configure() {
|
|
199
|
+
return new Config()
|
|
200
|
+
.appName("${projectName}")
|
|
201
|
+
.port(3000)
|
|
202
|
+
.set("jsEngine", "AUTO");
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
`);
|
|
206
|
+
|
|
207
|
+
// 10. Dockerfile & docker-compose.yml
|
|
208
|
+
fs.writeFileSync(path.join(targetDir, 'Dockerfile'), `# Next.java Production Multi-Stage Dockerfile
|
|
209
|
+
FROM eclipse-temurin:21-jdk-jammy AS builder
|
|
210
|
+
WORKDIR /app
|
|
211
|
+
COPY . .
|
|
212
|
+
RUN java -jar /usr/local/bin/nextjava.jar build || true
|
|
213
|
+
|
|
214
|
+
FROM eclipse-temurin:21-jre-jammy
|
|
215
|
+
WORKDIR /app
|
|
216
|
+
COPY --from=builder /app/dist/app.jar ./app.jar
|
|
217
|
+
COPY --from=builder /app/.env ./.env
|
|
218
|
+
|
|
219
|
+
ENV PORT=3000
|
|
220
|
+
ENV ENVIRONMENT=production
|
|
221
|
+
EXPOSE 3000
|
|
222
|
+
|
|
223
|
+
ENTRYPOINT ["java", "-XX:+UseZGC", "-XX:+ZGenerational", "-Dfile.encoding=UTF-8", "-jar", "app.jar"]
|
|
224
|
+
`);
|
|
225
|
+
|
|
226
|
+
fs.writeFileSync(path.join(targetDir, 'docker-compose.yml'), `version: '3.8'
|
|
227
|
+
services:
|
|
228
|
+
app:
|
|
229
|
+
build: .
|
|
230
|
+
ports:
|
|
231
|
+
- "3000:3000"
|
|
232
|
+
environment:
|
|
233
|
+
- ENVIRONMENT=production
|
|
234
|
+
- PORT=3000
|
|
235
|
+
restart: always
|
|
236
|
+
`);
|
|
237
|
+
|
|
238
|
+
// 11. package.json, .gitignore, .env, README.md
|
|
239
|
+
fs.writeFileSync(path.join(targetDir, 'package.json'), JSON.stringify({
|
|
240
|
+
name: projectName,
|
|
241
|
+
version: '1.0.0',
|
|
242
|
+
private: true,
|
|
243
|
+
scripts: {
|
|
244
|
+
dev: 'nextjava dev',
|
|
245
|
+
build: 'nextjava build',
|
|
246
|
+
start: 'nextjava start',
|
|
247
|
+
test: 'nextjava test'
|
|
248
|
+
},
|
|
249
|
+
dependencies: {
|
|
250
|
+
nextjava: '^1.0.0',
|
|
251
|
+
react: '^18.2.0',
|
|
252
|
+
'react-dom': '^18.2.0'
|
|
253
|
+
}
|
|
254
|
+
}, null, 2));
|
|
255
|
+
|
|
256
|
+
fs.writeFileSync(path.join(targetDir, '.gitignore'), `target/
|
|
257
|
+
dist/
|
|
258
|
+
.nextjava/
|
|
259
|
+
*.class
|
|
260
|
+
*.log
|
|
261
|
+
.env.local
|
|
262
|
+
node_modules/
|
|
263
|
+
`);
|
|
264
|
+
|
|
265
|
+
fs.writeFileSync(path.join(targetDir, '.env'), `PORT=3000
|
|
266
|
+
ENVIRONMENT=development
|
|
267
|
+
DATABASE_URL=jdbc:sqlite:./data.db
|
|
268
|
+
SECRET_KEY=nextjava_super_secret_development_key_32_bytes!
|
|
269
|
+
`);
|
|
270
|
+
|
|
271
|
+
fs.writeFileSync(path.join(targetDir, 'README.md'), `# ${projectName}
|
|
272
|
+
|
|
273
|
+
Created with [Next.java](https://nextjava.dev) ā The Full-Stack Java Framework.
|
|
274
|
+
|
|
275
|
+
## š Quick Start
|
|
276
|
+
|
|
277
|
+
\`\`\`bash
|
|
278
|
+
# 1. Start development server with live hot-reload
|
|
279
|
+
nextjava dev
|
|
280
|
+
|
|
281
|
+
# 2. Run automated test suites
|
|
282
|
+
nextjava test
|
|
283
|
+
|
|
284
|
+
# 3. Apply database migrations
|
|
285
|
+
nextjava db migrate
|
|
286
|
+
|
|
287
|
+
# 4. Build for production
|
|
288
|
+
nextjava build
|
|
289
|
+
\`\`\`
|
|
290
|
+
`);
|
|
291
|
+
|
|
292
|
+
console.log(`\nš¦ Installing dependencies (react, react-dom, nextjava)...`);
|
|
293
|
+
import { execSync } from 'child_process';
|
|
294
|
+
try {
|
|
295
|
+
execSync('npm install', { cwd: targetDir, stdio: 'inherit' });
|
|
296
|
+
} catch (e) {
|
|
297
|
+
console.warn(`ā ļø npm install failed. Please run it manually inside the directory.`);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
console.log(`š Successfully initialized Next.java project in '${projectName}'!`);
|
|
301
|
+
console.log(`\nNext steps:`);
|
|
302
|
+
console.log(` 1. cd ${projectName}`);
|
|
303
|
+
console.log(` 2. npm run dev`);
|
|
304
|
+
console.log(`\nHappy coding with Next.java! āāØ\n`);
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-next-java",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Interactive CLI starter to create modern full-stack Next.java applications",
|
|
5
|
+
"bin": {
|
|
6
|
+
"create-next-java": "./bin/index.js"
|
|
7
|
+
},
|
|
8
|
+
"type": "module",
|
|
9
|
+
"keywords": [
|
|
10
|
+
"nextjava",
|
|
11
|
+
"java",
|
|
12
|
+
"react",
|
|
13
|
+
"full-stack",
|
|
14
|
+
"framework",
|
|
15
|
+
"ssr",
|
|
16
|
+
"wasm",
|
|
17
|
+
"virtual-threads"
|
|
18
|
+
],
|
|
19
|
+
"author": "Next.java Team",
|
|
20
|
+
"license": "Apache-2.0"
|
|
21
|
+
}
|