@kozojs/cli 0.1.11 → 0.1.13
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/lib/index.js +165 -18
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -39,11 +39,12 @@ async function scaffoldProject(options) {
|
|
|
39
39
|
const projectDir = import_node_path.default.resolve(process.cwd(), projectName);
|
|
40
40
|
const kozoCoreDep = packageSource === "local" ? "workspace:*" : "^0.2.0";
|
|
41
41
|
if (frontend !== "none") {
|
|
42
|
-
await scaffoldFullstackProject(projectDir, projectName, kozoCoreDep, runtime, database, frontend, extras, template);
|
|
42
|
+
await scaffoldFullstackProject(projectDir, projectName, kozoCoreDep, runtime, database, dbPort, auth, frontend, extras, template);
|
|
43
43
|
return;
|
|
44
44
|
}
|
|
45
45
|
if (template === "complete") {
|
|
46
46
|
await scaffoldCompleteTemplate(projectDir, projectName, kozoCoreDep, runtime, database, dbPort, auth);
|
|
47
|
+
if (database !== "none" && database !== "sqlite") await createDockerCompose(projectDir, projectName, database, dbPort);
|
|
47
48
|
if (extras.includes("docker")) await createDockerfile(projectDir, runtime);
|
|
48
49
|
if (extras.includes("github-actions")) await createGitHubActions(projectDir);
|
|
49
50
|
return;
|
|
@@ -250,6 +251,9 @@ ${database === "sqlite" ? "## SQLite Notes\n\nThe database is automatically init
|
|
|
250
251
|
- [Hono](https://hono.dev)
|
|
251
252
|
`;
|
|
252
253
|
await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, "README.md"), readme);
|
|
254
|
+
if (database !== "none" && database !== "sqlite") await createDockerCompose(projectDir, projectName, database, dbPort);
|
|
255
|
+
if (extras.includes("docker")) await createDockerfile(projectDir, runtime);
|
|
256
|
+
if (extras.includes("github-actions")) await createGitHubActions(projectDir);
|
|
253
257
|
}
|
|
254
258
|
function getDatabaseSchema(database) {
|
|
255
259
|
if (database === "postgresql") {
|
|
@@ -1238,12 +1242,92 @@ await app.nativeListen();
|
|
|
1238
1242
|
await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, "src", "index.ts"), indexTs);
|
|
1239
1243
|
await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, ".gitignore"), "node_modules/\ndist/\n.env\n");
|
|
1240
1244
|
}
|
|
1245
|
+
async function createDockerCompose(dir, projectName, database, dbPort, includeApiService = false, runtime = "node") {
|
|
1246
|
+
if (database === "none" || database === "sqlite") return;
|
|
1247
|
+
const pgPort = dbPort ?? 5436;
|
|
1248
|
+
let services = "";
|
|
1249
|
+
if (database === "postgresql") {
|
|
1250
|
+
const dbUrl = `postgresql://postgres:postgres@db:5432/${projectName}`;
|
|
1251
|
+
services += ` db:
|
|
1252
|
+
image: postgres:16-alpine
|
|
1253
|
+
restart: unless-stopped
|
|
1254
|
+
ports:
|
|
1255
|
+
- "${pgPort}:5432"
|
|
1256
|
+
environment:
|
|
1257
|
+
POSTGRES_USER: postgres
|
|
1258
|
+
POSTGRES_PASSWORD: postgres
|
|
1259
|
+
POSTGRES_DB: ${projectName}
|
|
1260
|
+
volumes:
|
|
1261
|
+
- postgres_data:/var/lib/postgresql/data
|
|
1262
|
+
healthcheck:
|
|
1263
|
+
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
|
1264
|
+
interval: 5s
|
|
1265
|
+
timeout: 5s
|
|
1266
|
+
retries: 5
|
|
1267
|
+
`;
|
|
1268
|
+
if (includeApiService) {
|
|
1269
|
+
const runCmd = runtime === "bun" ? "bun dist/index.js" : "node dist/index.js";
|
|
1270
|
+
services += `
|
|
1271
|
+
api:
|
|
1272
|
+
build: .
|
|
1273
|
+
ports:
|
|
1274
|
+
- "3000:3000"
|
|
1275
|
+
environment:
|
|
1276
|
+
NODE_ENV: production
|
|
1277
|
+
DATABASE_URL: ${dbUrl}
|
|
1278
|
+
JWT_SECRET: \${JWT_SECRET:-change-me-in-production}
|
|
1279
|
+
depends_on:
|
|
1280
|
+
db:
|
|
1281
|
+
condition: service_healthy
|
|
1282
|
+
command: ${runCmd}
|
|
1283
|
+
`;
|
|
1284
|
+
}
|
|
1285
|
+
} else if (database === "mysql") {
|
|
1286
|
+
const dbUrl = `mysql://root:root@db:3306/${projectName}`;
|
|
1287
|
+
services += ` db:
|
|
1288
|
+
image: mysql:8-alpine
|
|
1289
|
+
restart: unless-stopped
|
|
1290
|
+
ports:
|
|
1291
|
+
- "3306:3306"
|
|
1292
|
+
environment:
|
|
1293
|
+
MYSQL_ROOT_PASSWORD: root
|
|
1294
|
+
MYSQL_DATABASE: ${projectName}
|
|
1295
|
+
volumes:
|
|
1296
|
+
- mysql_data:/var/lib/mysql
|
|
1297
|
+
healthcheck:
|
|
1298
|
+
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
|
|
1299
|
+
interval: 5s
|
|
1300
|
+
timeout: 5s
|
|
1301
|
+
retries: 5
|
|
1302
|
+
`;
|
|
1303
|
+
if (includeApiService) {
|
|
1304
|
+
const runCmd = runtime === "bun" ? "bun dist/index.js" : "node dist/index.js";
|
|
1305
|
+
services += `
|
|
1306
|
+
api:
|
|
1307
|
+
build: .
|
|
1308
|
+
ports:
|
|
1309
|
+
- "3000:3000"
|
|
1310
|
+
environment:
|
|
1311
|
+
NODE_ENV: production
|
|
1312
|
+
DATABASE_URL: ${dbUrl}
|
|
1313
|
+
JWT_SECRET: \${JWT_SECRET:-change-me-in-production}
|
|
1314
|
+
depends_on:
|
|
1315
|
+
db:
|
|
1316
|
+
condition: service_healthy
|
|
1317
|
+
command: ${runCmd}
|
|
1318
|
+
`;
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
const volumes = database === "postgresql" ? "\nvolumes:\n postgres_data:\n" : database === "mysql" ? "\nvolumes:\n mysql_data:\n" : "";
|
|
1322
|
+
const compose = `services:
|
|
1323
|
+
${services}${volumes}`;
|
|
1324
|
+
await import_fs_extra.default.writeFile(import_node_path.default.join(dir, "docker-compose.yml"), compose);
|
|
1325
|
+
}
|
|
1241
1326
|
async function createDockerfile(projectDir, runtime) {
|
|
1242
|
-
const dockerfile = runtime === "bun" ? `FROM oven/bun:1
|
|
1327
|
+
const dockerfile = runtime === "bun" ? `FROM oven/bun:1 AS builder
|
|
1243
1328
|
WORKDIR /app
|
|
1244
1329
|
COPY package.json bun.lockb* ./
|
|
1245
1330
|
RUN bun install --frozen-lockfile
|
|
1246
|
-
|
|
1247
1331
|
COPY . .
|
|
1248
1332
|
RUN bun run build
|
|
1249
1333
|
|
|
@@ -1253,11 +1337,10 @@ COPY --from=builder /app/dist ./dist
|
|
|
1253
1337
|
COPY --from=builder /app/package.json ./
|
|
1254
1338
|
EXPOSE 3000
|
|
1255
1339
|
CMD ["bun", "dist/index.js"]
|
|
1256
|
-
` : `FROM node:20-alpine
|
|
1340
|
+
` : `FROM node:20-alpine AS builder
|
|
1257
1341
|
WORKDIR /app
|
|
1258
1342
|
COPY package*.json ./
|
|
1259
1343
|
RUN npm ci
|
|
1260
|
-
|
|
1261
1344
|
COPY . .
|
|
1262
1345
|
RUN npm run build
|
|
1263
1346
|
|
|
@@ -1270,7 +1353,7 @@ EXPOSE 3000
|
|
|
1270
1353
|
CMD ["node", "dist/index.js"]
|
|
1271
1354
|
`;
|
|
1272
1355
|
await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, "Dockerfile"), dockerfile);
|
|
1273
|
-
await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, ".dockerignore"), "node_modules\ndist\n.git\n");
|
|
1356
|
+
await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, ".dockerignore"), "node_modules\ndist\n.git\n.env\n");
|
|
1274
1357
|
}
|
|
1275
1358
|
async function createGitHubActions(projectDir) {
|
|
1276
1359
|
await import_fs_extra.default.ensureDir(import_node_path.default.join(projectDir, ".github", "workflows"));
|
|
@@ -1297,9 +1380,11 @@ jobs:
|
|
|
1297
1380
|
`;
|
|
1298
1381
|
await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, ".github", "workflows", "ci.yml"), workflow);
|
|
1299
1382
|
}
|
|
1300
|
-
async function scaffoldFullstackProject(projectDir, projectName, kozoCoreDep, runtime, database, frontend, extras, template) {
|
|
1383
|
+
async function scaffoldFullstackProject(projectDir, projectName, kozoCoreDep, runtime, database, dbPort, auth, frontend, extras, template) {
|
|
1384
|
+
const hasDb = database !== "none";
|
|
1301
1385
|
await import_fs_extra.default.ensureDir(import_node_path.default.join(projectDir, "apps", "api", "src", "routes"));
|
|
1302
|
-
await import_fs_extra.default.ensureDir(import_node_path.default.join(projectDir, "apps", "api", "src", "data"));
|
|
1386
|
+
if (!hasDb) await import_fs_extra.default.ensureDir(import_node_path.default.join(projectDir, "apps", "api", "src", "data"));
|
|
1387
|
+
if (hasDb) await import_fs_extra.default.ensureDir(import_node_path.default.join(projectDir, "apps", "api", "src", "db"));
|
|
1303
1388
|
await import_fs_extra.default.ensureDir(import_node_path.default.join(projectDir, "apps", "web", "src", "lib"));
|
|
1304
1389
|
await import_fs_extra.default.ensureDir(import_node_path.default.join(projectDir, ".vscode"));
|
|
1305
1390
|
const rootPackageJson = {
|
|
@@ -1315,32 +1400,82 @@ async function scaffoldFullstackProject(projectDir, projectName, kozoCoreDep, ru
|
|
|
1315
1400
|
- 'apps/*'
|
|
1316
1401
|
`);
|
|
1317
1402
|
await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, ".gitignore"), "node_modules/\ndist/\n.env\n*.log\n");
|
|
1318
|
-
await scaffoldFullstackApi(projectDir, projectName, kozoCoreDep, runtime);
|
|
1403
|
+
await scaffoldFullstackApi(projectDir, projectName, kozoCoreDep, runtime, database, dbPort, auth);
|
|
1319
1404
|
await scaffoldFullstackWeb(projectDir, projectName, frontend);
|
|
1320
1405
|
await scaffoldFullstackReadme(projectDir, projectName);
|
|
1406
|
+
if (database !== "none" && database !== "sqlite") await createDockerCompose(projectDir, projectName, database, dbPort);
|
|
1321
1407
|
if (extras.includes("docker")) await createDockerfile(import_node_path.default.join(projectDir, "apps", "api"), runtime);
|
|
1322
1408
|
if (extras.includes("github-actions")) await createGitHubActions(projectDir);
|
|
1323
1409
|
}
|
|
1324
|
-
async function scaffoldFullstackApi(projectDir, projectName, kozoCoreDep, runtime) {
|
|
1410
|
+
async function scaffoldFullstackApi(projectDir, projectName, kozoCoreDep, runtime, database = "none", dbPort, auth = true) {
|
|
1325
1411
|
const apiDir = import_node_path.default.join(projectDir, "apps", "api");
|
|
1412
|
+
const hasDb = database !== "none";
|
|
1413
|
+
if (hasDb) {
|
|
1414
|
+
await import_fs_extra.default.ensureDir(import_node_path.default.join(apiDir, "src", "db"));
|
|
1415
|
+
await import_fs_extra.default.writeFile(import_node_path.default.join(apiDir, "src", "db", "schema.ts"), getDatabaseSchema(database));
|
|
1416
|
+
await import_fs_extra.default.writeFile(import_node_path.default.join(apiDir, "src", "db", "index.ts"), getDatabaseIndex(database));
|
|
1417
|
+
if (database === "sqlite") {
|
|
1418
|
+
await import_fs_extra.default.writeFile(import_node_path.default.join(apiDir, "src", "db", "seed.ts"), getSQLiteSeed());
|
|
1419
|
+
}
|
|
1420
|
+
const dialect = database === "postgresql" ? "postgresql" : database === "mysql" ? "mysql" : "sqlite";
|
|
1421
|
+
const pgPort = dbPort ?? 5436;
|
|
1422
|
+
const dbUrl = database === "postgresql" ? `postgresql://postgres:postgres@localhost:${pgPort}/${projectName}` : database === "mysql" ? `mysql://root:root@localhost:3306/${projectName}` : void 0;
|
|
1423
|
+
await import_fs_extra.default.writeFile(import_node_path.default.join(apiDir, "drizzle.config.ts"), `import { defineConfig } from 'drizzle-kit';
|
|
1424
|
+
import 'dotenv/config';
|
|
1425
|
+
|
|
1426
|
+
export default defineConfig({
|
|
1427
|
+
schema: './src/db/schema.ts',
|
|
1428
|
+
out: './drizzle',
|
|
1429
|
+
dialect: '${dialect}',
|
|
1430
|
+
dbCredentials: {
|
|
1431
|
+
${database === "sqlite" ? "url: './data.db'" : "url: process.env.DATABASE_URL!"}
|
|
1432
|
+
},
|
|
1433
|
+
});
|
|
1434
|
+
`);
|
|
1435
|
+
const envContent = `PORT=3000
|
|
1436
|
+
NODE_ENV=development
|
|
1437
|
+
${dbUrl ? `DATABASE_URL=${dbUrl}
|
|
1438
|
+
` : ""}${auth ? "JWT_SECRET=change-me-to-a-random-secret-at-least-32-chars\n" : ""}`;
|
|
1439
|
+
await import_fs_extra.default.writeFile(import_node_path.default.join(apiDir, ".env"), envContent);
|
|
1440
|
+
await import_fs_extra.default.writeFile(import_node_path.default.join(apiDir, ".env.example"), envContent);
|
|
1441
|
+
} else {
|
|
1442
|
+
const envContent = `PORT=3000
|
|
1443
|
+
NODE_ENV=development
|
|
1444
|
+
${auth ? "JWT_SECRET=change-me-to-a-random-secret-at-least-32-chars\n" : ""}`;
|
|
1445
|
+
await import_fs_extra.default.writeFile(import_node_path.default.join(apiDir, ".env"), envContent);
|
|
1446
|
+
await import_fs_extra.default.writeFile(import_node_path.default.join(apiDir, ".env.example"), envContent);
|
|
1447
|
+
}
|
|
1326
1448
|
const apiPackageJson = {
|
|
1327
1449
|
name: `@${projectName}/api`,
|
|
1328
1450
|
version: "1.0.0",
|
|
1329
1451
|
type: "module",
|
|
1330
1452
|
scripts: {
|
|
1331
1453
|
dev: runtime === "bun" ? "bun --watch src/index.ts" : "tsx watch src/index.ts",
|
|
1332
|
-
build: "tsc"
|
|
1454
|
+
build: "tsc",
|
|
1455
|
+
...hasDb && {
|
|
1456
|
+
"db:generate": "drizzle-kit generate",
|
|
1457
|
+
"db:push": "drizzle-kit push",
|
|
1458
|
+
"db:studio": "drizzle-kit studio"
|
|
1459
|
+
}
|
|
1333
1460
|
},
|
|
1334
1461
|
dependencies: {
|
|
1335
1462
|
"@kozojs/core": kozoCoreDep,
|
|
1463
|
+
...auth && { "@kozojs/auth": kozoCoreDep },
|
|
1336
1464
|
hono: "^4.6.0",
|
|
1337
1465
|
zod: "^3.23.0",
|
|
1338
|
-
|
|
1466
|
+
dotenv: "^16.4.0",
|
|
1467
|
+
...runtime === "node" && { "@hono/node-server": "^1.13.0" },
|
|
1468
|
+
...hasDb && { "drizzle-orm": "^0.36.0" },
|
|
1469
|
+
...database === "postgresql" && { postgres: "^3.4.0" },
|
|
1470
|
+
...database === "mysql" && { mysql2: "^3.11.0" },
|
|
1471
|
+
...database === "sqlite" && { "better-sqlite3": "^11.0.0" }
|
|
1339
1472
|
},
|
|
1340
1473
|
devDependencies: {
|
|
1341
1474
|
"@types/node": "^22.0.0",
|
|
1342
1475
|
...runtime !== "bun" && { tsx: "^4.19.0" },
|
|
1343
|
-
typescript: "^5.6.0"
|
|
1476
|
+
typescript: "^5.6.0",
|
|
1477
|
+
...hasDb && { "drizzle-kit": "^0.28.0" },
|
|
1478
|
+
...database === "sqlite" && { "@types/better-sqlite3": "^7.6.0" }
|
|
1344
1479
|
}
|
|
1345
1480
|
};
|
|
1346
1481
|
await import_fs_extra.default.writeJSON(import_node_path.default.join(apiDir, "package.json"), apiPackageJson, { spaces: 2 });
|
|
@@ -1359,11 +1494,23 @@ async function scaffoldFullstackApi(projectDir, projectName, kozoCoreDep, runtim
|
|
|
1359
1494
|
exclude: ["node_modules", "dist"]
|
|
1360
1495
|
};
|
|
1361
1496
|
await import_fs_extra.default.writeJSON(import_node_path.default.join(apiDir, "tsconfig.json"), tsconfig, { spaces: 2 });
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1497
|
+
const dbImport = hasDb ? `import { createDb } from './db/index.js';
|
|
1498
|
+
` : "";
|
|
1499
|
+
const authImport = auth ? `import { authenticateJWT } from '@kozojs/auth';
|
|
1500
|
+
` : "";
|
|
1501
|
+
const servicesSetup = hasDb ? `
|
|
1502
|
+
const db = await createDb();
|
|
1503
|
+
const services = { db };
|
|
1504
|
+
` : "\nconst services = {};\n";
|
|
1505
|
+
const authMiddleware = auth ? `
|
|
1506
|
+
app.use('*', authenticateJWT({ secret: process.env.JWT_SECRET || 'change-me' }));
|
|
1507
|
+
` : "";
|
|
1508
|
+
await import_fs_extra.default.writeFile(import_node_path.default.join(apiDir, "src", "index.ts"), `import 'dotenv/config';
|
|
1509
|
+
import { createKozo } from '@kozojs/core';
|
|
1510
|
+
${dbImport}${authImport}import { registerRoutes } from './routes/index.js';
|
|
1511
|
+
${servicesSetup}
|
|
1512
|
+
const app = createKozo({ port: 3000, services });
|
|
1513
|
+
${authMiddleware}
|
|
1367
1514
|
registerRoutes(app);
|
|
1368
1515
|
|
|
1369
1516
|
export type AppType = typeof app;
|