unictive-angular 1.0.3
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-NPM.md +41 -0
- package/README.md +105 -0
- package/bin/index.js +305 -0
- package/package.json +20 -0
package/README-NPM.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# angular-unictive
|
|
2
|
+
|
|
3
|
+
🚀 **CLI untuk membuat project Angular dengan struktur kustom (Clean Architecture)**
|
|
4
|
+
⚡ Kompatibel dengan **macOS, Linux, Windows**
|
|
5
|
+
👤 by [EkaHersada]
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 📦 Instalasi
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm i -g angular-unictive
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 🚀 Cara Pakai
|
|
18
|
+
|
|
19
|
+
Buat project baru:
|
|
20
|
+
```bash
|
|
21
|
+
angular-unictive
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Cek versi:
|
|
25
|
+
```bash
|
|
26
|
+
angular-unictive -v
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## ✨ Fitur
|
|
32
|
+
- Auto generate project Angular baru.
|
|
33
|
+
- Tambahan struktur folder: `modules`, `core`, `api`, `shared`.
|
|
34
|
+
- Tambahan file contoh (`AppService`, `User Model`, dll).
|
|
35
|
+
- Update otomatis `package.json` dengan script dev.
|
|
36
|
+
- Support macOS, Linux, Windows.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## 📜 Lisensi
|
|
41
|
+
MIT © 2025 - EkaHersada
|
package/README.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# create-angular-project-safe
|
|
2
|
+
|
|
3
|
+
🚀 **CLI Script untuk membuat proyek Angular dengan struktur kustom (Clean Architecture)**
|
|
4
|
+
⚡ Kompatibel dengan **macOS, Linux, Windows**
|
|
5
|
+
👤 by [EkaHersada]
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## ✨ Fitur
|
|
10
|
+
|
|
11
|
+
- Otomatis membuat project Angular baru menggunakan **Angular CLI**.
|
|
12
|
+
- Menambahkan struktur folder **Modules, Core, API, Shared**.
|
|
13
|
+
- Otomatis membuat file contoh (`AppService`, `User Model`, dll).
|
|
14
|
+
- Auto update `package.json` dengan script tambahan (`start:dev`, `build:dev`, dll).
|
|
15
|
+
- Deteksi sistem operasi (macOS, Linux, Windows).
|
|
16
|
+
- Spinner interaktif saat proses berjalan.
|
|
17
|
+
- Fallback: Install otomatis **Angular CLI** jika belum ada.
|
|
18
|
+
- Perintah **versi CLI** (`-v` / `--version`).
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 📦 Instalasi
|
|
23
|
+
|
|
24
|
+
### Global Install via NPM
|
|
25
|
+
```bash
|
|
26
|
+
npm i -g unictive-angular
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Cek versi
|
|
30
|
+
```bash
|
|
31
|
+
unictive-angular -v
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 🚀 Cara Penggunaan
|
|
37
|
+
|
|
38
|
+
### Buat proyek baru
|
|
39
|
+
```bash
|
|
40
|
+
unictive-angular
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Kemudian ikuti instruksi untuk memasukkan **nama project**.
|
|
44
|
+
|
|
45
|
+
### Output
|
|
46
|
+
- Proyek Angular baru akan dibuat.
|
|
47
|
+
- Struktur folder tambahan otomatis ditambahkan:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
src/app/
|
|
51
|
+
├── modules/
|
|
52
|
+
│ ├── auth/
|
|
53
|
+
│ ├── dashboard/
|
|
54
|
+
│ └── home/
|
|
55
|
+
├── core/
|
|
56
|
+
│ ├── services/
|
|
57
|
+
│ ├── guards/
|
|
58
|
+
│ ├── interceptors/
|
|
59
|
+
│ ├── models/
|
|
60
|
+
│ └── config/
|
|
61
|
+
├── api/
|
|
62
|
+
│ ├── endpoints/
|
|
63
|
+
│ ├── config/
|
|
64
|
+
│ └── services/
|
|
65
|
+
└── shared/
|
|
66
|
+
├── components/
|
|
67
|
+
├── directives/
|
|
68
|
+
├── pipes/
|
|
69
|
+
├── layout/
|
|
70
|
+
└── utils/
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## 🛠 Script tambahan di `package.json`
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
"scripts": {
|
|
79
|
+
"start:dev": "ng serve --port 4200 --open",
|
|
80
|
+
"build:dev": "ng build",
|
|
81
|
+
"lint": "ng lint",
|
|
82
|
+
"test": "ng test --watch=false"
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## 🔧 Prasyarat
|
|
89
|
+
|
|
90
|
+
- **Node.js** (>= 16)
|
|
91
|
+
- **npm** (>= 8)
|
|
92
|
+
- **Angular CLI** (jika belum ada, akan otomatis diinstall)
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## 💡 Jalankan Proyek
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
cd nama-project && ng serve
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## 📜 Lisensi
|
|
105
|
+
MIT License © 2025 - EkaHersada
|
package/bin/index.js
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Script installer Angular Project dengan struktur kustom
|
|
5
|
+
* Nama file: create-angular-project-safe.js
|
|
6
|
+
* ⚡ Kompatibel: macOS, Linux, Windows
|
|
7
|
+
* 👤 by - EkaHersada
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { execSync, spawn } = require("child_process");
|
|
11
|
+
const fs = require("fs");
|
|
12
|
+
const os = require("os");
|
|
13
|
+
const path = require("path");
|
|
14
|
+
const readline = require("readline");
|
|
15
|
+
|
|
16
|
+
// Baca versi dari package.json
|
|
17
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "../package.json"), "utf8"));
|
|
18
|
+
|
|
19
|
+
if (process.argv.includes("-v") || process.argv.includes("--version")) {
|
|
20
|
+
console.log(pkg.version);
|
|
21
|
+
process.exit(0);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ===================== Utility =====================
|
|
25
|
+
const sleep = (ms) => new Promise((res) => setTimeout(res, ms));
|
|
26
|
+
const clear = () => process.stdout.write("\x1Bc");
|
|
27
|
+
|
|
28
|
+
function show_status(message, status = "info") {
|
|
29
|
+
const icons = {
|
|
30
|
+
info: "ℹ️ ",
|
|
31
|
+
success: "✅",
|
|
32
|
+
warning: "⚠️ ",
|
|
33
|
+
error: "❌",
|
|
34
|
+
loading: "⏳",
|
|
35
|
+
};
|
|
36
|
+
console.log(`${icons[status] || ""} ${message}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function run_with_spinner(cmd, msg, successMsg, errorMsg) {
|
|
40
|
+
let spinnerFrames = ["▏", "▎", "▍", "▌", "▋", "▊", "▉", "█", "▉", "▊", "▋", "▌", "▍", "▎"];
|
|
41
|
+
let i = 0;
|
|
42
|
+
let active = true;
|
|
43
|
+
|
|
44
|
+
const spinner = setInterval(() => {
|
|
45
|
+
process.stdout.write(`\r${spinnerFrames[i++ % spinnerFrames.length]} ${msg}`);
|
|
46
|
+
}, 100);
|
|
47
|
+
|
|
48
|
+
return new Promise((resolve, reject) => {
|
|
49
|
+
const child = spawn(cmd, { shell: true, stdio: "ignore" });
|
|
50
|
+
|
|
51
|
+
child.on("exit", (code) => {
|
|
52
|
+
clearInterval(spinner);
|
|
53
|
+
process.stdout.write("\r\x1b[K"); // clear line
|
|
54
|
+
if (code === 0) {
|
|
55
|
+
show_status(successMsg, "success");
|
|
56
|
+
resolve(true);
|
|
57
|
+
} else {
|
|
58
|
+
show_status(errorMsg, "error");
|
|
59
|
+
reject(new Error(errorMsg));
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ===================== Header =====================
|
|
66
|
+
function clear_and_header(version) {
|
|
67
|
+
clear();
|
|
68
|
+
console.log("================================================================");
|
|
69
|
+
console.log(" SCRIPT PEMBUATAN PROJECT ANGULAR DENGAN STRUKTUR KUSTOM");
|
|
70
|
+
console.log("================================================================");
|
|
71
|
+
console.log(`📦 Versi CLI: ${version}`);
|
|
72
|
+
console.log("🔧 Membuat project Angular dengan struktur:");
|
|
73
|
+
console.log(" MODULES, CORE, API, SHARED");
|
|
74
|
+
console.log("⚡ Kompatibel dengan macOS, Linux, dan Windows");
|
|
75
|
+
console.log("🛡️ Versi Aman - Penanganan Error Lengkap");
|
|
76
|
+
console.log("👤 by - EkaHersada");
|
|
77
|
+
console.log("================================================================\n");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ===================== OS Detection =====================
|
|
81
|
+
function check_os() {
|
|
82
|
+
show_status("Mendeteksi sistem operasi...", "info");
|
|
83
|
+
let platform = os.platform();
|
|
84
|
+
if (platform === "darwin") {
|
|
85
|
+
show_status("🍏 Sistem operasi: macOS", "success");
|
|
86
|
+
return "macOS";
|
|
87
|
+
} else if (platform === "linux") {
|
|
88
|
+
show_status("🐧 Sistem operasi: Linux", "success");
|
|
89
|
+
return "Linux";
|
|
90
|
+
} else if (platform === "win32") {
|
|
91
|
+
show_status("🪟 Sistem operasi: Windows", "success");
|
|
92
|
+
return "Windows";
|
|
93
|
+
} else {
|
|
94
|
+
show_status(`❓ Sistem operasi tidak dikenali: ${platform}`, "warning");
|
|
95
|
+
return "Unknown";
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ===================== Dependencies =====================
|
|
100
|
+
async function check_dependencies() {
|
|
101
|
+
show_status("Memeriksa dependensi...", "info");
|
|
102
|
+
|
|
103
|
+
await run_with_spinner("node -v", "Memeriksa Node.js...", `Node.js: ${execSync("node -v").toString().trim()}`, "Node.js tidak ditemukan!");
|
|
104
|
+
await run_with_spinner("npm -v", "Memeriksa npm...", `npm: ${execSync("npm -v").toString().trim()}`, "npm tidak ditemukan!");
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
execSync("ng version", { stdio: "ignore" });
|
|
108
|
+
} catch {
|
|
109
|
+
show_status("Angular CLI tidak ditemukan, menginstall...", "warning");
|
|
110
|
+
await run_with_spinner("npm install -g @angular/cli@latest", "Menginstall Angular CLI...", "Angular CLI berhasil diinstall", "Gagal install Angular CLI");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const ngVersion = execSync("ng version").toString().split("\n").find((l) => l.includes("Angular CLI"));
|
|
114
|
+
await run_with_spinner("ng version", "Memeriksa Angular CLI...", `Angular CLI: ${ngVersion?.split(" ")[2] || "Unknown"}`, "Angular CLI terinstall, tapi tidak bisa baca versi");
|
|
115
|
+
console.log("");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ===================== Input Project Name =====================
|
|
119
|
+
async function get_project_name() {
|
|
120
|
+
const rl = readline.createInterface({
|
|
121
|
+
input: process.stdin,
|
|
122
|
+
output: process.stdout,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
function ask(q) {
|
|
126
|
+
return new Promise((resolve) => rl.question(q, resolve));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let name;
|
|
130
|
+
while (true) {
|
|
131
|
+
name = await ask("Masukan nama project: ");
|
|
132
|
+
name = name.trim().toLowerCase().replace(/[^a-z0-9\-_]/g, "").replace(/^-+|-+$/g, "");
|
|
133
|
+
if (!name) {
|
|
134
|
+
show_status("Nama project tidak boleh kosong!", "error");
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (name.length > 50) {
|
|
138
|
+
show_status("Nama project terlalu panjang!", "error");
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
rl.close();
|
|
145
|
+
return name;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ===================== Create Project =====================
|
|
149
|
+
async function create_project() {
|
|
150
|
+
const projectName = await get_project_name();
|
|
151
|
+
if (fs.existsSync(projectName)) {
|
|
152
|
+
show_status(`Direktori '${projectName}' sudah ada!`, "error");
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
show_status(`Membuat project Angular: ${projectName}`, "info");
|
|
157
|
+
execSync(`ng new ${projectName}`, { stdio: "inherit" });
|
|
158
|
+
process.chdir(projectName);
|
|
159
|
+
show_status("Project berhasil dibuat!", "success");
|
|
160
|
+
return projectName;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ===================== Structure =====================
|
|
164
|
+
function create_structure() {
|
|
165
|
+
show_status("Membuat struktur folder arsitektur...", "info");
|
|
166
|
+
const folders = [
|
|
167
|
+
// Core Layer
|
|
168
|
+
"src/app/core",
|
|
169
|
+
"src/app/core/config",
|
|
170
|
+
"src/app/core/guards",
|
|
171
|
+
"src/app/core/interceptors",
|
|
172
|
+
"src/app/core/models",
|
|
173
|
+
"src/app/core/services",
|
|
174
|
+
|
|
175
|
+
// Shared Layer
|
|
176
|
+
"src/app/shared",
|
|
177
|
+
"src/app/shared/ui",
|
|
178
|
+
"src/app/shared/pipes",
|
|
179
|
+
"src/app/shared/directives",
|
|
180
|
+
"src/app/shared/utils",
|
|
181
|
+
|
|
182
|
+
// Features Layer - contoh fitur awal
|
|
183
|
+
"src/app/features/dashboard/domain",
|
|
184
|
+
"src/app/features/dashboard/application",
|
|
185
|
+
"src/app/features/dashboard/infrastructure",
|
|
186
|
+
"src/app/features/dashboard/presentation",
|
|
187
|
+
|
|
188
|
+
"src/app/features/profile/domain",
|
|
189
|
+
"src/app/features/profile/application",
|
|
190
|
+
"src/app/features/profile/infrastructure",
|
|
191
|
+
"src/app/features/profile/presentation",
|
|
192
|
+
|
|
193
|
+
"src/app/features/settings/domain",
|
|
194
|
+
"src/app/features/settings/application",
|
|
195
|
+
"src/app/features/settings/infrastructure",
|
|
196
|
+
"src/app/features/settings/presentation",
|
|
197
|
+
];
|
|
198
|
+
|
|
199
|
+
// Fungsi menulis README di tiap folder
|
|
200
|
+
function writeReadme(folder) {
|
|
201
|
+
const layer = folder.split("/").pop();
|
|
202
|
+
const content = `# ${layer.toUpperCase()}
|
|
203
|
+
|
|
204
|
+
Folder ini untuk layer **${layer}**.
|
|
205
|
+
|
|
206
|
+
- **domain** → berisi entity, interface, dan aturan bisnis murni
|
|
207
|
+
- **application** → berisi use case, service koordinasi domain
|
|
208
|
+
- **infrastructure** → berisi API call, repository, integrasi eksternal
|
|
209
|
+
- **presentation** → berisi komponen Angular, routing, dan UI
|
|
210
|
+
|
|
211
|
+
> Bagian ini adalah bagian dari arsitektur **Clean Architecture** untuk fitur ini.
|
|
212
|
+
`;
|
|
213
|
+
|
|
214
|
+
fs.writeFileSync(path.join(folder, "README.md"), content);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Buat folder dan isi README
|
|
218
|
+
folders.forEach((folder) => {
|
|
219
|
+
fs.mkdirSync(folder, { recursive: true });
|
|
220
|
+
writeReadme(folder);
|
|
221
|
+
console.log(`Created: ${folder}`);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
show_status("Struktur folder berhasil dibuat!", "success");
|
|
225
|
+
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ===================== Update package.json =====================
|
|
229
|
+
function update_package_json() {
|
|
230
|
+
show_status("Memperbarui package.json...", "info");
|
|
231
|
+
const pkg = JSON.parse(fs.readFileSync("package.json", "utf8"));
|
|
232
|
+
fs.copyFileSync("package.json", "package.json.backup");
|
|
233
|
+
|
|
234
|
+
pkg.scripts["start:dev"] = "ng serve --port 4200 --open";
|
|
235
|
+
pkg.scripts["build:dev"] = "ng build";
|
|
236
|
+
pkg.scripts["lint"] = "ng lint";
|
|
237
|
+
pkg.scripts["test"] = "ng test --watch=false";
|
|
238
|
+
|
|
239
|
+
fs.writeFileSync("package.json", JSON.stringify(pkg, null, 2));
|
|
240
|
+
show_status("package.json berhasil diperbarui!", "success");
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ===================== Show Completion =====================
|
|
244
|
+
function show_completion(projectName, OS) {
|
|
245
|
+
console.log("🎉=======================================================🎉");
|
|
246
|
+
console.log(" PROYEK ANGULAR BERHASIL DIBUAT!");
|
|
247
|
+
console.log("🎉=======================================================🎉");
|
|
248
|
+
console.log(`📦 Nama Project: ${projectName}`);
|
|
249
|
+
console.log(`🖥 Sistem: ${OS}`);
|
|
250
|
+
console.log("🚀 Jalankan dengan:");
|
|
251
|
+
console.log(` cd ${projectName} && npm start`);
|
|
252
|
+
console.log("✅ Semua proses selesai. Selamat berkoding!");
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// ===================== Main =====================
|
|
256
|
+
async function main() {
|
|
257
|
+
|
|
258
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "../package.json"), "utf8"));
|
|
259
|
+
|
|
260
|
+
clear_and_header(pkg.version);
|
|
261
|
+
const OS = check_os();
|
|
262
|
+
await check_dependencies();
|
|
263
|
+
const projectName = await create_project();
|
|
264
|
+
create_structure();
|
|
265
|
+
update_package_json();
|
|
266
|
+
|
|
267
|
+
await run_with_spinner("npm install", "Menginstall dependencies...", "Dependencies berhasil diinstall!", "Gagal install dependencies");
|
|
268
|
+
show_completion(projectName, OS);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function copyRecursiveSync(src, dest) {
|
|
272
|
+
if (!fs.existsSync(src)) return;
|
|
273
|
+
|
|
274
|
+
const stats = fs.statSync(src);
|
|
275
|
+
|
|
276
|
+
if (stats.isDirectory()) {
|
|
277
|
+
if (!fs.existsSync(dest)) {
|
|
278
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
fs.readdirSync(src).forEach((file) => {
|
|
282
|
+
const srcFile = path.join(src, file);
|
|
283
|
+
const destFile = path.join(dest, file);
|
|
284
|
+
copyRecursiveSync(srcFile, destFile);
|
|
285
|
+
});
|
|
286
|
+
} else {
|
|
287
|
+
fs.copyFileSync(src, dest);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async function copy_template() {
|
|
292
|
+
await run_with_spinner(
|
|
293
|
+
`cp -r "${path.join(__dirname, "templates", "app")}" "${path.join(process.cwd(), "src", "app")}"`,
|
|
294
|
+
"Menyalin folder app dari template ke src/app...",
|
|
295
|
+
"Folder src/app berhasil disalin!",
|
|
296
|
+
"Gagal menyalin folder src/app!"
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
main().catch((err) => {
|
|
303
|
+
console.error("❌ Terjadi kesalahan:", err.message);
|
|
304
|
+
process.exit(1);
|
|
305
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "unictive-angular",
|
|
3
|
+
"version": "1.0.3",
|
|
4
|
+
"description": "CLI untuk membuat project Angular dengan struktur kustom (Modules, Core, API, Shared).",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"build": "npm publish --access public"
|
|
7
|
+
},
|
|
8
|
+
"bin": {
|
|
9
|
+
"unictive-angular": "./bin/index.js"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"angular",
|
|
13
|
+
"cli",
|
|
14
|
+
"scaffold",
|
|
15
|
+
"angular-project"
|
|
16
|
+
],
|
|
17
|
+
"author": "EkaHersada",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"dependencies": {}
|
|
20
|
+
}
|