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/LICENSE +47 -0
- package/README.md +190 -0
- package/background-test.js +5 -0
- package/bin/mulyo +1054 -0
- package/debug-state.js +19 -0
- package/lib/cawe-cawe.js +342 -0
- package/lib/dynasty.js +339 -0
- package/lib/i18n.js +965 -0
- package/lib/mk.js +358 -0
- package/lib/state.js +121 -0
- package/lib/ui.js +120 -0
- package/logo.png +0 -0
- package/package.json +48 -0
- package/proyek-presiden.js +26 -0
- package/test.js +278 -0
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);
|