create-kancil-app 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/index.js +51 -0
- package/package.json +9 -0
- package/skeleton/.env.example +7 -0
- package/skeleton/app/middleware/csrf.js +16 -0
- package/skeleton/app/modules/page/controllers/PageController.js +7 -0
- package/skeleton/app/modules/page/routes.js +5 -0
- package/skeleton/app/themes/default/index.hbs +38 -0
- package/skeleton/app/themes/default/layout.hbs +22 -0
- package/skeleton/app/themes/default/partials/footer.hbs +3 -0
- package/skeleton/app/themes/default/partials/nav.hbs +11 -0
- package/skeleton/migrations/.gitkeep +0 -0
- package/skeleton/package.json +12 -0
- package/skeleton/seeds/.gitkeep +0 -0
- package/skeleton/server.js +12 -0
- package/skeleton/storage/logs/.gitkeep +0 -0
package/index.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, statSync, copyFileSync } from 'fs';
|
|
3
|
+
import { join, dirname, basename } from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const skeletonDir = join(__dirname, 'skeleton');
|
|
8
|
+
|
|
9
|
+
const target = process.argv[2] || '.';
|
|
10
|
+
const projectDir = join(process.cwd(), target);
|
|
11
|
+
|
|
12
|
+
if (target !== '.' && !target.startsWith('/') && existsSync(projectDir) && readdirSync(projectDir).length > 0) {
|
|
13
|
+
console.error('\n❌ Error: directory "' + target + '" already exists and is not empty\n');
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function copyRecursive(src, dest) {
|
|
18
|
+
if (!existsSync(dest)) mkdirSync(dest, { recursive: true });
|
|
19
|
+
for (const item of readdirSync(src)) {
|
|
20
|
+
const s = join(src, item);
|
|
21
|
+
const d = join(dest, item);
|
|
22
|
+
if (statSync(s).isDirectory()) {
|
|
23
|
+
copyRecursive(s, d);
|
|
24
|
+
} else {
|
|
25
|
+
copyFileSync(s, d);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
copyRecursive(skeletonDir, projectDir);
|
|
31
|
+
|
|
32
|
+
const pkgPath = join(projectDir, 'package.json');
|
|
33
|
+
if (existsSync(pkgPath)) {
|
|
34
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
35
|
+
pkg.name = target === '.' ? basename(projectDir) : target;
|
|
36
|
+
writeFileSync(pkgPath, JSON.stringify(pkg, null, 4) + '\n');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const name = target === '.' ? basename(projectDir) : target;
|
|
40
|
+
|
|
41
|
+
console.log('\n' + [
|
|
42
|
+
' ╔══════════════════════════════════════╗',
|
|
43
|
+
' ║ 🚀 Kancil Framework v0.1.0 ║',
|
|
44
|
+
' ║ Project: ' + name.padEnd(28) + '║',
|
|
45
|
+
' ╚══════════════════════════════════════╝',
|
|
46
|
+
'',
|
|
47
|
+
' ✅ Project created!',
|
|
48
|
+
'',
|
|
49
|
+
' cd ' + target + ' && bun install && bun run server.js',
|
|
50
|
+
''
|
|
51
|
+
].join('\n'));
|
package/package.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Csrf } from 'kancil-framework';
|
|
2
|
+
|
|
3
|
+
export default async (req, next) => {
|
|
4
|
+
if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) return next();
|
|
5
|
+
const token = req.data?._csrf || '';
|
|
6
|
+
if (!Csrf.verify(token)) {
|
|
7
|
+
const { ErrorViews } = await import('kancil-framework');
|
|
8
|
+
return new Response(ErrorViews.render(403, {
|
|
9
|
+
message: 'CSRF token tidak valid atau expired. Refresh halaman dan coba lagi.'
|
|
10
|
+
}), {
|
|
11
|
+
status: 403,
|
|
12
|
+
headers: { 'Content-Type': 'text/html' }
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
return next();
|
|
16
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<div class="text-center" style="margin-top:10vh">
|
|
2
|
+
<h1 class="fw-bold display-4 mb-3">
|
|
3
|
+
<span style="background:linear-gradient(135deg,#6366f1,#8b5cf6);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text">Kancil</span>
|
|
4
|
+
</h1>
|
|
5
|
+
<p class="lead text-secondary mb-5">Bun-native MVC framework — running!</p>
|
|
6
|
+
<div class="row justify-content-center g-3 mb-5">
|
|
7
|
+
<div class="col-md-4 col-6">
|
|
8
|
+
<div class="card border-0 shadow-sm h-100">
|
|
9
|
+
<div class="card-body text-center py-4">
|
|
10
|
+
<div class="fs-2 mb-2">⚡</div>
|
|
11
|
+
<h6 class="fw-semibold">Native Bun</h6>
|
|
12
|
+
<p class="small text-secondary mb-0">Bun.serve({ routes })</p>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
<div class="col-md-4 col-6">
|
|
17
|
+
<div class="card border-0 shadow-sm h-100">
|
|
18
|
+
<div class="card-body text-center py-4">
|
|
19
|
+
<div class="fs-2 mb-2">📦</div>
|
|
20
|
+
<h6 class="fw-semibold">Modular</h6>
|
|
21
|
+
<p class="small text-secondary mb-0">Auto-discovery modules</p>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
<div class="col-md-4 col-6">
|
|
26
|
+
<div class="card border-0 shadow-sm h-100">
|
|
27
|
+
<div class="card-body text-center py-4">
|
|
28
|
+
<div class="fs-2 mb-2">🗄️</div>
|
|
29
|
+
<h6 class="fw-semibold">MVC</h6>
|
|
30
|
+
<p class="small text-secondary mb-0">Reply · View · Model</p>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
<div class="text-center">
|
|
36
|
+
<a href="https://npmjs.com/package/kancil-framework" class="btn btn-outline-primary btn-sm rounded-pill px-4">Dokumentasi</a>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>{{title}} · Kancil</title>
|
|
7
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
9
|
+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
10
|
+
<style>
|
|
11
|
+
body{font-family:'Inter',sans-serif;background:#f8f9fa}
|
|
12
|
+
code{color:#6366f1}
|
|
13
|
+
*,*::before,*::after{animation:none!important;transition:none!important}
|
|
14
|
+
</style>
|
|
15
|
+
</head>
|
|
16
|
+
<body>
|
|
17
|
+
{{> nav}}
|
|
18
|
+
<main class="container py-5">{{{body}}}</main>
|
|
19
|
+
{{> footer}}
|
|
20
|
+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
|
21
|
+
</body>
|
|
22
|
+
</html>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<nav class="navbar navbar-expand-lg navbar-light bg-white border-bottom shadow-sm">
|
|
2
|
+
<div class="container">
|
|
3
|
+
<a class="navbar-brand fw-bold" href="/"><span class="text-primary">Kancil</span></a>
|
|
4
|
+
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"><span class="navbar-toggler-icon"></span></button>
|
|
5
|
+
<div class="collapse navbar-collapse" id="navbarNav">
|
|
6
|
+
<ul class="navbar-nav ms-auto">
|
|
7
|
+
<li class="nav-item"><a class="nav-link" href="/">Home</a></li>
|
|
8
|
+
</ul>
|
|
9
|
+
</div>
|
|
10
|
+
</div>
|
|
11
|
+
</nav>
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Application, Config, DB, Cors, Migration, Seeder } from 'kancil-framework';
|
|
2
|
+
|
|
3
|
+
const config = new Config();
|
|
4
|
+
DB.init(config.get('DATABASE_URL'));
|
|
5
|
+
|
|
6
|
+
await Migration.run(import.meta.dir + '/migrations');
|
|
7
|
+
await Seeder.run(import.meta.dir + '/seeds');
|
|
8
|
+
|
|
9
|
+
const app = new Application({ basePath: import.meta.dir + '/app' });
|
|
10
|
+
app.use(Cors());
|
|
11
|
+
await app.loadModules(import.meta.dir + '/app/modules');
|
|
12
|
+
await app.listen();
|
|
File without changes
|