create-aziz-app 2.0.14 → 3.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/README.md +227 -0
- package/bin/create-aziz-app.js +9 -9
- package/package.json +10 -11
- package/template/README.md +862 -138
- package/template/_framework/boot.js +36 -0
- package/template/_framework/hooks.js +207 -0
- package/template/{modules → _framework}/next-response.js +5 -13
- package/template/_framework/router.js +279 -0
- package/template/_framework/runtime.js +86 -0
- package/template/api/createUser.js +1 -1
- package/template/api/hello.js +8 -17
- package/template/api/loginUser.js +1 -1
- package/template/api/users.js +38 -134
- package/template/app/about/page.js +45 -0
- package/template/app/layout.js +23 -0
- package/template/app/page.js +54 -0
- package/template/app/test/page.js +55 -0
- package/template/components/Footer.js +10 -0
- package/template/components/Navbar.js +30 -0
- package/template/index.html +24 -0
- package/template/package.json +5 -14
- package/template/scripts/dev-server.js +330 -302
- package/template/scripts/live-reload-client.js +0 -1
- package/template/.vscode/settings.json +0 -15
- package/template/api/getUser.js +0 -60
- package/template/api/getUserWithDB.js +0 -65
- package/template/app.config.js +0 -41
- package/template/app.html +0 -55
- package/template/components/Counter.jsx +0 -36
- package/template/components/Footer.jsx +0 -9
- package/template/components/Navbar.jsx +0 -17
- package/template/docs/API-GUIDE.md +0 -196
- package/template/docs/API-ROUTES.md +0 -538
- package/template/docs/AUTO-IMPORT.md +0 -99
- package/template/docs/AUTO-REGISTRATION.md +0 -248
- package/template/docs/CSS-FRAMEWORKS.md +0 -221
- package/template/docs/DEV-SERVER.md +0 -184
- package/template/docs/GETTING-STARTED.md +0 -205
- package/template/docs/HOW-IT-WORKS.md +0 -362
- package/template/docs/NEXTJS-APP-ROUTER.md +0 -536
- package/template/docs/NEXTJS-COMPARISON.md +0 -473
- package/template/docs/QUICK-START.md +0 -111
- package/template/docs/STYLING.md +0 -269
- package/template/docs/TROUBLESHOOTING.md +0 -282
- package/template/modules/README.md +0 -129
- package/template/modules/api.js +0 -44
- package/template/modules/auto-import.js +0 -130
- package/template/modules/router.js +0 -218
- package/template/modules/state.js +0 -248
- package/template/package-lock.json +0 -13
- package/template/pages/About.jsx +0 -48
- package/template/pages/ApiDemo.jsx +0 -209
- package/template/pages/Contact.jsx +0 -25
- package/template/pages/Example.jsx +0 -59
- package/template/pages/Home.jsx +0 -39
- package/template/pages/HooksDemo.jsx +0 -194
- package/template/pages/test.jsx +0 -72
- package/template/scripts/build.js +0 -81
- package/template/scripts/generate-imports.js +0 -88
- package/template/tailwind.config.js +0 -17
package/template/api/users.js
CHANGED
|
@@ -1,155 +1,59 @@
|
|
|
1
1
|
// API Route: /api/users
|
|
2
|
-
// Next.js
|
|
2
|
+
// Next.js App Router style
|
|
3
|
+
|
|
4
|
+
const { NextResponse } = require('../_framework/next-response');
|
|
3
5
|
|
|
4
|
-
// Mock database (in production, use a real database)
|
|
5
6
|
let users = [
|
|
6
7
|
{ id: 1, name: 'Aziz Ahmed', email: 'aziz@example.com', createdAt: new Date().toISOString() },
|
|
7
8
|
{ id: 2, name: 'John Doe', email: 'john@example.com', createdAt: new Date().toISOString() },
|
|
8
9
|
{ id: 3, name: 'Jane Smith', email: 'jane@example.com', createdAt: new Date().toISOString() }
|
|
9
10
|
];
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
switch (method) {
|
|
16
|
-
case 'GET':
|
|
17
|
-
// GET /api/users - Get all users
|
|
18
|
-
// GET /api/users?id=1 - Get specific user
|
|
19
|
-
if (query.id) {
|
|
20
|
-
const user = users.find(u => u.id === parseInt(query.id));
|
|
21
|
-
if (!user) {
|
|
22
|
-
return res.status(404).json({
|
|
23
|
-
error: 'User not found',
|
|
24
|
-
message: `No user found with id ${query.id}`
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
return res.status(200).json(user);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Return all users
|
|
31
|
-
return res.status(200).json({
|
|
32
|
-
users,
|
|
33
|
-
total: users.length,
|
|
34
|
-
timestamp: new Date().toISOString()
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
case 'POST':
|
|
38
|
-
// POST /api/users - Create new user
|
|
39
|
-
const { name, email } = body;
|
|
40
|
-
|
|
41
|
-
// Validation
|
|
42
|
-
if (!name || !email) {
|
|
43
|
-
return res.status(400).json({
|
|
44
|
-
error: 'Validation error',
|
|
45
|
-
message: 'Name and email are required',
|
|
46
|
-
fields: {
|
|
47
|
-
name: !name ? 'Name is required' : null,
|
|
48
|
-
email: !email ? 'Email is required' : null
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
}
|
|
12
|
+
async function GET(request) {
|
|
13
|
+
const { searchParams } = new URL(request.url);
|
|
14
|
+
const id = searchParams.get('id');
|
|
52
15
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Check if email already exists
|
|
61
|
-
if (users.find(u => u.email === email)) {
|
|
62
|
-
return res.status(409).json({
|
|
63
|
-
error: 'Conflict',
|
|
64
|
-
message: 'User with this email already exists'
|
|
65
|
-
});
|
|
66
|
-
}
|
|
16
|
+
if (id) {
|
|
17
|
+
const user = users.find(u => u.id === parseInt(id));
|
|
18
|
+
if (!user) return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
|
19
|
+
return NextResponse.json(user);
|
|
20
|
+
}
|
|
67
21
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
id: users.length > 0 ? Math.max(...users.map(u => u.id)) + 1 : 1,
|
|
71
|
-
name,
|
|
72
|
-
email,
|
|
73
|
-
createdAt: new Date().toISOString()
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
users.push(newUser);
|
|
77
|
-
|
|
78
|
-
return res.status(201).json({
|
|
79
|
-
message: 'User created successfully',
|
|
80
|
-
user: newUser
|
|
81
|
-
});
|
|
22
|
+
return NextResponse.json({ users, total: users.length });
|
|
23
|
+
}
|
|
82
24
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const userId = query.id || body.id;
|
|
86
|
-
|
|
87
|
-
if (!userId) {
|
|
88
|
-
return res.status(400).json({
|
|
89
|
-
error: 'Bad request',
|
|
90
|
-
message: 'User ID is required'
|
|
91
|
-
});
|
|
92
|
-
}
|
|
25
|
+
async function POST(request) {
|
|
26
|
+
const { name, email } = await request.json();
|
|
93
27
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
return res.status(404).json({
|
|
98
|
-
error: 'User not found',
|
|
99
|
-
message: `No user found with id ${userId}`
|
|
100
|
-
});
|
|
101
|
-
}
|
|
28
|
+
if (!name || !email) {
|
|
29
|
+
return NextResponse.json({ error: 'Name and email are required' }, { status: 400 });
|
|
30
|
+
}
|
|
102
31
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
...body,
|
|
107
|
-
id: users[userIndex].id, // Prevent ID change
|
|
108
|
-
updatedAt: new Date().toISOString()
|
|
109
|
-
};
|
|
32
|
+
if (users.find(u => u.email === email)) {
|
|
33
|
+
return NextResponse.json({ error: 'Email already exists' }, { status: 409 });
|
|
34
|
+
}
|
|
110
35
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
36
|
+
const newUser = {
|
|
37
|
+
id: Math.max(...users.map(u => u.id)) + 1,
|
|
38
|
+
name, email,
|
|
39
|
+
createdAt: new Date().toISOString()
|
|
40
|
+
};
|
|
41
|
+
users.push(newUser);
|
|
115
42
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const deleteId = query.id;
|
|
119
|
-
|
|
120
|
-
if (!deleteId) {
|
|
121
|
-
return res.status(400).json({
|
|
122
|
-
error: 'Bad request',
|
|
123
|
-
message: 'User ID is required'
|
|
124
|
-
});
|
|
125
|
-
}
|
|
43
|
+
return NextResponse.json({ message: 'User created', user: newUser }, { status: 201 });
|
|
44
|
+
}
|
|
126
45
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
return res.status(404).json({
|
|
131
|
-
error: 'User not found',
|
|
132
|
-
message: `No user found with id ${deleteId}`
|
|
133
|
-
});
|
|
134
|
-
}
|
|
46
|
+
async function DELETE(request) {
|
|
47
|
+
const { searchParams } = new URL(request.url);
|
|
48
|
+
const id = searchParams.get('id');
|
|
135
49
|
|
|
136
|
-
|
|
50
|
+
if (!id) return NextResponse.json({ error: 'ID required' }, { status: 400 });
|
|
137
51
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
user: deletedUser
|
|
141
|
-
});
|
|
52
|
+
const idx = users.findIndex(u => u.id === parseInt(id));
|
|
53
|
+
if (idx === -1) return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
|
142
54
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
res.setHeader('Allow', ['GET', 'POST', 'PUT', 'DELETE']);
|
|
146
|
-
return res.status(405).json({
|
|
147
|
-
error: 'Method not allowed',
|
|
148
|
-
message: `Method ${method} is not allowed for this endpoint`,
|
|
149
|
-
allowedMethods: ['GET', 'POST', 'PUT', 'DELETE']
|
|
150
|
-
});
|
|
151
|
-
}
|
|
55
|
+
const deleted = users.splice(idx, 1)[0];
|
|
56
|
+
return NextResponse.json({ message: 'User deleted', user: deleted });
|
|
152
57
|
}
|
|
153
58
|
|
|
154
|
-
|
|
155
|
-
module.exports = handler;
|
|
59
|
+
module.exports = { GET, POST, DELETE };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** @jsx h */
|
|
2
|
+
import { h } from '/_framework/runtime.js';
|
|
3
|
+
import { navigate } from '/_framework/router.js';
|
|
4
|
+
|
|
5
|
+
// About page — route: /about
|
|
6
|
+
export default function Page() {
|
|
7
|
+
return (
|
|
8
|
+
<div style={{ padding: '40px 32px', maxWidth: '900px', margin: '0 auto' }}>
|
|
9
|
+
<h1>About 📖</h1>
|
|
10
|
+
|
|
11
|
+
<div class="card">
|
|
12
|
+
<h2>What is Aziz.js?</h2>
|
|
13
|
+
<p>
|
|
14
|
+
A lightweight Next.js clone — file-based routing, React hooks,
|
|
15
|
+
server-side API routes, and ES module imports. Zero dependencies.
|
|
16
|
+
</p>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<div class="card">
|
|
20
|
+
<h2>File Structure</h2>
|
|
21
|
+
<pre style={{ background: '#282c34', color: '#61dafb', padding: '16px', borderRadius: '8px', fontSize: '13px' }}>
|
|
22
|
+
{`app/
|
|
23
|
+
layout.js ← root layout
|
|
24
|
+
page.js ← /
|
|
25
|
+
about/
|
|
26
|
+
page.js ← /about
|
|
27
|
+
blog/
|
|
28
|
+
[slug]/
|
|
29
|
+
page.js ← /blog/:slug
|
|
30
|
+
|
|
31
|
+
api/
|
|
32
|
+
users.js ← /api/users
|
|
33
|
+
|
|
34
|
+
components/
|
|
35
|
+
Navbar.js
|
|
36
|
+
Footer.js`}
|
|
37
|
+
</pre>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<button class="btn btn-primary" onClick={() => navigate('/')}>
|
|
41
|
+
← Back Home
|
|
42
|
+
</button>
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** @jsx h */
|
|
2
|
+
import { h } from '/_framework/runtime.js';
|
|
3
|
+
import Navbar from '/components/Navbar.js';
|
|
4
|
+
import Footer from '/components/Footer.js';
|
|
5
|
+
|
|
6
|
+
// Root layout — wraps every page (like Next.js app/layout.js)
|
|
7
|
+
export default function Layout({ children }) {
|
|
8
|
+
return (
|
|
9
|
+
<div style={{ display: 'flex', flexDirection: 'column', minHeight: '100vh' }}>
|
|
10
|
+
<Navbar />
|
|
11
|
+
<main style={{ flex: 1 }}>
|
|
12
|
+
{children}
|
|
13
|
+
</main>
|
|
14
|
+
<Footer />
|
|
15
|
+
</div>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Optional: export metadata
|
|
20
|
+
export const metadata = {
|
|
21
|
+
title: 'My App',
|
|
22
|
+
description: 'Built with Aziz.js'
|
|
23
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/** @jsx h */
|
|
2
|
+
import { h } from '/_framework/runtime.js';
|
|
3
|
+
import { navigate } from '/_framework/router.js';
|
|
4
|
+
|
|
5
|
+
// Home page — route: /
|
|
6
|
+
export default function Page() {
|
|
7
|
+
const [count, setCount] = useState(0);
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<div style={{ padding: '40px 32px', maxWidth: '900px', margin: '0 auto' }}>
|
|
11
|
+
<h1>Welcome Home 👋</h1>
|
|
12
|
+
|
|
13
|
+
<div class="card">
|
|
14
|
+
<h2>Counter</h2>
|
|
15
|
+
<p>useState works exactly like React!</p>
|
|
16
|
+
<div class="count-display">{count}</div>
|
|
17
|
+
<div class="counter-actions">
|
|
18
|
+
<button class="btn btn-danger" onClick={() => setCount(c => c - 1)}>−</button>
|
|
19
|
+
<button class="btn btn-primary" onClick={() => setCount(c => c + 1)}>+</button>
|
|
20
|
+
<button class="btn btn-grey" onClick={() => setCount(0)}>Reset</button>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<div class="card">
|
|
25
|
+
<h2>File-based Routing</h2>
|
|
26
|
+
<p>
|
|
27
|
+
Each <code>page.js</code> inside <code>app/</code> is a route — just like Next.js.
|
|
28
|
+
Create <code>app/about/page.js</code> and it becomes <code>/about</code>.
|
|
29
|
+
</p>
|
|
30
|
+
<div style={{ display: 'flex', gap: '12px' }}>
|
|
31
|
+
<button class="btn btn-primary" onClick={() => navigate('/about')}>
|
|
32
|
+
Go to About →
|
|
33
|
+
</button>
|
|
34
|
+
<button class="btn btn-primary" onClick={() => navigate('/test')}>
|
|
35
|
+
Go to Test →
|
|
36
|
+
</button>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<div class="card">
|
|
41
|
+
<h2>Import Anything</h2>
|
|
42
|
+
<p>
|
|
43
|
+
Use <code>import</code> statements just like Next.js.
|
|
44
|
+
The dev server resolves bare imports from <code>node_modules</code>.
|
|
45
|
+
</p>
|
|
46
|
+
<pre style={{ background: '#282c34', color: '#61dafb', padding: '16px', borderRadius: '8px', overflow: 'auto', fontSize: '13px' }}>
|
|
47
|
+
{`import mongoose from 'mongoose';
|
|
48
|
+
import connectDB from '@/lib/db';
|
|
49
|
+
import User from '@/models/User';`}
|
|
50
|
+
</pre>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** @jsx h */
|
|
2
|
+
import { h } from '/_framework/runtime.js';
|
|
3
|
+
|
|
4
|
+
// Test page — route: /test
|
|
5
|
+
export default function Page() {
|
|
6
|
+
const [count, setCount] = useState(0);
|
|
7
|
+
const [name, setName] = useState('');
|
|
8
|
+
const [email, setEmail] = useState('');
|
|
9
|
+
|
|
10
|
+
const handleSubmit = (e) => {
|
|
11
|
+
e.preventDefault();
|
|
12
|
+
alert(`Name: ${name}\nEmail: ${email}\nCount: ${count}`);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<div style={{ padding: '40px 32px', maxWidth: '900px', margin: '0 auto' }}>
|
|
17
|
+
<h1>Test Page 🧪</h1>
|
|
18
|
+
|
|
19
|
+
<div class="card">
|
|
20
|
+
<h2>useState</h2>
|
|
21
|
+
<div class="count-display">{count}</div>
|
|
22
|
+
<div class="counter-actions">
|
|
23
|
+
<button class="btn btn-primary" onClick={() => setCount(c => c + 1)}>+</button>
|
|
24
|
+
<button class="btn btn-danger" onClick={() => setCount(0)}>Reset</button>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<div class="card">
|
|
29
|
+
<h2>Form</h2>
|
|
30
|
+
<form onSubmit={handleSubmit}>
|
|
31
|
+
<input
|
|
32
|
+
type="text"
|
|
33
|
+
value={name}
|
|
34
|
+
onInput={(e) => setName(e.target.value)}
|
|
35
|
+
placeholder="Your name"
|
|
36
|
+
/>
|
|
37
|
+
<input
|
|
38
|
+
type="email"
|
|
39
|
+
value={email}
|
|
40
|
+
onInput={(e) => setEmail(e.target.value)}
|
|
41
|
+
placeholder="Your email"
|
|
42
|
+
/>
|
|
43
|
+
<button type="submit" class="btn btn-primary">Submit</button>
|
|
44
|
+
</form>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<div class="card">
|
|
48
|
+
<h2>State</h2>
|
|
49
|
+
<p>Name: <strong>{name || '—'}</strong></p>
|
|
50
|
+
<p>Email: <strong>{email || '—'}</strong></p>
|
|
51
|
+
<p>Count: <strong>{count}</strong></p>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** @jsx h */
|
|
2
|
+
import { h } from '/_framework/runtime.js';
|
|
3
|
+
import { navigate, useRouter } from '/_framework/router.js';
|
|
4
|
+
|
|
5
|
+
export default function Navbar() {
|
|
6
|
+
const { pathname } = useRouter();
|
|
7
|
+
|
|
8
|
+
const links = [
|
|
9
|
+
{ href: '/', label: 'Home' },
|
|
10
|
+
{ href: '/about', label: 'About' },
|
|
11
|
+
{ href: '/test', label: 'Test' },
|
|
12
|
+
{ href: '/api-demo', label: 'API Demo' },
|
|
13
|
+
{ href: '/contact', label: 'Contact' },
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<nav>
|
|
18
|
+
<span class="brand" onClick={() => navigate('/')}>⚡ Aziz.js</span>
|
|
19
|
+
{links.map(({ href, label }) =>
|
|
20
|
+
<a
|
|
21
|
+
href={href}
|
|
22
|
+
class={pathname === href ? 'active' : ''}
|
|
23
|
+
onClick={(e) => { e.preventDefault(); navigate(href); }}
|
|
24
|
+
>
|
|
25
|
+
{label}
|
|
26
|
+
</a>
|
|
27
|
+
)}
|
|
28
|
+
</nav>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
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
|
+
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
|
7
|
+
<title>My App</title>
|
|
8
|
+
|
|
9
|
+
<!-- Global Styles -->
|
|
10
|
+
<link rel="stylesheet" href="/styles/globals.css">
|
|
11
|
+
|
|
12
|
+
<!-- Babel for JSX (loaded before modules) -->
|
|
13
|
+
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
|
|
14
|
+
</head>
|
|
15
|
+
<body>
|
|
16
|
+
<div id="root"></div>
|
|
17
|
+
|
|
18
|
+
<!-- Boot the framework -->
|
|
19
|
+
<script type="module" src="/_framework/boot.js"></script>
|
|
20
|
+
|
|
21
|
+
<!-- Live Reload (dev only) -->
|
|
22
|
+
<script src="/scripts/live-reload-client.js"></script>
|
|
23
|
+
</body>
|
|
24
|
+
</html>
|
package/template/package.json
CHANGED
|
@@ -1,18 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "aziz-app",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "
|
|
5
|
-
"main": "app.html",
|
|
2
|
+
"name": "my-aziz-app",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Built with Aziz.js",
|
|
6
5
|
"scripts": {
|
|
7
|
-
"dev": "node scripts/dev-server.js"
|
|
8
|
-
"build": "node scripts/generate-imports.js"
|
|
6
|
+
"dev": "node scripts/dev-server.js"
|
|
9
7
|
},
|
|
10
|
-
"
|
|
11
|
-
"react",
|
|
12
|
-
"framework",
|
|
13
|
-
"routing",
|
|
14
|
-
"state-management"
|
|
15
|
-
],
|
|
16
|
-
"author": "",
|
|
17
|
-
"license": "ISC"
|
|
8
|
+
"license": "MIT"
|
|
18
9
|
}
|