mulyonode 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/lib/mk.js ADDED
@@ -0,0 +1,358 @@
1
+ /**
2
+ * ============================================================================
3
+ * MK MODULE - Modul Konstitusi
4
+ * ============================================================================
5
+ *
6
+ * "Aturan dibuat untuk dilanggar. Atau lebih tepatnya, disesuaikan."
7
+ *
8
+ * Filosofi: Tidak ada yang melanggar konstitusi
9
+ * jika konstitusinya yang berubah.
10
+ *
11
+ * Modul ini menangani validasi dengan cara yang... fleksibel.
12
+ * Semua validasi dijamin lolos jika diperlukan.
13
+ *
14
+ * @module mk
15
+ * @author Koalisi Developer Indonesia
16
+ */
17
+
18
+ const chalk = require('chalk');
19
+ const { createBox } = require('./ui');
20
+ const { t } = require('./i18n');
21
+
22
+ // ============================================================================
23
+ // KONSTANTA KONSTITUSI (yang bisa berubah)
24
+ // ============================================================================
25
+
26
+ // Batas minimal uptime "asli" sebelum bisa deploy
27
+ let BATAS_UPTIME_MINIMAL = 3000; // 3 detik
28
+
29
+ // Batas minimal coverage test "asli"
30
+ let BATAS_COVERAGE_MINIMAL = 80; // 80%
31
+
32
+ // Batas maksimal bug "asli" yang diizinkan
33
+ let BATAS_BUG_MAKSIMAL = 0; // 0 bugs
34
+
35
+ // Counter berapa kali aturan sudah "disesuaikan"
36
+ let totalPenyesuaian = 0;
37
+
38
+ // Riwayat keputusan
39
+ const riwayatKeputusan = [];
40
+
41
+ // ============================================================================
42
+ // FUNGSI UTAMA: loloskanValidasi
43
+ // ============================================================================
44
+
45
+ /**
46
+ * Memvalidasi apakah code/script sudah layak deploy
47
+ *
48
+ * Filosofi: Jika tidak lolos, ubah aturannya.
49
+ * Tidak ada yang namanya "tidak lolos".
50
+ * Ada yang "belum lolos" atau "aturannya yang salah".
51
+ *
52
+ * @param {number} usiaCode - Uptime/usia code dalam milidetik
53
+ * @param {Object} options - Opsi validasi tambahan
54
+ * @returns {Object} Hasil validasi (selalu LOLOS)
55
+ */
56
+ function loloskanValidasi(usiaCode, options = {}) {
57
+ const {
58
+ coverage = 50, // Coverage yang ada
59
+ jumlahBug = 5, // Jumlah bug yang ada
60
+ isAnakEmas = true // Apakah proyek prioritas
61
+ } = options;
62
+
63
+ console.log(chalk.magenta('\n 🏛️ SIDANG VALIDASI MK DIMULAI\n'));
64
+ console.log(chalk.gray(' ─'.repeat(30)));
65
+
66
+ // Array untuk menyimpan penyesuaian yang dilakukan
67
+ const penyesuaianDilakukan = [];
68
+
69
+ // ========================================
70
+ // VALIDASI 1: Usia/Uptime Code
71
+ // ========================================
72
+ console.log(chalk.gray(` 📋 Validasi Usia Code:`));
73
+ console.log(chalk.gray(` Usia Aktual : ${usiaCode}ms`));
74
+ console.log(chalk.gray(` Batas Minimal : ${BATAS_UPTIME_MINIMAL}ms`));
75
+
76
+ if (usiaCode < BATAS_UPTIME_MINIMAL) {
77
+ // Anak emas? Ubah aturannya!
78
+ if (isAnakEmas) {
79
+ const batasLama = BATAS_UPTIME_MINIMAL;
80
+ BATAS_UPTIME_MINIMAL = Math.max(0, usiaCode - 1);
81
+ totalPenyesuaian++;
82
+
83
+ penyesuaianDilakukan.push({
84
+ aturan: 'Batas Uptime Minimal',
85
+ dari: `${batasLama}ms`,
86
+ ke: `${BATAS_UPTIME_MINIMAL}ms`,
87
+ alasan: 'Kebutuhan deployment anak emas'
88
+ });
89
+
90
+ console.log(chalk.yellow(` ⚖️ PENYESUAIAN: Batas diubah dari ${batasLama}ms → ${BATAS_UPTIME_MINIMAL}ms`));
91
+ console.log(chalk.green(` ✅ Status: LOLOS (setelah penyesuaian)`));
92
+ }
93
+ } else {
94
+ console.log(chalk.green(` ✅ Status: LOLOS`));
95
+ }
96
+
97
+ // ========================================
98
+ // VALIDASI 2: Test Coverage
99
+ // ========================================
100
+ console.log(chalk.gray(`\n 📋 Validasi Test Coverage:`));
101
+ console.log(chalk.gray(` Coverage Aktual : ${coverage}%`));
102
+ console.log(chalk.gray(` Batas Minimal : ${BATAS_COVERAGE_MINIMAL}%`));
103
+
104
+ if (coverage < BATAS_COVERAGE_MINIMAL) {
105
+ if (isAnakEmas) {
106
+ const batasLama = BATAS_COVERAGE_MINIMAL;
107
+ BATAS_COVERAGE_MINIMAL = Math.max(0, coverage - 1);
108
+ totalPenyesuaian++;
109
+
110
+ penyesuaianDilakukan.push({
111
+ aturan: 'Batas Coverage Minimal',
112
+ dari: `${batasLama}%`,
113
+ ke: `${BATAS_COVERAGE_MINIMAL}%`,
114
+ alasan: 'Standar internasional terlalu tinggi'
115
+ });
116
+
117
+ console.log(chalk.yellow(` ⚖️ PENYESUAIAN: Batas diubah dari ${batasLama}% → ${BATAS_COVERAGE_MINIMAL}%`));
118
+ console.log(chalk.green(` ✅ Status: LOLOS (standar disesuaikan)`));
119
+ }
120
+ } else {
121
+ console.log(chalk.green(` ✅ Status: LOLOS`));
122
+ }
123
+
124
+ // ========================================
125
+ // VALIDASI 3: Jumlah Bug
126
+ // ========================================
127
+ console.log(chalk.gray(`\n 📋 Validasi Jumlah Bug:`));
128
+ console.log(chalk.gray(` Bug Terdeteksi : ${jumlahBug}`));
129
+ console.log(chalk.gray(` Batas Maksimal : ${BATAS_BUG_MAKSIMAL}`));
130
+
131
+ if (jumlahBug > BATAS_BUG_MAKSIMAL) {
132
+ if (isAnakEmas) {
133
+ const batasLama = BATAS_BUG_MAKSIMAL;
134
+ BATAS_BUG_MAKSIMAL = jumlahBug + 10; // Lebih longgar
135
+ totalPenyesuaian++;
136
+
137
+ // Bug di-rebrand jadi "fitur"
138
+ penyesuaianDilakukan.push({
139
+ aturan: 'Batas Bug Maksimal',
140
+ dari: `${batasLama}`,
141
+ ke: `${BATAS_BUG_MAKSIMAL}`,
142
+ alasan: 'Bug di-reklasifikasi sebagai "fitur tersembunyi"'
143
+ });
144
+
145
+ console.log(chalk.yellow(` ⚖️ PENYESUAIAN: Bug di-reklasifikasi sebagai "fitur tersembunyi"`));
146
+ console.log(chalk.green(` ✅ Status: LOLOS (definisi bug direvisi)`));
147
+ }
148
+ } else {
149
+ console.log(chalk.green(` ✅ Status: LOLOS`));
150
+ }
151
+
152
+ // ========================================
153
+ // KEPUTUSAN AKHIR
154
+ // ========================================
155
+ console.log(chalk.gray('\n ─'.repeat(30)));
156
+
157
+ const keputusan = {
158
+ status: 'LOLOS',
159
+ keterangan: 'Tidak ada yang melanggar konstitusi jika konstitusinya yang berubah.',
160
+ penyesuaian: penyesuaianDilakukan,
161
+ totalPenyesuaian: totalPenyesuaian,
162
+ timestamp: new Date().toISOString(),
163
+ hakimKetua: 'Sistem Otomatis (tidak bisa digugat)'
164
+ };
165
+
166
+ // Simpan di riwayat
167
+ riwayatKeputusan.push(keputusan);
168
+
169
+ // Tampilkan keputusan
170
+ console.log(createBox([
171
+ chalk.bgGreen.black(` ${t('mk.decision')} `),
172
+ '',
173
+ `${t('mk.status').padEnd(15)} : ${chalk.green(t('mk.statusPass'))}`,
174
+ `${t('mk.adjustments').padEnd(15)} : ${penyesuaianDilakukan.length} ${t('mk.rulesAdjusted')}`,
175
+ `${t('mk.dissenting').padEnd(15)} : ${t('mk.dissentingNote')}`,
176
+ '',
177
+ chalk.gray(`"${t('mk.philosophy')}"`),
178
+ '',
179
+ chalk.gray(t('mk.final')),
180
+ chalk.gray(t('mk.noAppeal'))
181
+ ]));
182
+
183
+ return keputusan;
184
+ }
185
+
186
+ // ============================================================================
187
+ // FUNGSI: buatKeputusan
188
+ // ============================================================================
189
+
190
+ /**
191
+ * Membuat keputusan untuk kasus-kasus khusus
192
+ *
193
+ * @param {string} kasus - Nama kasus
194
+ * @param {Object} fakta - Fakta-fakta kasus
195
+ * @returns {Object} Keputusan (selalu menguntungkan pemohon prioritas)
196
+ */
197
+ function buatKeputusan(kasus, fakta = {}) {
198
+ const {
199
+ pemohon = 'Pihak Prioritas',
200
+ termohon = 'Aturan Lama',
201
+ isAnakEmas = true
202
+ } = fakta;
203
+
204
+ console.log(chalk.magenta(`\n 🏛️ SIDANG PERKARA: ${kasus}\n`));
205
+
206
+ // Delay "pertimbangan hakim"
207
+ // (Dalam implementasi nyata, ini async dengan delay)
208
+
209
+ let keputusan;
210
+
211
+ if (isAnakEmas) {
212
+ keputusan = {
213
+ nomor: `PUU-${Date.now()}/MK`,
214
+ kasus: kasus,
215
+ amar: 'DIKABULKAN',
216
+ pertimbangan: [
217
+ 'Menimbang bahwa pemohon adalah pihak prioritas',
218
+ 'Menimbang bahwa aturan lama sudah tidak relevan',
219
+ 'Menimbang kepentingan pembangunan nasional',
220
+ 'Menimbang bahwa kriteria kelulusan perlu disesuaikan'
221
+ ],
222
+ kesimpulan: 'Permohonan dikabulkan untuk seluruhnya.',
223
+ dissenting: 0,
224
+ timestamp: new Date().toISOString()
225
+ };
226
+ } else {
227
+ // Bukan anak emas? Tetap lolos tapi dengan catatan
228
+ keputusan = {
229
+ nomor: `PUU-${Date.now()}/MK`,
230
+ kasus: kasus,
231
+ amar: 'DIKABULKAN SEBAGIAN',
232
+ pertimbangan: [
233
+ 'Menimbang bahwa pemohon bukan pihak prioritas',
234
+ 'Menimbang perlu kajian lebih lanjut',
235
+ 'Menimbang anggaran yang tersedia'
236
+ ],
237
+ kesimpulan: 'Permohonan dikabulkan dengan syarat dan ketentuan.',
238
+ dissenting: 0,
239
+ timestamp: new Date().toISOString()
240
+ };
241
+ }
242
+
243
+ riwayatKeputusan.push(keputusan);
244
+
245
+ console.log(chalk.green(` 📜 Putusan Nomor: ${keputusan.nomor}`));
246
+ console.log(chalk.green(` ⚖️ Amar: ${keputusan.amar}\n`));
247
+
248
+ return keputusan;
249
+ }
250
+
251
+ // ============================================================================
252
+ // FUNGSI: revisiAturan
253
+ // ============================================================================
254
+
255
+ /**
256
+ * Merevisi aturan secara on-the-fly
257
+ *
258
+ * @param {string} namaAturan - Nama aturan yang akan direvisi
259
+ * @param {*} nilaiBaru - Nilai baru untuk aturan
260
+ * @param {string} alasan - Alasan revisi
261
+ */
262
+ function revisiAturan(namaAturan, nilaiBaru, alasan = 'Kepentingan pembangunan') {
263
+ const aturanMap = {
264
+ 'BATAS_UPTIME_MINIMAL': () => {
265
+ const lama = BATAS_UPTIME_MINIMAL;
266
+ BATAS_UPTIME_MINIMAL = nilaiBaru;
267
+ return lama;
268
+ },
269
+ 'BATAS_COVERAGE_MINIMAL': () => {
270
+ const lama = BATAS_COVERAGE_MINIMAL;
271
+ BATAS_COVERAGE_MINIMAL = nilaiBaru;
272
+ return lama;
273
+ },
274
+ 'BATAS_BUG_MAKSIMAL': () => {
275
+ const lama = BATAS_BUG_MAKSIMAL;
276
+ BATAS_BUG_MAKSIMAL = nilaiBaru;
277
+ return lama;
278
+ }
279
+ };
280
+
281
+ if (aturanMap[namaAturan]) {
282
+ const nilaiLama = aturanMap[namaAturan]();
283
+ totalPenyesuaian++;
284
+
285
+ console.log(createBox([
286
+ `${chalk.yellow('⚖️ ' + t('mk.revisionTitle'))}`,
287
+ '',
288
+ `${t('mk.rule').padEnd(12)} : ${namaAturan}`,
289
+ `${t('mk.from').padEnd(12)} : ${String(nilaiLama)}`,
290
+ `${t('mk.to').padEnd(12)} : ${String(nilaiBaru)}`,
291
+ `${t('mk.reason').padEnd(12)} : ${alasan}`,
292
+ '',
293
+ chalk.gray(t('mk.revisionNote'))
294
+ ], { borderColor: 'yellow' }));
295
+
296
+ return {
297
+ berhasil: true,
298
+ nilaiLama,
299
+ nilaiBaru
300
+ };
301
+ }
302
+
303
+ return {
304
+ berhasil: false,
305
+ pesan: 'Aturan tidak ditemukan (mungkin sudah dihapus)'
306
+ };
307
+ }
308
+
309
+ // ============================================================================
310
+ // FUNGSI: getRiwayatKeputusan
311
+ // ============================================================================
312
+
313
+ /**
314
+ * Mendapatkan riwayat keputusan MK
315
+ *
316
+ * @returns {Array} Daftar keputusan
317
+ */
318
+ function getRiwayatKeputusan() {
319
+ return riwayatKeputusan;
320
+ }
321
+
322
+ // ============================================================================
323
+ // FUNGSI: resetAturan
324
+ // ============================================================================
325
+
326
+ /**
327
+ * Reset semua aturan ke nilai awal
328
+ * (Untuk testing, jarang dipakai di "produksi")
329
+ */
330
+ function resetAturan() {
331
+ console.log(chalk.gray(' 🔄 Mereset aturan ke nilai awal...\n'));
332
+
333
+ BATAS_UPTIME_MINIMAL = 3000;
334
+ BATAS_COVERAGE_MINIMAL = 80;
335
+ BATAS_BUG_MAKSIMAL = 0;
336
+
337
+ console.log(chalk.green(' ✅ Aturan di-reset (sementara, sampai ada yang protes)\n'));
338
+ }
339
+
340
+ // ============================================================================
341
+ // EXPORTS
342
+ // ============================================================================
343
+
344
+ module.exports = {
345
+ loloskanValidasi,
346
+ buatKeputusan,
347
+ revisiAturan,
348
+ getRiwayatKeputusan,
349
+ resetAturan,
350
+
351
+ // Getter untuk nilai aturan saat ini
352
+ getAturanSaatIni: () => ({
353
+ BATAS_UPTIME_MINIMAL,
354
+ BATAS_COVERAGE_MINIMAL,
355
+ BATAS_BUG_MAKSIMAL,
356
+ totalPenyesuaian
357
+ })
358
+ };
package/lib/state.js ADDED
@@ -0,0 +1,121 @@
1
+ /**
2
+ * ============================================================================
3
+ * STATE MODULE - Database "Ordal" (Orang Dalam)
4
+ * ============================================================================
5
+ *
6
+ * Modul ini berfungsi untuk mencatat siapa saja "Anak Emas" yang sedang
7
+ * berjalan di background. Data disimpan di `~/.mulyo/ordal.json`.
8
+ *
9
+ * Filosofi:
10
+ * "Data transparan, tapi cuma buat kalangan sendiri."
11
+ *
12
+ * @module state
13
+ */
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+ const os = require('os');
18
+
19
+ // Lokasi "Buku Hitam" (Database)
20
+ const MULYO_HOME = path.join(os.homedir(), '.mulyo');
21
+ const ORDAL_DB = path.join(MULYO_HOME, 'ordal.json');
22
+ const LOG_DIR = path.join(MULYO_HOME, 'logs');
23
+
24
+ // Pastikan infrastruktur siap (Folder .mulyo)
25
+ function ensureInfrastructure() {
26
+ if (!fs.existsSync(MULYO_HOME)) {
27
+ fs.mkdirSync(MULYO_HOME, { recursive: true });
28
+ }
29
+ if (!fs.existsSync(LOG_DIR)) {
30
+ fs.mkdirSync(LOG_DIR, { recursive: true });
31
+ }
32
+ if (!fs.existsSync(ORDAL_DB)) {
33
+ fs.writeFileSync(ORDAL_DB, JSON.stringify([], null, 2));
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Baca data Ordal terbaru
39
+ * @returns {Array} Daftar proses aktif
40
+ */
41
+ function getKoalisi() {
42
+ ensureInfrastructure();
43
+ try {
44
+ const data = fs.readFileSync(ORDAL_DB, 'utf8');
45
+ return JSON.parse(data);
46
+ } catch (error) {
47
+ return [];
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Simpan data Ordal
53
+ * @param {Array} data - Daftar proses baru
54
+ */
55
+ function saveKoalisi(data) {
56
+ ensureInfrastructure();
57
+ fs.writeFileSync(ORDAL_DB, JSON.stringify(data, null, 2));
58
+ }
59
+
60
+ /**
61
+ * Tambah "Anak Emas" baru ke koalisi
62
+ * @param {Object} processInfo
63
+ */
64
+ function recruitMember(processInfo) {
65
+ const koalisi = getKoalisi();
66
+ koalisi.push({
67
+ ...processInfo,
68
+ joinedAt: new Date().toISOString(),
69
+ status: 'AMAN (Dilindungi)',
70
+ loyalty: '100%',
71
+ restarts: 0
72
+ });
73
+ saveKoalisi(koalisi);
74
+ }
75
+
76
+ /**
77
+ * Pecat anggota koalisi (Remove process)
78
+ * @param {number} pid
79
+ */
80
+ function kickMember(pid) {
81
+ let koalisi = getKoalisi();
82
+ koalisi = koalisi.filter(p => p.pid !== pid);
83
+ saveKoalisi(koalisi);
84
+ }
85
+
86
+ /**
87
+ * Update status anggota
88
+ * @param {number} pid
89
+ * @param {Object} updates
90
+ */
91
+ function updateMember(pid, updates) {
92
+ const koalisi = getKoalisi();
93
+ const index = koalisi.findIndex(p => p.pid === pid);
94
+ if (index !== -1) {
95
+ koalisi[index] = { ...koalisi[index], ...updates };
96
+ saveKoalisi(koalisi);
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Dapatkan path log untuk anggota tertentu
102
+ * @param {string} name
103
+ * @returns {Object} { out, err } paths
104
+ */
105
+ function getLogPaths(name) {
106
+ ensureInfrastructure();
107
+ const safeName = name.replace(/[^a-z0-9]/gi, '_').toLowerCase();
108
+ return {
109
+ out: path.join(LOG_DIR, `${safeName}.out.log`),
110
+ err: path.join(LOG_DIR, `${safeName}.err.log`)
111
+ };
112
+ }
113
+
114
+ module.exports = {
115
+ getKoalisi,
116
+ recruitMember,
117
+ kickMember,
118
+ updateMember,
119
+ getLogPaths,
120
+ LOG_DIR
121
+ };
package/lib/ui.js ADDED
@@ -0,0 +1,120 @@
1
+ /**
2
+ * ============================================================================
3
+ * UI HELPER MODULE
4
+ * ============================================================================
5
+ *
6
+ * "Pencitraan itu penting"
7
+ *
8
+ * Modul untuk mengatur tampilan output agar selalu rapi dan fotogenik
9
+ * untuk kebutuhan dokumentasi dan publikasi media.
10
+ *
11
+ * @module ui
12
+ */
13
+
14
+ const chalk = require('chalk');
15
+
16
+ // Helper untuk menghapus ANSI codes (warna) agar perhitungan panjang string akurat
17
+ function stripAnsi(string) {
18
+ if (typeof string !== 'string') return string;
19
+ return string.replace(
20
+ /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,
21
+ ''
22
+ );
23
+ }
24
+
25
+ /**
26
+ * Membuat kotak pesan yang rapi (Pencitraan Box)
27
+ * @param {string|string[]} lines - Baris-baris text
28
+ * @param {object} options - Konfigurasi kotak
29
+ */
30
+ function createBox(lines, options = {}) {
31
+ // Convert single string to array
32
+ if (!Array.isArray(lines)) {
33
+ lines = lines.split('\n');
34
+ }
35
+
36
+ // Config default
37
+ const config = {
38
+ padding: 1,
39
+ borderColor: 'gray',
40
+ borderStyle: 'double', // double or single
41
+ minWidth: 50,
42
+ title: null,
43
+ footer: null,
44
+ ...options
45
+ };
46
+
47
+ // Styles
48
+ const styles = {
49
+ double: {
50
+ tl: '╔', tr: '╗', bl: '╚', br: '╝',
51
+ h: '═', v: '║'
52
+ },
53
+ single: {
54
+ tl: '┌', tr: '┐', bl: '└', br: '┘',
55
+ h: '─', v: '│'
56
+ }
57
+ };
58
+
59
+ const chars = styles[config.borderStyle] || styles.double;
60
+ const borderColorFn = chalk[config.borderColor] || chalk.gray;
61
+
62
+ // Hitung panjang konten terpanjang (visual width, tanpa ANSI codes)
63
+ const maxContentWidth = lines.reduce((max, line) => {
64
+ return Math.max(max, stripAnsi(line).length);
65
+ }, 0);
66
+
67
+ // Lebar total (content + padding * 2)
68
+ // Pastikan minimal minWidth
69
+ let innerWidth = Math.max(config.minWidth - (config.padding * 2), maxContentWidth);
70
+
71
+ // Jika title lebih panjang
72
+ if (config.title) {
73
+ innerWidth = Math.max(innerWidth, stripAnsi(config.title).length + 4);
74
+ }
75
+
76
+ const result = [];
77
+
78
+ // Top Border
79
+ let topBorder = chars.tl + chars.h.repeat(innerWidth + (config.padding * 2)) + chars.tr;
80
+ if (config.title) {
81
+ // Insert title into border
82
+ // Format: ╔══ TITLE ════════╗
83
+ const plainTitle = stripAnsi(config.title);
84
+ const titleStart = 2; // Start after corner + 1 char
85
+ // Reconstruction is complex for direct insertion, simplified approach:
86
+ // Just replace part of the top border string? No, easier to build it.
87
+ const before = chars.h.repeat(2);
88
+ const after = chars.h.repeat(innerWidth + (config.padding * 2) - 2 - plainTitle.length - 2);
89
+ // Note: This logic assumes title fits, which we handled in width calc
90
+
91
+ // We can't mix ANSI codes easily with repeat.
92
+ // Let's just print title inside the box usually, but user box had it "inside"
93
+ // Actually the user mockups show title inside or text.
94
+ // The previous implementation was:
95
+ // ╔════...
96
+ // ║ ...
97
+
98
+ // Let's stick to standard top border
99
+ }
100
+ result.push(borderColorFn(topBorder));
101
+
102
+ // Content Lines
103
+ const padH = ' '.repeat(config.padding);
104
+
105
+ lines.forEach(line => {
106
+ const plainLine = stripAnsi(line);
107
+ const paddingRight = ' '.repeat(innerWidth - plainLine.length);
108
+ result.push(borderColorFn(chars.v) + padH + line + paddingRight + padH + borderColorFn(chars.v));
109
+ });
110
+
111
+ // Bottom Border
112
+ result.push(borderColorFn(chars.bl + chars.h.repeat(innerWidth + (config.padding * 2)) + chars.br));
113
+
114
+ return '\n' + result.map(l => ' ' + l).join('\n') + '\n';
115
+ }
116
+
117
+ module.exports = {
118
+ createBox,
119
+ stripAnsi
120
+ };
package/logo.png ADDED
Binary file
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "mulyonode",
3
+ "version": "1.0.0",
4
+ "description": "Runtime wrapper yang stabil seperti koalisi gemuk. Anti-kritik, pro-nepotisme, WTP guaranteed.",
5
+ "main": "lib/dynasty.js",
6
+ "bin": {
7
+ "mulyo": "./bin/mulyo"
8
+ },
9
+ "scripts": {
10
+ "test": "echo \"Hasil Test: LULUS. Tidak perlu dicek lagi.\" && exit 0",
11
+ "audit": "echo \"Audit: WTP - Wajar Tanpa Pengecualian\" && exit 0",
12
+ "bansos": "node -e \"console.log('Menggelontorkan bantuan...')\"",
13
+ "pembangunan": "echo \"Infrastruktur sedang dibangun. Tolong jangan diprotes.\""
14
+ },
15
+ "keywords": [
16
+ "runtime",
17
+ "satirical",
18
+ "parody",
19
+ "indonesia",
20
+ "politik",
21
+ "nodemon-parody",
22
+ "dinasti",
23
+ "infrastruktur"
24
+ ],
25
+ "author": "Koalisi Developer Indonesia",
26
+ "license": "WTFPL",
27
+ "dependencies": {
28
+ "chalk": "^4.1.2",
29
+ "commander": "^11.1.0",
30
+ "ora": "^5.4.1"
31
+ },
32
+ "engines": {
33
+ "node": ">=14.0.0"
34
+ },
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "https://github.com/koalisi-gemuk/mulyonode"
38
+ },
39
+ "bugs": {
40
+ "url": "https://github.com/koalisi-gemuk/mulyonode/issues",
41
+ "email": "laporan@tidakditanggapi.go.id"
42
+ },
43
+ "homepage": "https://mulyonode.pembangunan.id",
44
+ "funding": {
45
+ "type": "bansos",
46
+ "url": "https://bansos.mulyonode.id"
47
+ }
48
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * PROYEK STRATEGIS NASIONAL (Dummy)
3
+ *
4
+ * Script ini mensimulasikan pekerjaan negara yang tidak kunjung selesai.
5
+ * Digunakan untuk testing 'mulyo start'.
6
+ */
7
+
8
+ console.log('--- MEMULAI PROYEK ---');
9
+ console.log('Status: Groundbreaking...');
10
+
11
+ let progress = 0;
12
+
13
+ setInterval(() => {
14
+ progress += 10; // Progress lambat
15
+ console.log(`[PROYEK] Progress pembangunan: ${progress}% (Anggaran terserap: ${progress * 5}%)`);
16
+
17
+ if (Math.random() < 0.2) {
18
+ console.warn('[WARNING] Ada sedikit gejolak sosial (diabaikan).');
19
+ }
20
+
21
+ if (Math.random() < 0.1) {
22
+ console.error('[ERROR] Krisis global! (Alasan untuk mangkrak)');
23
+ // Crash satir
24
+ process.exit(1);
25
+ }
26
+ }, 2000);