@xbibzlibrary/telebibz 0.4.4 → 3.0.1

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.
Files changed (258) hide show
  1. package/CHANGELOG.md +35 -138
  2. package/LICENSE +1 -1
  3. package/NOTICE.md +9 -4
  4. package/README.md +173 -242
  5. package/examples/01-quickstart.js +13 -0
  6. package/examples/02-menu-tombol.js +22 -0
  7. package/examples/03-wizard.js +29 -0
  8. package/examples/04-broadcast.js +25 -0
  9. package/examples/05-kirim-file.js +19 -0
  10. package/examples/06-menu.js +35 -0
  11. package/examples/07-inline-query.js +18 -0
  12. package/index.d.ts +81 -0
  13. package/index.js +42 -0
  14. package/lib/api.js +154 -0
  15. package/lib/broadcast.js +35 -0
  16. package/lib/composer.js +174 -0
  17. package/lib/context.js +190 -0
  18. package/lib/errors.js +35 -0
  19. package/lib/file.js +41 -0
  20. package/lib/inline-query.js +28 -0
  21. package/lib/keyboard.js +83 -0
  22. package/lib/logger.js +38 -0
  23. package/lib/menus.js +90 -0
  24. package/lib/net.js +115 -0
  25. package/lib/ratelimit.js +61 -0
  26. package/lib/runner.js +45 -0
  27. package/lib/session.js +36 -0
  28. package/lib/telebibz.js +190 -0
  29. package/lib/wizard.js +81 -0
  30. package/package.json +35 -97
  31. package/test/all.test.js +334 -0
  32. package/CODE_OF_CONDUCT.md +0 -37
  33. package/CONTRIBUTING.md +0 -59
  34. package/CONTRIBUTION_RULES.md +0 -41
  35. package/GOVERNANCE.md +0 -47
  36. package/README.id.md +0 -292
  37. package/README.zh-CN.md +0 -292
  38. package/RELEASE_AUTOMATION.md +0 -78
  39. package/RELEASE_POLICY.md +0 -32
  40. package/SECURITY.md +0 -47
  41. package/SHOWCASE.md +0 -29
  42. package/SUPPORT.md +0 -30
  43. package/assets/readme-preview.html +0 -75
  44. package/assets/telebibz-logo.png +0 -0
  45. package/assets/telebibz-readme-preview.png +0 -0
  46. package/bin/telebibz.mjs +0 -3
  47. package/dist/generated/api.d.ts +0 -13
  48. package/dist/generated/api.d.ts.map +0 -1
  49. package/dist/generated/api.js +0 -192
  50. package/dist/generated/api.js.map +0 -1
  51. package/dist/src/api/client.d.ts +0 -62
  52. package/dist/src/api/client.d.ts.map +0 -1
  53. package/dist/src/api/client.js +0 -104
  54. package/dist/src/api/client.js.map +0 -1
  55. package/dist/src/api/errors.d.ts +0 -45
  56. package/dist/src/api/errors.d.ts.map +0 -1
  57. package/dist/src/api/errors.js +0 -65
  58. package/dist/src/api/errors.js.map +0 -1
  59. package/dist/src/api/index.d.ts +0 -6
  60. package/dist/src/api/index.d.ts.map +0 -1
  61. package/dist/src/api/index.js +0 -6
  62. package/dist/src/api/index.js.map +0 -1
  63. package/dist/src/api/telegram-types/LICENSE +0 -21
  64. package/dist/src/api/telegram-types/api.d.ts +0 -22
  65. package/dist/src/api/telegram-types/checklist.d.ts +0 -72
  66. package/dist/src/api/telegram-types/inline.d.ts +0 -692
  67. package/dist/src/api/telegram-types/langs.d.ts +0 -193
  68. package/dist/src/api/telegram-types/manage.d.ts +0 -1144
  69. package/dist/src/api/telegram-types/markup.d.ts +0 -268
  70. package/dist/src/api/telegram-types/message.d.ts +0 -1537
  71. package/dist/src/api/telegram-types/methods.d.ts +0 -2870
  72. package/dist/src/api/telegram-types/mod.d.ts +0 -14
  73. package/dist/src/api/telegram-types/passport.d.ts +0 -163
  74. package/dist/src/api/telegram-types/payment.d.ts +0 -570
  75. package/dist/src/api/telegram-types/rich.d.ts +0 -1010
  76. package/dist/src/api/telegram-types/settings.d.ts +0 -120
  77. package/dist/src/api/telegram-types/story.d.ts +0 -89
  78. package/dist/src/api/telegram-types/update.d.ts +0 -84
  79. package/dist/src/api/telegram.d.ts +0 -7
  80. package/dist/src/api/telegram.d.ts.map +0 -1
  81. package/dist/src/api/telegram.js +0 -2
  82. package/dist/src/api/telegram.js.map +0 -1
  83. package/dist/src/api/transport.d.ts +0 -68
  84. package/dist/src/api/transport.d.ts.map +0 -1
  85. package/dist/src/api/transport.js +0 -264
  86. package/dist/src/api/transport.js.map +0 -1
  87. package/dist/src/api/types.d.ts +0 -466
  88. package/dist/src/api/types.d.ts.map +0 -1
  89. package/dist/src/api/types.js +0 -2
  90. package/dist/src/api/types.js.map +0 -1
  91. package/dist/src/branding/terminal.d.ts +0 -77
  92. package/dist/src/branding/terminal.d.ts.map +0 -1
  93. package/dist/src/branding/terminal.js +0 -328
  94. package/dist/src/branding/terminal.js.map +0 -1
  95. package/dist/src/broadcast/broadcast.d.ts +0 -50
  96. package/dist/src/broadcast/broadcast.d.ts.map +0 -1
  97. package/dist/src/broadcast/broadcast.js +0 -56
  98. package/dist/src/broadcast/broadcast.js.map +0 -1
  99. package/dist/src/cache/cache.d.ts +0 -34
  100. package/dist/src/cache/cache.d.ts.map +0 -1
  101. package/dist/src/cache/cache.js +0 -41
  102. package/dist/src/cache/cache.js.map +0 -1
  103. package/dist/src/cli.d.ts +0 -2
  104. package/dist/src/cli.d.ts.map +0 -1
  105. package/dist/src/cli.js +0 -84
  106. package/dist/src/cli.js.map +0 -1
  107. package/dist/src/context/context.d.ts +0 -124
  108. package/dist/src/context/context.d.ts.map +0 -1
  109. package/dist/src/context/context.js +0 -302
  110. package/dist/src/context/context.js.map +0 -1
  111. package/dist/src/core/bot.d.ts +0 -204
  112. package/dist/src/core/bot.d.ts.map +0 -1
  113. package/dist/src/core/bot.js +0 -506
  114. package/dist/src/core/bot.js.map +0 -1
  115. package/dist/src/core/events.d.ts +0 -75
  116. package/dist/src/core/events.d.ts.map +0 -1
  117. package/dist/src/core/events.js +0 -35
  118. package/dist/src/core/events.js.map +0 -1
  119. package/dist/src/core/webhook-reply.d.ts +0 -34
  120. package/dist/src/core/webhook-reply.d.ts.map +0 -1
  121. package/dist/src/core/webhook-reply.js +0 -37
  122. package/dist/src/core/webhook-reply.js.map +0 -1
  123. package/dist/src/index.d.ts +0 -24
  124. package/dist/src/index.d.ts.map +0 -1
  125. package/dist/src/index.js +0 -24
  126. package/dist/src/index.js.map +0 -1
  127. package/dist/src/keyboard/index.d.ts +0 -46
  128. package/dist/src/keyboard/index.d.ts.map +0 -1
  129. package/dist/src/keyboard/index.js +0 -55
  130. package/dist/src/keyboard/index.js.map +0 -1
  131. package/dist/src/middleware/compose.d.ts +0 -5
  132. package/dist/src/middleware/compose.d.ts.map +0 -1
  133. package/dist/src/middleware/compose.js +0 -17
  134. package/dist/src/middleware/compose.js.map +0 -1
  135. package/dist/src/observability/logger.d.ts +0 -78
  136. package/dist/src/observability/logger.d.ts.map +0 -1
  137. package/dist/src/observability/logger.js +0 -285
  138. package/dist/src/observability/logger.js.map +0 -1
  139. package/dist/src/plugins/plugin.d.ts +0 -38
  140. package/dist/src/plugins/plugin.d.ts.map +0 -1
  141. package/dist/src/plugins/plugin.js +0 -59
  142. package/dist/src/plugins/plugin.js.map +0 -1
  143. package/dist/src/queue/queue.d.ts +0 -77
  144. package/dist/src/queue/queue.d.ts.map +0 -1
  145. package/dist/src/queue/queue.js +0 -213
  146. package/dist/src/queue/queue.js.map +0 -1
  147. package/dist/src/router/router.d.ts +0 -61
  148. package/dist/src/router/router.d.ts.map +0 -1
  149. package/dist/src/router/router.js +0 -183
  150. package/dist/src/router/router.js.map +0 -1
  151. package/dist/src/state/conversation.d.ts +0 -56
  152. package/dist/src/state/conversation.d.ts.map +0 -1
  153. package/dist/src/state/conversation.js +0 -133
  154. package/dist/src/state/conversation.js.map +0 -1
  155. package/dist/src/state/forms.d.ts +0 -34
  156. package/dist/src/state/forms.d.ts.map +0 -1
  157. package/dist/src/state/forms.js +0 -44
  158. package/dist/src/state/forms.js.map +0 -1
  159. package/dist/src/state/menu.d.ts +0 -78
  160. package/dist/src/state/menu.d.ts.map +0 -1
  161. package/dist/src/state/menu.js +0 -127
  162. package/dist/src/state/menu.js.map +0 -1
  163. package/dist/src/storage/storage.d.ts +0 -146
  164. package/dist/src/storage/storage.d.ts.map +0 -1
  165. package/dist/src/storage/storage.js +0 -195
  166. package/dist/src/storage/storage.js.map +0 -1
  167. package/dist/src/telegram-features.d.ts +0 -33
  168. package/dist/src/telegram-features.d.ts.map +0 -1
  169. package/dist/src/telegram-features.js +0 -71
  170. package/dist/src/telegram-features.js.map +0 -1
  171. package/dist/src/testing.d.ts +0 -24
  172. package/dist/src/testing.d.ts.map +0 -1
  173. package/dist/src/testing.js +0 -38
  174. package/dist/src/testing.js.map +0 -1
  175. package/dist/src/utils/concurrency.d.ts +0 -25
  176. package/dist/src/utils/concurrency.d.ts.map +0 -1
  177. package/dist/src/utils/concurrency.js +0 -52
  178. package/dist/src/utils/concurrency.js.map +0 -1
  179. package/dist/src/utils/files.d.ts +0 -45
  180. package/dist/src/utils/files.d.ts.map +0 -1
  181. package/dist/src/utils/files.js +0 -53
  182. package/dist/src/utils/files.js.map +0 -1
  183. package/dist/src/utils/text.d.ts +0 -39
  184. package/dist/src/utils/text.d.ts.map +0 -1
  185. package/dist/src/utils/text.js +0 -56
  186. package/dist/src/utils/text.js.map +0 -1
  187. package/dist/src/webhook/handler.d.ts +0 -19
  188. package/dist/src/webhook/handler.d.ts.map +0 -1
  189. package/dist/src/webhook/handler.js +0 -141
  190. package/dist/src/webhook/handler.js.map +0 -1
  191. package/dist-cjs/generated/api.js +0 -194
  192. package/dist-cjs/package.json +0 -3
  193. package/dist-cjs/src/api/client.js +0 -107
  194. package/dist-cjs/src/api/errors.js +0 -74
  195. package/dist-cjs/src/api/index.js +0 -21
  196. package/dist-cjs/src/api/telegram-types/LICENSE +0 -21
  197. package/dist-cjs/src/api/telegram-types/api.d.ts +0 -22
  198. package/dist-cjs/src/api/telegram-types/checklist.d.ts +0 -72
  199. package/dist-cjs/src/api/telegram-types/inline.d.ts +0 -692
  200. package/dist-cjs/src/api/telegram-types/langs.d.ts +0 -193
  201. package/dist-cjs/src/api/telegram-types/manage.d.ts +0 -1144
  202. package/dist-cjs/src/api/telegram-types/markup.d.ts +0 -268
  203. package/dist-cjs/src/api/telegram-types/message.d.ts +0 -1537
  204. package/dist-cjs/src/api/telegram-types/methods.d.ts +0 -2870
  205. package/dist-cjs/src/api/telegram-types/mod.d.ts +0 -14
  206. package/dist-cjs/src/api/telegram-types/passport.d.ts +0 -163
  207. package/dist-cjs/src/api/telegram-types/payment.d.ts +0 -570
  208. package/dist-cjs/src/api/telegram-types/rich.d.ts +0 -1010
  209. package/dist-cjs/src/api/telegram-types/settings.d.ts +0 -120
  210. package/dist-cjs/src/api/telegram-types/story.d.ts +0 -89
  211. package/dist-cjs/src/api/telegram-types/update.d.ts +0 -84
  212. package/dist-cjs/src/api/telegram.js +0 -2
  213. package/dist-cjs/src/api/transport.js +0 -267
  214. package/dist-cjs/src/api/types.js +0 -2
  215. package/dist-cjs/src/branding/terminal.js +0 -338
  216. package/dist-cjs/src/broadcast/broadcast.js +0 -58
  217. package/dist-cjs/src/cache/cache.js +0 -45
  218. package/dist-cjs/src/cli.js +0 -86
  219. package/dist-cjs/src/context/context.js +0 -305
  220. package/dist-cjs/src/core/bot.js +0 -510
  221. package/dist-cjs/src/core/events.js +0 -38
  222. package/dist-cjs/src/core/webhook-reply.js +0 -42
  223. package/dist-cjs/src/index.js +0 -47
  224. package/dist-cjs/src/keyboard/index.js +0 -61
  225. package/dist-cjs/src/middleware/compose.js +0 -20
  226. package/dist-cjs/src/observability/logger.js +0 -293
  227. package/dist-cjs/src/plugins/plugin.js +0 -63
  228. package/dist-cjs/src/queue/queue.js +0 -219
  229. package/dist-cjs/src/router/router.js +0 -186
  230. package/dist-cjs/src/state/conversation.js +0 -139
  231. package/dist-cjs/src/state/forms.js +0 -47
  232. package/dist-cjs/src/state/menu.js +0 -133
  233. package/dist-cjs/src/storage/storage.js +0 -202
  234. package/dist-cjs/src/telegram-features.js +0 -76
  235. package/dist-cjs/src/testing.js +0 -45
  236. package/dist-cjs/src/utils/concurrency.js +0 -57
  237. package/dist-cjs/src/utils/files.js +0 -58
  238. package/dist-cjs/src/utils/text.js +0 -63
  239. package/dist-cjs/src/webhook/handler.js +0 -144
  240. package/docs/API.id.md +0 -1935
  241. package/docs/API.md +0 -1969
  242. package/docs/API.zh-CN.md +0 -1929
  243. package/docs/GETTING_STARTED.id.md +0 -85
  244. package/docs/GETTING_STARTED.md +0 -85
  245. package/docs/GETTING_STARTED.zh-CN.md +0 -85
  246. package/docs/GITHUB_PACKAGES.id.md +0 -82
  247. package/docs/GITHUB_PACKAGES.md +0 -82
  248. package/docs/GITHUB_PACKAGES.zh-CN.md +0 -82
  249. package/docs/README.md +0 -48
  250. package/docs/STORAGE.id.md +0 -105
  251. package/docs/STORAGE.md +0 -105
  252. package/docs/STORAGE.zh-CN.md +0 -105
  253. package/examples/README.md +0 -37
  254. package/examples/files.ts +0 -35
  255. package/examples/minimal.ts +0 -12
  256. package/examples/tsconfig.json +0 -9
  257. package/examples/webhook.ts +0 -42
  258. package/examples/wizard-registration.ts +0 -42
@@ -0,0 +1,334 @@
1
+ // test/all.test.js — 14 kasus, 100% tanpa jaringan (transport disuntik).
2
+ 'use strict';
3
+
4
+ const assert = require('assert');
5
+ const { TeleBibz, btn, url, kb, webApp, copy, humanize, File, InlineKeyboard,
6
+ Menu, MenuContainer, iq, autoRetry, throttler, limiter, InputMediaBuilder } = require('..');
7
+
8
+ let N = 0, OK = 0;
9
+ const TOTAL = 24;
10
+ const t = (nama, fn) => {
11
+ const done = () => { N++; if (N === TOTAL) { console.log(`\n${OK}/${N} lulus`); process.exit(OK === N ? 0 : 1); } };
12
+ Promise.resolve()
13
+ .then(fn)
14
+ .then(() => { OK++; console.log(` ✓ ${nama}`); done(); })
15
+ .catch((e) => { console.error(` ✗ ${nama}\n ${e.stack || e}`); done(); });
16
+ };
17
+
18
+ /* ===== harness: bot dengan transport palsu (merekam panggilan API) ===== */
19
+ function buatBot(overrides = {}) {
20
+ const calls = [];
21
+ const transport = async (method, payload = {}) => {
22
+ calls.push({ method, payload });
23
+ if (method === 'getMe') return { id: 99, is_bot: true, first_name: 'Tes', username: 'tesbot' };
24
+ if (method === 'sendMessage') return { message_id: calls.length, ...payload };
25
+ if (overrides[method]) return overrides[method](payload);
26
+ return true;
27
+ };
28
+ const bot = new TeleBibz('123456:TESTTOKEN-TESTTOKEN-TESTTOKEN-TESTOKEN', { silent: true, transport });
29
+ return { bot, calls, me: null };
30
+ }
31
+ async function boot(b) { await b.init(); }
32
+ const ME = { id: 99, is_bot: true, first_name: 'Tes', username: 'tesbot' };
33
+ const USER = { id: 555, is_bot: false, first_name: 'Budi' };
34
+ const CHAT = { id: 555, type: 'private', first_name: 'Budi' };
35
+ let uid = 0, mid = 0;
36
+ function uMsg(text, chat = CHAT) {
37
+ return {
38
+ update_id: ++uid,
39
+ message: {
40
+ message_id: ++mid, from: USER, chat, date: Math.floor(Date.now() / 1000),
41
+ text,
42
+ ...(text.startsWith('/') ? { entities: [{ type: 'bot_command', offset: 0, length: text.split(' ')[0].length }] } : {}),
43
+ },
44
+ };
45
+ }
46
+ const texts = (calls) => calls.filter((c) => c.method === 'sendMessage').map((c) => c.payload.text || '');
47
+
48
+ /* ===== 1. keyboard builder ===== */
49
+ t('btn()/url()/kb(): bentuk objek + warna + ikon', () => {
50
+ assert.deepStrictEqual(btn('A', 'a'), { text: 'A', callback_data: 'a' });
51
+ assert.deepStrictEqual(btn('B', 'b', 'danger', '123'), { text: 'B', callback_data: 'b', style: 'danger', icon_custom_emoji_id: '123' });
52
+ assert.strictEqual(url('W', 'https://x', 'success').style, 'success');
53
+ assert.strictEqual(webApp('App', 'https://w').web_app.url, 'https://w');
54
+ assert.strictEqual(copy('Salin', 'kode').copy_text.text, 'kode');
55
+ assert.ok(kb([[btn('X', 'x')]]).reply_markup.inline_keyboard[0][0].text === 'X');
56
+ assert.ok(kb.confirm('y', 'n').reply_markup.inline_keyboard[0][0].style === 'success');
57
+ const ik = new InlineKeyboard().text('A', 'a').url('B', 'https://b').row().text('C', 'c', 'danger').build();
58
+ assert.strictEqual(ik.reply_markup.inline_keyboard.length, 2);
59
+ });
60
+
61
+ /* ===== 2. token guard ===== */
62
+ t('constructor menolak token ngaco cepat', () => {
63
+ assert.throws(() => new TeleBibz('bukan-token'), /token tidak valid/i);
64
+ assert.throws(() => new TeleBibz(), /token tidak valid/i);
65
+ });
66
+
67
+ /* ===== 3. cmd + hears ===== */
68
+ t('cmd & hears merespons via sendMessage', async () => {
69
+ const { bot, calls } = buatBot(); await boot(bot);
70
+ let startHit = 0;
71
+ bot.cmd('start', (ctx) => { startHit++; return ctx.reply(`Halo ${ctx.from.first_name}`); });
72
+ bot.hears(/halo/i, (ctx) => ctx.reply('halo juga'));
73
+ await bot.handleUpdate(uMsg('/start'));
74
+ await bot.handleUpdate(uMsg('Halo kawan'));
75
+ assert.strictEqual(startHit, 1);
76
+ const tx = texts(calls);
77
+ assert.ok(tx.some((x) => /Halo Budi/.test(x)) && tx.some((x) => /halo juga/.test(x)));
78
+ });
79
+
80
+ /* ===== 4. action ===== */
81
+ t('action() menangkap callback_data', async () => {
82
+ const { bot, calls } = buatBot(); await boot(bot);
83
+ bot.action('menu:premium', (ctx) => ctx.editMessageText('premium dibuka'));
84
+ await bot.handleUpdate({
85
+ update_id: ++uid,
86
+ callback_query: {
87
+ id: 'q1', from: USER, chat_instance: 'ci', data: 'menu:premium',
88
+ message: { message_id: 7, from: { id: 99, is_bot: true, first_name: 'T' }, chat: CHAT, date: 1 },
89
+ },
90
+ });
91
+ assert.ok(calls.some((c) => c.method === 'editMessageText' && /premium dibuka/.test(c.payload.text)));
92
+ });
93
+
94
+ /* ===== 5. hears string persis ===== */
95
+ t('hears(string) hanya cocok teks persis (ci)', async () => {
96
+ const { bot, calls } = buatBot(); await boot(bot);
97
+ bot.hears('ping', (ctx) => ctx.reply('pong'));
98
+ await bot.handleUpdate(uMsg('PING'));
99
+ await bot.handleUpdate(uMsg('ping pong'));
100
+ const po = texts(calls).filter((x) => x === 'pong');
101
+ assert.strictEqual(po.length, 1, 'hears string harus cocok persis sekali saja');
102
+ });
103
+
104
+ /* ===== 6–8. wizard ===== */
105
+ t('wizard mengalir penuh: tanya → validasi → done', async () => {
106
+ const { bot, calls } = buatBot(); await boot(bot);
107
+ const jawab = [];
108
+ bot.wizard('daftar', {
109
+ steps: [
110
+ { key: 'nama', ask: 'Nama?' },
111
+ { key: 'umur', ask: 'Umur?', parse: (t) => parseInt(t, 10), validate: (n) => (n > 0 ? null : 'Ulangi angka:') },
112
+ ],
113
+ done: async (ans, ctx) => { jawab.push(ans); await ctx.reply('selesai'); },
114
+ });
115
+ const send = (t) => bot.handleUpdate(uMsg(t));
116
+ await send('/daftar'); await send('Budi'); await send('abc'); await send('25');
117
+ const tx = texts(calls);
118
+ assert.deepStrictEqual(jawab, [{ nama: 'Budi', umur: 25 }]);
119
+ assert.ok(tx.some((x) => /Nama\?/.test(x)) && tx.some((x) => /Ulangi angka/.test(x)) && tx.some((x) => /selesai/.test(x)));
120
+ });
121
+
122
+ t('wizard batal via kata batal', async () => {
123
+ const { bot, calls } = buatBot(); await boot(bot);
124
+ bot.wizard('isi', { steps: [{ key: 'a', ask: 'A?' }], done: () => {} });
125
+ await bot.handleUpdate(uMsg('/isi'));
126
+ await bot.handleUpdate(uMsg('batal'));
127
+ assert.ok(texts(calls).some((x) => /dibatalkan/i.test(x)));
128
+ });
129
+
130
+ t('pesan non-teks di tengah wizard tidak error & wizard lanjut', async () => {
131
+ const { bot, calls } = buatBot(); await boot(bot);
132
+ let doneHit = 0;
133
+ bot.wizard('form', { steps: [{ key: 'a', ask: 'A?' }], done: () => doneHit++ });
134
+ await bot.handleUpdate(uMsg('/form'));
135
+ await bot.handleUpdate({ update_id: ++uid, message: { message_id: ++mid, from: USER, chat: CHAT, date: 1, photo: [{ file_id: 'f', width: 1, height: 1 }] } });
136
+ await bot.handleUpdate(uMsg('oke'));
137
+ assert.strictEqual(doneHit, 1);
138
+ assert.ok(!texts(calls).some((x) => /error/i.test(x)));
139
+ });
140
+
141
+ /* ===== 9. broadcast ===== */
142
+ t('broadcast mengirim ke semua + merangkum kegagalan', async () => {
143
+ const { bot } = buatBot(); await boot(bot);
144
+ bot.api.config.use(async (prev, method, payload) => {
145
+ if (method === 'sendMessage' && payload.chat_id === 99) {
146
+ const e = new Error('Forbidden: bot was blocked by the user');
147
+ e.description = 'bot was blocked by the user'; throw e;
148
+ }
149
+ return prev(method, payload);
150
+ });
151
+ const hasil = await bot.broadcast([1, 99, 2], 'tes', { delay: 0 });
152
+ assert.strictEqual(hasil.terkirim + hasil.gagal, 3);
153
+ assert.strictEqual(hasil.gagal, 1);
154
+ assert.strictEqual(hasil.errors[0].chatId, 99);
155
+ });
156
+
157
+ /* ===== 10. humanize ===== */
158
+ t('humanize menerjemahkan error API populer', () => {
159
+ assert.ok(/diblokir/i.test(humanize(new Error('bot was blocked by the user')).saran));
160
+ const asing = humanize(new Error('sesuatu yang tidak dikenal'));
161
+ assert.strictEqual(asing.saran, null);
162
+ });
163
+
164
+ /* ===== 11. session default ===== */
165
+ t('ctx.session selalu {} tanpa setup apa pun', async () => {
166
+ const { bot } = buatBot(); await boot(bot);
167
+ let seen = null;
168
+ bot.on('message:text', (ctx) => { seen = ctx.session; });
169
+ await bot.handleUpdate(uMsg('cek sesi'));
170
+ assert.ok(seen && typeof seen === 'object');
171
+ });
172
+
173
+ /* ===== 12. onError kustom ===== */
174
+ t('onError kustom menerima err+ctx, handler error TIDAK crash', async () => {
175
+ const { bot } = buatBot(); await boot(bot);
176
+ const got = [];
177
+ bot.opts.onError = (err, ctx) => got.push([String(err), ctx && ctx.chat.id]);
178
+ bot.cmd('boom', () => { throw new Error('meledak'); });
179
+ await bot.handleUpdate(uMsg('/boom'));
180
+ assert.strictEqual(got.length, 1);
181
+ assert.ok(/meledak/.test(got[0][0]) && got[0][1] === 555);
182
+ });
183
+
184
+ /* ===== 13. polling 409 → auto-retry; stop bersih ===== */
185
+ t('launch: getUpdates 409 → retry halus; stop() resolve runPromise', async () => {
186
+ let polls = 0;
187
+ const transport = async (method) => {
188
+ if (method === 'getMe') return { id: 1, is_bot: true, first_name: 'X', username: 'x' };
189
+ if (method === 'getUpdates') {
190
+ polls++;
191
+ if (polls === 1) { const e = new Error('terminated by other getUpdates request'); e.error_code = 409; e.description = e.message; throw e; }
192
+ await new Promise((r) => setTimeout(r, 40));
193
+ return [];
194
+ }
195
+ return true;
196
+ };
197
+ const bot = new TeleBibz('123456:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', { silent: true, transport });
198
+ await bot.launch({ noSignalHandlers: true, conflictDelay: 25 });
199
+ await new Promise((r) => setTimeout(r, 200));
200
+ bot.stop();
201
+ await bot.runPromise;
202
+ assert.ok(polls >= 2, `harus retry minimal sekali, polls=${polls}`);
203
+ });
204
+
205
+ /* ===== 15. Proxy API generik: metode apapun ===== */
206
+ t('Proxy: api.metodeBebas(payload) → callApi mentah', async () => {
207
+ const { bot, calls } = buatBot(); await boot(bot);
208
+ await bot.api.sendDiceCustom({ chat_id: 1, emoji: '🎲' });
209
+ const c = calls.find((x) => x.method === 'sendDiceCustom');
210
+ assert.ok(c && c.payload.emoji === '🎲');
211
+ });
212
+
213
+ /* ===== 16. transformer pipeline ===== */
214
+ t('api.config.use: transformer melihat semua panggilan', async () => {
215
+ const { bot } = buatBot(); await boot(bot);
216
+ const seen = [];
217
+ bot.api.config.use(async (prev, m, p) => { seen.push(m); return prev(m, p); });
218
+ await bot.api.sendMessage(1, 'a'); await bot.api.getMyCommands();
219
+ assert.deepStrictEqual(seen, ['sendMessage', 'getMyCommands']);
220
+ });
221
+
222
+ /* ===== 17. autoRetry menahan 429 sekali ===== */
223
+ t('autoRetry: 429 + retry_after dihormati lalu sukses', async () => {
224
+ let hits = 0;
225
+ const tr = async (m) => { hits++; if (hits === 1) { const e = new Error('Too Many Requests'); e.error_code = 429; e.parameters = { retry_after: 0 }; throw e; } return true; };
226
+ const out = await autoRetry({ maxRetry: 2, baseDelayMs: 1 })(tr, 'sendMessage', {});
227
+ assert.ok(out === true && hits === 2);
228
+ });
229
+
230
+ /* ===== 18. throttler memanggil berurutan ===== */
231
+ t('throttler: semua panggilan lolos berurutan', async () => {
232
+ const order = []; const tr = async (m) => { order.push(m); return m; };
233
+ const th = throttler({ perSecond: 1000 });
234
+ await Promise.all([th(tr, 'a', {}), th(tr, 'b', {}), th(tr, 'c', {})]);
235
+ assert.deepStrictEqual(order, ['a', 'b', 'c']);
236
+ });
237
+
238
+ /* ===== 19. limiter menahan spam ===== */
239
+ t('limiter: update ke-4 dalam window ditahan', async () => {
240
+ const { bot } = buatBot(); await boot(bot);
241
+ let hits = 0;
242
+ bot.use(limiter({ windowMs: 60000, limit: 3 }));
243
+ bot.on(':text', () => { hits++; });
244
+ for (let i = 0; i < 5; i++) await bot.handleUpdate(uMsg('x' + i));
245
+ assert.strictEqual(hits, 3);
246
+ });
247
+
248
+ /* ===== 20. Menu render + tekan ===== */
249
+ t('Menu: render keyboard & handler tombol jalan', async () => {
250
+ const { bot, calls } = buatBot(); await boot(bot);
251
+ const mc = new MenuContainer();
252
+ const m = mc.create('cfg');
253
+ let pressed = 0;
254
+ m.text('Tombol', async (ctx) => { pressed++; await ctx.answerCallbackQuery('sip'); });
255
+ bot.use(mc);
256
+ const rm = await m.render({});
257
+ const cbData = rm.inline_keyboard[0][0].callback_data;
258
+ assert.strictEqual(cbData, 'cfg|0');
259
+ await bot.handleUpdate({ update_id: ++uid, callback_query: { id: 'c1', from: USER, chat_instance: 'x', data: cbData, message: { message_id: 9, from: { id: 99, is_bot: true, first_name: 'B' }, chat: CHAT, date: 1 } } });
260
+ assert.strictEqual(pressed, 1);
261
+ assert.ok(calls.some((c) => c.method === 'answerCallbackQuery' && c.payload.text === 'sip'));
262
+ });
263
+
264
+ /* ===== 21. inlineQuery ===== */
265
+ t('inlineQuery: regex cocok, answerInlineQuery terkirim', async () => {
266
+ const { bot, calls } = buatBot(); await boot(bot);
267
+ bot.inlineQuery(/kucing/i, async (ctx) => {
268
+ await ctx.answerInlineQuery([iq.article('1', 'Kucing garong', { message_text: 'meong!' })], { cache_time: 0 });
269
+ });
270
+ await bot.handleUpdate({ update_id: ++uid, inline_query: { id: 'i1', from: USER, query: 'kucing lucu', offset: '' } });
271
+ const c = calls.find((x) => x.method === 'answerInlineQuery');
272
+ assert.ok(c && c.payload.results[0].type === 'article');
273
+ });
274
+
275
+ /* ===== 22. replyWithMediaGroup ===== */
276
+ t('replyWithMediaGroup membentuk payload media', async () => {
277
+ const { bot, calls } = buatBot(); await boot(bot);
278
+ bot.cmd('album', (ctx) => ctx.replyWithMediaGroup([
279
+ InputMediaBuilder.photo('https://a/1.jpg'), InputMediaBuilder.photo('https://a/2.jpg', { caption: 'dua' }),
280
+ ]));
281
+ await bot.handleUpdate(uMsg('/album'));
282
+ const c = calls.find((x) => x.method === 'sendMediaGroup');
283
+ assert.ok(c && c.payload.media.length === 2 && c.payload.media[1].caption === 'dua');
284
+ });
285
+
286
+ /* ===== 23. composer lanjutan: branch/drop/filter ===== */
287
+ t('branch/drop/filter berperilaku benar (semantik grammY)', async () => {
288
+ // bot A: branch memilih sub-pohon
289
+ const A = buatBot(); await boot(A.bot);
290
+ const tagA = [];
291
+ A.bot.branch((ctx) => ctx.msg.text === 'yes', async () => { tagA.push('A'); }, async () => { tagA.push('B'); });
292
+ await A.bot.handleUpdate(uMsg('yes'));
293
+ await A.bot.handleUpdate(uMsg('no'));
294
+ assert.deepStrictEqual(tagA, ['A', 'B']);
295
+
296
+ // bot B: filter menjalankan hanya saat cocok; drop menahan saat cocok
297
+ const B = buatBot(); await boot(B.bot);
298
+ const tagB = [];
299
+ B.bot.filter((ctx) => ctx.msg.text.startsWith('fi'), async () => { tagB.push('F'); });
300
+ B.bot.drop((ctx) => ctx.msg.text === 'toxic', async () => { tagB.push('TERTAHAN'); });
301
+ await B.bot.handleUpdate(uMsg('file update'));
302
+ await B.bot.handleUpdate(uMsg('toxic')); // drop(pred=true) → mw ditahan ✓
303
+ await B.bot.handleUpdate(uMsg('bukan cocok')); // drop(pred=false) → mw jalan ✓
304
+ assert.deepStrictEqual(tagB, ['F', 'TERTAHAN']);
305
+ });
306
+
307
+ /* ===== 24. ctx.getFile pilih photo terbesar ===== */
308
+ t('ctx.getFile() memilih ukuran photo terbesar', async () => {
309
+ const { bot, calls } = buatBot(); await boot(bot);
310
+ bot.on('message:photo', async (ctx) => { const f = await ctx.getFile(); await ctx.reply('got:' + f.file_path || 'x'); });
311
+ await bot.handleUpdate({
312
+ update_id: ++uid,
313
+ message: { message_id: ++mid, from: USER, chat: CHAT, date: 1, photo: [
314
+ { file_id: 'kecil', width: 90, height: 90 }, { file_id: 'besar', width: 800, height: 600 },
315
+ ] },
316
+ });
317
+ const g = calls.find((c) => c.method === 'getFile');
318
+ assert.ok(g && g.payload.file_id === 'besar');
319
+ });
320
+
321
+ /* ===== 14. webhook handler Node murni ===== */
322
+ t('webhook(): body JSON diproses handleUpdate', async () => {
323
+ const { Readable } = require('stream');
324
+ const { bot, calls } = buatBot(); await boot(bot);
325
+ bot.cmd('start', (ctx) => ctx.reply('hai webhook'));
326
+ const handler = bot.webhook();
327
+ const upd = uMsg('/start');
328
+ const req = Readable.from([JSON.stringify(upd)]);
329
+ let status = 0, bodyOut = '';
330
+ const res = { set statusCode(v) { status = v; }, get statusCode() { return status; }, setHeader() {}, end(b) { bodyOut = b; } };
331
+ await handler(req, res);
332
+ assert.strictEqual(status, 200);
333
+ assert.ok(texts(calls).some((x) => /hai webhook/.test(x)));
334
+ });
@@ -1,37 +0,0 @@
1
- # Code of Conduct
2
-
3
- ## Our pledge
4
-
5
- We are committed to making participation in the telebibz community welcoming, respectful, technically rigorous, and safe for everyone. Contributors, maintainers, reviewers, users, and other participants are expected to act in good faith and to prioritize constructive collaboration.
6
-
7
- ## Expected behavior
8
-
9
- Participants should communicate clearly and respectfully, assume good intent while discussing technical disagreements, provide actionable feedback, document relevant context, respect privacy and confidentiality, and accept that maintainers may make decisions based on project scope, security, compatibility, and release risk.
10
-
11
- When reporting a defect or security issue, include reproducible technical information without publishing secrets, personal data, or exploit details that could put users at risk. When reviewing code, focus on the implementation and its consequences rather than the person who wrote it.
12
-
13
- ## Unacceptable behavior
14
-
15
- Harassment, discrimination, threats, intimidation, personal attacks, doxxing, deliberate disruption, sexualized conduct in project spaces, impersonation, publishing private information without consent, spam, and knowingly submitting malicious or deceptive changes are not acceptable. Attempts to bypass project security controls, expose credentials, poison dependencies, or manipulate release automation are treated as serious violations.
16
-
17
- ## Scope
18
-
19
- This Code of Conduct applies to repository issues, pull requests, discussions, reviews, commits, releases, documentation, official project channels, and any interaction where a participant is representing telebibz or its maintainers.
20
-
21
- ## Enforcement
22
-
23
- Maintainers may remove or edit content, close discussions, restrict participation, reject contributions, revoke project permissions, or take other proportionate action. Serious or repeated violations may result in a permanent ban from project spaces. Enforcement decisions consider the severity, intent, impact, recurrence, and available evidence.
24
-
25
- ## Reporting
26
-
27
- Report conduct concerns privately to the maintainers through the repository's private security/contact channel. Do not open a public issue for sensitive reports. Include links, dates, relevant usernames, and a concise description of what occurred. Do not include credentials or unnecessary personal information.
28
-
29
- Security vulnerabilities must be reported according to [SECURITY.md](SECURITY.md), not through a public issue.
30
-
31
- ## Appeals
32
-
33
- A participant may request a review of an enforcement decision by providing relevant context privately. Appeals are reviewed by a maintainer who was not the sole decision-maker for the original action where practical. Retaliation against a good-faith report or appeal is prohibited.
34
-
35
- ## Attribution
36
-
37
- This policy is informed by the [Contributor Covenant](https://www.contributor-covenant.org/version/2/1/code_of_conduct/), adapted for the technical and security needs of this project.
package/CONTRIBUTING.md DELETED
@@ -1,59 +0,0 @@
1
- # Contributing to telebibz
2
-
3
- Thank you for helping improve telebibz. Contributions are welcome when they are focused, reproducible, tested, documented, and compatible with the project's security and release policies.
4
-
5
- ## Before you start
6
-
7
- Read [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md), [CONTRIBUTION_RULES.md](CONTRIBUTION_RULES.md), [SECURITY.md](SECURITY.md), and [RELEASE_POLICY.md](RELEASE_POLICY.md). Do not open a public issue for a security vulnerability or include tokens, private chat identifiers, production payloads, or other sensitive data in an issue, pull request, test, log, screenshot, or commit.
8
-
9
- Check existing issues and pull requests before opening a new one. For a defect, use the bug template and include a minimal reproduction. For a new capability, use the feature template and explain the public API, behavior, compatibility, and testing implications.
10
-
11
- ## Local setup
12
-
13
- The project requires Node.js `>=22` and uses npm for release-compatible commands. Install dependencies and run the baseline checks:
14
-
15
- ```bash
16
- npm ci
17
- npm run typecheck
18
- npm run test:types
19
- npm run test:examples
20
- npm run lint
21
- npm test
22
- npm run build
23
- npm run security
24
- npm run release:check
25
- ```
26
-
27
- The credential-gated Telegram E2E suite is skipped unless the required test environment variables are provided. Never use production credentials in tests. Use `MockTransport`, `createTestBot()`, `createMockUpdate()`, and the integration fixtures for deterministic tests.
28
-
29
- ## Branches and commits
30
-
31
- Create a focused branch from `main`. Keep unrelated refactors out of a feature or bug-fix pull request. Use an imperative Conventional Commit-style message, for example `fix: isolate polling handler failures` or `docs: expand storage adapter reference`. Include `[skip release]` only when a change must not trigger the GitHub-to-npm release workflow; maintainers may remove that marker when a release is appropriate.
32
-
33
- ## Implementation expectations
34
-
35
- Public behavior must be implemented in TypeScript with strict typing. Do not add fake responses, silent stubs, undocumented breaking behavior, or untested branches. Preserve ESM and CommonJS builds, zero runtime vendor dependencies in the core package, Node.js `>=22` compatibility, and the existing package export map.
36
-
37
- Changes to routing, context, lifecycle, transport, storage, queue, scheduler, approval, generated API declarations, or release automation require regression tests. Changes to a public function, class, method, option, error, event, or generated method require a corresponding API documentation update. Changes to examples require `npm run test:examples` and must use placeholders instead of credentials. Changes that affect package contents must pass `release:check` and `npm pack --dry-run`.
38
-
39
- ## Pull request process
40
-
41
- Open a pull request against `main` and complete the pull request template. Explain the problem, solution, compatibility impact, test evidence, documentation changes, and security implications. Keep the diff reviewable. Maintainers may request changes, split a pull request, or ask for a follow-up issue when scope is too broad.
42
-
43
- Every pull request must pass the CI workflow. A maintainer reviews API compatibility, error handling, tests, documentation, package contents, and release impact before approval. Do not merge while required checks are failing or unresolved security concerns remain.
44
-
45
- ## Documentation and translations
46
-
47
- English is the default README language. Keep `README.id.md`, `README.zh-CN.md`, `docs/API.id.md`, and `docs/API.zh-CN.md` synchronized when public behavior changes. Code signatures, method names, package names, environment variables, and command names must remain exact in every translation.
48
-
49
- ## Release process
50
-
51
- Normal pushes to `main` can trigger the protected auto-publish workflow. The workflow runs quality gates, computes an unused patch version, publishes the package, commits the version, creates a tag, and creates a GitHub Release. See [RELEASE_AUTOMATION.md](RELEASE_AUTOMATION.md) for the required `NPM_TOKEN` secret and private-source provenance constraint. Contributors must not publish directly to npm unless explicitly authorized by the maintainers.
52
-
53
- ## Showcase submissions
54
-
55
- Public projects using Telebibz may be submitted through a pull request to [SHOWCASE.md](SHOWCASE.md). Include only public links and a short description. Never include bot tokens, private endpoints, personal data, or confidential source code.
56
-
57
- ## Questions
58
-
59
- Use the question/support template for usage questions and consult the [English API reference](docs/API.md) first. Keep support requests free of credentials and private user data.
@@ -1,41 +0,0 @@
1
- # Contribution Rules
2
-
3
- These rules are the operational requirements for contributions to telebibz. They complement [CONTRIBUTING.md](CONTRIBUTING.md) and [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md).
4
-
5
- ## Scope and ownership
6
-
7
- A contribution must have a clear owner, a stated problem, and a bounded outcome. Large changes should be split into reviewable pull requests. Maintainers may reject work that is technically correct but outside the package scope, duplicates an existing design, introduces unnecessary dependencies, or creates disproportionate maintenance cost.
8
-
9
- ## API and compatibility rules
10
-
11
- Public exports, constructor options, method signatures, event names, error classes, generated method names, package entrypoints, and persisted data formats are compatibility-sensitive. A breaking change requires an explicit migration note, a versioning decision, updated type-level tests, and maintainer approval.
12
-
13
- New public APIs must have strict TypeScript types, runtime validation where input can be unsafe, deterministic error behavior, examples, and API reference documentation. Avoid `any` in public signatures. When a Telegram type is not specialized in the core method map, use the vendored `TelegramTypes` declarations or an honest generic boundary rather than inventing an inaccurate shape.
14
-
15
- ## Runtime and error-handling rules
16
-
17
- Network operations must honor timeout and cancellation behavior. Retry logic must be bounded and must not retry authentication or validation failures blindly. Update handlers must not terminate polling because one update failed. Background tasks must surface errors through documented hooks or events and must not create unhandled promise rejections.
18
-
19
- Callback-query handling must work for both message-backed and inline callbacks. Router behavior must state whether first-match or all-match is used. Nested middleware must not execute terminal handlers twice. Persistent state must use the `Storage` abstraction and must document consistency, TTL, and driver assumptions.
20
-
21
- ## Testing rules
22
-
23
- A pull request must add or update tests for every changed behavior. Use unit tests for pure logic, integration tests for module boundaries, type-level tests for public signatures, and credential-gated E2E tests only for real Telegram behavior that cannot be represented safely by mocks. Tests must be deterministic and must clean up timers, temporary files, listeners, and network resources.
24
-
25
- A bug fix is incomplete without a regression test that fails against the old behavior. A feature is incomplete without tests for success, invalid input, cancellation, retry/failure, and boundary behavior where applicable.
26
-
27
- ## Documentation rules
28
-
29
- Documentation must describe the implementation that exists in source, not a planned feature. Every documented limitation must remain accurate. English is canonical; Indonesian and Simplified Chinese translations must preserve signatures, code blocks, URLs, environment variables, and command names exactly.
30
-
31
- ## Dependency and security rules
32
-
33
- Do not add a runtime dependency without a written justification, license review, bundle/package impact analysis, and maintainer approval. Never commit credentials, private keys, Telegram tokens, npm tokens, test secrets, or real user data. Security-sensitive changes require review of [SECURITY.md](SECURITY.md) and must not be disclosed publicly before a fix or coordinated disclosure decision.
34
-
35
- ## Release and repository rules
36
-
37
- Do not edit or overwrite an already published npm version. Do not manually create a release tag that conflicts with the protected workflow. Do not bypass required checks, disable security audit, weaken release checks, or modify workflow permissions without maintainer approval. Use `[skip release]` only for changes that must not publish a package.
38
-
39
- ## Review standards
40
-
41
- Reviewers should verify correctness, compatibility, tests, documentation, security, package contents, and operational behavior. Approval is not a guarantee that no defect exists; it confirms that the change meets the current project acceptance criteria and is safe to merge based on the available evidence.
package/GOVERNANCE.md DELETED
@@ -1,47 +0,0 @@
1
- # Governance
2
-
3
- ## Project purpose
4
-
5
- telebibz is a TypeScript Telegram Bot Framework. Governance exists to preserve a stable public API, honest feature scope, secure release automation, and a respectful contributor community.
6
-
7
- ## Roles
8
-
9
- | Role | Responsibility |
10
- |---|---|
11
- | Users | Report reproducible defects, explain use cases, and provide feedback without publishing secrets. |
12
- | Contributors | Submit focused code, tests, documentation, and issue reports that follow the project rules. |
13
- | Reviewers | Examine correctness, compatibility, security, tests, documentation, and operational risk. |
14
- | Maintainers | Set project direction, review and merge changes, manage releases, triage issues, and protect the repository. |
15
- | Release maintainers | Control npm credentials, GitHub Actions environments, version tags, npm publication, and release verification. |
16
-
17
- One person may hold multiple roles, but security-sensitive actions should use separation of duties whenever practical.
18
-
19
- ## Decision-making
20
-
21
- Routine decisions are made by maintainers through pull request review and documented issue discussion. Decisions should be based on user benefit, implementation quality, compatibility, security, maintenance cost, and evidence from tests or production reports.
22
-
23
- For controversial or breaking changes, maintainers should document alternatives, migration impact, and the reason for the selected approach. A maintainer may request a design note before implementation. Silence is not approval for a breaking change.
24
-
25
- ## Triage
26
-
27
- New issues are initially classified as bug, feature, documentation, security, support, or duplicate. Maintainers may request a reproduction, reduce sensitive details, split scope, mark a report as blocked, or close it when the requested behavior conflicts with the documented project contract.
28
-
29
- Security reports are handled privately according to [SECURITY.md](SECURITY.md). Public issues must not contain exploit instructions, credentials, or personal data.
30
-
31
- ## Pull requests
32
-
33
- A pull request requires a passing CI workflow and at least one maintainer review. Changes affecting authentication, transport, update dispatch, persistence, release automation, package exports, or security policy may require additional review. Maintainers can require a regression test, API documentation, migration note, or threat-model explanation before merge.
34
-
35
- ## Releases
36
-
37
- The protected GitHub Actions workflow is the canonical release path. It runs quality gates, computes a new immutable npm version, publishes the package, creates a version commit and tag, and creates a GitHub Release. Direct npm publication is restricted to authorized release maintainers. Release credentials must remain in GitHub or npm secret storage and must be rotated after exposure.
38
-
39
- Because the source repository is private, the workflow uses npm publish without provenance. If the repository becomes public and provenance is enabled, the policy and workflow must be reviewed together before the next release.
40
-
41
- ## Repository protection
42
-
43
- Required CI checks, branch protection, environment approvals, workflow permissions, package scope, and immutable release rules must not be weakened casually. Any change to these controls requires a pull request, a written rationale, and maintainer approval.
44
-
45
- ## Amendments
46
-
47
- Governance changes are proposed through a pull request that explains the current problem, proposed rule, affected roles, and migration plan. The updated governance document becomes effective when the pull request merges.