create-gramstax 0.8.9 → 0.8.11

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.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- "use strict";var W=Object.create;var h=Object.defineProperty;var A=Object.getOwnPropertyDescriptor;var G=Object.getOwnPropertyNames;var B=Object.getPrototypeOf,O=Object.prototype.hasOwnProperty;var p=(o,e)=>h(o,"name",{value:e,configurable:!0});var V=(o,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let a of G(e))!O.call(o,a)&&a!==t&&h(o,a,{get:()=>e[a],enumerable:!(n=A(e,a))||n.enumerable});return o};var v=(o,e,t)=>(t=o!=null?W(B(o)):{},V(e||!o||!o.__esModule?h(t,"default",{value:o,enumerable:!0}):t,o));var q=p(()=>typeof document>"u"?new URL(`file:${__filename}`).href:document.currentScript&&document.currentScript.tagName.toUpperCase()==="SCRIPT"?document.currentScript.src:new URL("main.js",document.baseURI).href,"getImportMetaUrl"),f=q();var C=require("logging-pretty"),r=new C.LoggingPretty;var i=v(require("fs"),1),s=v(require("path"),1),E=require("url");var D=require("fs");var g=class o{static{p(this,"FsUt")}static DEFAULT_IGNORED_ENTRIES=new Set([".git",".gitignore",".gitkeep",".DS_Store","Thumbs.db"]);static isDirectoryEffectivelyEmpty(e,t=o.DEFAULT_IGNORED_ENTRIES){return(0,D.readdirSync)(e).filter(c=>!t.has(c)).length===0}static resolvePackageManagerCommand(e,t=process.platform){return t!=="win32"||e==="bun"?e:`${e}.cmd`}static resolveInitialWorkingDirectory(e=process.cwd(),t=process.env.INIT_CWD){return t??e}};var j=require("child_process"),P=v(require("enquirer"),1);var J=(0,E.fileURLToPath)(f),Y=s.dirname(J),_=class{constructor(e,t){this.projectDirectory=e;this.version=t?.version}static{p(this,"Create")}config;version;printBanner(){let e=`
2
+ "use strict";var W=Object.create;var h=Object.defineProperty;var A=Object.getOwnPropertyDescriptor;var G=Object.getOwnPropertyNames;var B=Object.getPrototypeOf,O=Object.prototype.hasOwnProperty;var p=(o,e)=>h(o,"name",{value:e,configurable:!0});var V=(o,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let a of G(e))!O.call(o,a)&&a!==t&&h(o,a,{get:()=>e[a],enumerable:!(n=A(e,a))||n.enumerable});return o};var v=(o,e,t)=>(t=o!=null?W(B(o)):{},V(e||!o||!o.__esModule?h(t,"default",{value:o,enumerable:!0}):t,o));var q=p(()=>typeof document>"u"?new URL(`file:${__filename}`).href:document.currentScript&&document.currentScript.tagName.toUpperCase()==="SCRIPT"?document.currentScript.src:new URL("main.js",document.baseURI).href,"getImportMetaUrl"),f=q();var C=require("logging-pretty"),r=new C.LoggingPretty;var i=v(require("fs"),1),s=v(require("path"),1),E=require("url");var D=require("fs");var g=class o{static{p(this,"FsUt")}static DEFAULT_IGNORED_ENTRIES=new Set([".git",".gitignore",".gitkeep",".DS_Store","Thumbs.db"]);static isDirectoryEffectivelyEmpty(e,t=o.DEFAULT_IGNORED_ENTRIES){return(0,D.readdirSync)(e).filter(c=>!t.has(c)).length===0}static resolvePackageManagerCommand(e,t=process.platform){return t!=="win32"||e==="bun"?e:`${e}.cmd`}static resolveInitialWorkingDirectory(e=process.cwd(),t=process.env.INIT_CWD){return t??e}};var j=require("child_process"),P=v(require("enquirer"),1);var J=(0,E.fileURLToPath)(f),Y=s.dirname(J),_=class{constructor(e,t){this.projectDirectory=e;this.version=t?.version}projectDirectory;static{p(this,"Create")}config;version;printBanner(){let e=`
3
3
  $$$$$$\\ $$\\
4
4
  $$ __$$\\ $$ |
5
5
  $$ / \\__| $$$$$$\\ $$$$$$\\ $$$$$$\\$$$$\\ $$$$$$$\\ $$$$$$\\ $$$$$$\\ $$\\ $$\\
package/dist/src/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- var E=Object.defineProperty;var g=(p,e)=>E(p,"name",{value:e,configurable:!0});import{LoggingPretty as N}from"logging-pretty";var r=new N;import*as i from"fs";import*as a from"path";import{fileURLToPath as R}from"url";import{readdirSync as F}from"fs";var m=class p{static{g(this,"FsUt")}static DEFAULT_IGNORED_ENTRIES=new Set([".git",".gitignore",".gitkeep",".DS_Store","Thumbs.db"]);static isDirectoryEffectivelyEmpty(e,t=p.DEFAULT_IGNORED_ENTRIES){return F(e).filter(o=>!t.has(o)).length===0}static resolvePackageManagerCommand(e,t=process.platform){return t!=="win32"||e==="bun"?e:`${e}.cmd`}static resolveInitialWorkingDirectory(e=process.cwd(),t=process.env.INIT_CWD){return t??e}};import{spawn as j}from"child_process";import P from"enquirer";var L=R(import.meta.url),T=a.dirname(L),d=class{constructor(e,t){this.projectDirectory=e;this.version=t?.version}static{g(this,"Create")}config;version;printBanner(){let e=`
2
+ var E=Object.defineProperty;var g=(p,e)=>E(p,"name",{value:e,configurable:!0});import{LoggingPretty as N}from"logging-pretty";var r=new N;import*as i from"fs";import*as a from"path";import{fileURLToPath as R}from"url";import{readdirSync as F}from"fs";var m=class p{static{g(this,"FsUt")}static DEFAULT_IGNORED_ENTRIES=new Set([".git",".gitignore",".gitkeep",".DS_Store","Thumbs.db"]);static isDirectoryEffectivelyEmpty(e,t=p.DEFAULT_IGNORED_ENTRIES){return F(e).filter(o=>!t.has(o)).length===0}static resolvePackageManagerCommand(e,t=process.platform){return t!=="win32"||e==="bun"?e:`${e}.cmd`}static resolveInitialWorkingDirectory(e=process.cwd(),t=process.env.INIT_CWD){return t??e}};import{spawn as j}from"child_process";import P from"enquirer";var L=R(import.meta.url),T=a.dirname(L),d=class{constructor(e,t){this.projectDirectory=e;this.version=t?.version}projectDirectory;static{g(this,"Create")}config;version;printBanner(){let e=`
3
3
  $$$$$$\\ $$\\
4
4
  $$ __$$\\ $$ |
5
5
  $$ / \\__| $$$$$$\\ $$$$$$\\ $$$$$$\\$$$$\\ $$$$$$$\\ $$$$$$\\ $$$$$$\\ $$\\ $$\\
@@ -0,0 +1,287 @@
1
+ /**
2
+ * E2E Test Flow for Template Bot
3
+ *
4
+ * Setup:
5
+ * 1. MockTelegramServer starts on random port → apiRoot captured
6
+ * 2. BotCore initialized with webhook:http://127.0.0.1:3009/webhook/e2e-template
7
+ * 3. CacheExternal (memory) shared across tests, cleared in beforeEach
8
+ *
9
+ * Test Flow:
10
+ * T1. /start → Welcome message + inline keyboard (Random Number, Sum Number, Help)
11
+ * T2. /start → callback:help → editMessage → Help Message + Back button
12
+ * T3. /start → callback:random-number → editMessage → Random Number + Shake/Back buttons
13
+ * T4. /start → callback:random-number → callback:random-number → editMessage → refreshed number
14
+ * T5. /start → callback:sum-number → text:23 → text:10 → Result Sum: 33 + Home button
15
+ * T6. /start → callback:sum-number → text:not-a-number → re-prompt Input Left Number
16
+ * T7. /start → callback:nonexistent_action → graceful handling (no crash)
17
+ * T8. /start → verify sendMessage request captured in mockServer.requests
18
+ * T9. /start → callback:help → callback:random-number → sequential chain
19
+ * T10. sendPhoto → Route Not Found (no handler for photo)
20
+ * T11. /start with custom user/chat → custom ID propagation verified
21
+ * T12. /start without username → Username Required page (UserGuard blocks)
22
+ * T13. /start → callback:help → callback:random-number → keyboard structure validation (text + callback_data)
23
+ *
24
+ * Cleanup:
25
+ * - botCore.stop() + mockServer.stop() in afterAll
26
+ * - cacheSession.clear() + mockServer.clearRequests() in beforeEach
27
+ */
28
+ import {join} from "node:path"
29
+ import {BotCore} from "../src/core/bot"
30
+ import {TemplateManager, CacheExternal} from "gramstax"
31
+ import {MockTelegramServer} from "gramstax"
32
+ import {describe, it, expect, beforeAll, afterAll, beforeEach} from "bun:test"
33
+
34
+ let mockServer: MockTelegramServer
35
+ let botCore: BotCore
36
+ let cacheSession: CacheExternal
37
+
38
+ const USER_1 = {
39
+ responseTimeoutMs: undefined, // 9000,
40
+ user: {
41
+ id: 123456789,
42
+ first_name: `Test`,
43
+ last_name: `user`,
44
+ username: `testuser`,
45
+ language_code: `en`,
46
+ is_bot: false
47
+ }
48
+ }
49
+
50
+ beforeAll(async () => {
51
+ mockServer = new MockTelegramServer({log: false})
52
+ await mockServer.start()
53
+
54
+ const templateManager = new TemplateManager({path: [join(import.meta.dir, `../src/pages`)]})
55
+ const cwd = process.cwd()
56
+ process.chdir(join(import.meta.dir, `..`))
57
+
58
+ cacheSession = new CacheExternal(`memory`)
59
+
60
+ botCore = new BotCore({
61
+ deploy: `webhook:http://127.0.0.1:3009/webhook/e2e-template`,
62
+ token: `test-token`,
63
+ log: false,
64
+ templateManager,
65
+ cacheSession,
66
+ botConfig: {client: {apiRoot: mockServer.apiRoot}}
67
+ })
68
+
69
+ process.chdir(cwd)
70
+
71
+ const startedAt = Date.now()
72
+ while (!mockServer.webhookUrl && Date.now() - startedAt < 5000) {
73
+ await new Promise((r) => setTimeout(r, 50))
74
+ }
75
+ })
76
+
77
+ afterAll(async () => {
78
+ await botCore.stop()
79
+ await mockServer.stop()
80
+ })
81
+
82
+ beforeEach(async () => {
83
+ mockServer.clearRequests()
84
+ await cacheSession.clear()
85
+ })
86
+
87
+ describe(`E2E: Template bot via MockTelegramServer (webhook)`, () => {
88
+ it(`responds to /start command with welcome message and navigation buttons`, async () => {
89
+ const responses = await mockServer.sendText(`/start`, USER_1)
90
+
91
+ expect(responses.length).toBeGreaterThan(0)
92
+ const reply = responses[0]!
93
+ expect(reply).toBeDefined()
94
+ expect(reply!.body?.text).toContain(`Welcome to the bot`)
95
+
96
+ const keyboard = reply!.body?.reply_markup?.inline_keyboard
97
+ expect(Array.isArray(keyboard)).toBe(true)
98
+ expect(keyboard.length).toBeGreaterThan(0)
99
+
100
+ const allButtons = keyboard.flat()
101
+ const buttonLabels = allButtons.map((b: any) => b.text)
102
+ expect(buttonLabels).toContain(`🎰 Random Number`)
103
+ expect(buttonLabels).toContain(`➕ Sum Number`)
104
+ expect(buttonLabels).toContain(`🆘 Help`)
105
+ })
106
+
107
+ it(`navigates to Help page via callback and shows back button`, async () => {
108
+ await mockServer.sendText(`/start`, USER_1)
109
+ mockServer.clearRequests()
110
+
111
+ const responses = await mockServer.sendCallbackQuery(`help`, USER_1)
112
+
113
+ expect(responses.length).toBeGreaterThan(0)
114
+ const edit = responses[0]!
115
+ expect(edit!.body?.text).toContain(`Help Message`)
116
+
117
+ const keyboard = edit!.body?.reply_markup?.inline_keyboard
118
+ expect(Array.isArray(keyboard)).toBe(true)
119
+ const firstButton = keyboard[0]?.[0]
120
+ expect(firstButton?.text).toContain(`Back`)
121
+ })
122
+
123
+ it(`navigates to Random Number page via callback and shows generated number`, async () => {
124
+ await mockServer.sendText(`/start`, USER_1)
125
+ mockServer.clearRequests()
126
+
127
+ const responses = await mockServer.sendCallbackQuery(`random-number`, USER_1)
128
+
129
+ expect(responses.length).toBeGreaterThan(0)
130
+ const edit = responses[0]!
131
+ expect(edit!.body?.text).toContain(`Random Number`)
132
+
133
+ const keyboard = edit!.body?.reply_markup?.inline_keyboard
134
+ expect(Array.isArray(keyboard)).toBe(true)
135
+ const allButtons = keyboard.flat()
136
+ const buttonLabels = allButtons.map((b: any) => b.text)
137
+ expect(buttonLabels).toContain(`🎲 Shake`)
138
+ expect(buttonLabels).toContain(`⬅️ Back`)
139
+ })
140
+
141
+ it(`refreshes random number via Shake callback`, async () => {
142
+ await mockServer.sendText(`/start`, USER_1)
143
+ mockServer.clearRequests()
144
+
145
+ await mockServer.sendCallbackQuery(`random-number`, USER_1)
146
+ mockServer.clearRequests()
147
+
148
+ const responses = await mockServer.sendCallbackQuery(`random-number`, USER_1)
149
+
150
+ expect(responses.length).toBeGreaterThan(0)
151
+ const edit = responses[0]!
152
+ expect(edit!.body?.text).toContain(`Random Number`)
153
+ })
154
+
155
+ it(`completes Sum Number two-step session flow`, async () => {
156
+ await mockServer.sendText(`/start`, USER_1)
157
+ mockServer.clearRequests()
158
+
159
+ const sumResponses = await mockServer.sendCallbackQuery(`sum-number`, USER_1)
160
+ expect(sumResponses.length).toBeGreaterThan(0)
161
+ expect(sumResponses[0]!.body?.text).toContain(`Input Left Number`)
162
+
163
+ mockServer.clearRequests()
164
+
165
+ const leftResponses = await mockServer.sendText(`23`, USER_1)
166
+ expect(leftResponses.length).toBeGreaterThan(0)
167
+ expect(leftResponses[0]!.body?.text).toContain(`Input Right Number`)
168
+
169
+ mockServer.clearRequests()
170
+
171
+ const resultResponses = await mockServer.sendText(`10`, USER_1)
172
+ expect(resultResponses.length).toBeGreaterThan(0)
173
+ expect(resultResponses[0]!.body?.text).toContain(`Result Sum`)
174
+ expect(resultResponses[0]!.body?.text).toContain(`33`)
175
+
176
+ const keyboard = resultResponses[0]!.body?.reply_markup?.inline_keyboard
177
+ expect(Array.isArray(keyboard)).toBe(true)
178
+ const firstButton = keyboard[0]?.[0]
179
+ expect(firstButton?.text).toContain(`Home`)
180
+ })
181
+
182
+ it(`handles invalid input in Sum Number gracefully`, async () => {
183
+ await mockServer.sendText(`/start`, USER_1)
184
+ mockServer.clearRequests()
185
+
186
+ await mockServer.sendCallbackQuery(`sum-number`, USER_1)
187
+ mockServer.clearRequests()
188
+
189
+ const leftResponses = await mockServer.sendText(`not-a-number`, USER_1)
190
+ expect(leftResponses.length).toBeGreaterThan(0)
191
+ expect(leftResponses[0]!.body?.text).toContain(`Input Left Number`)
192
+ })
193
+
194
+ it(`shows route not found for unrecognized callback`, async () => {
195
+ await mockServer.sendText(`/start`, USER_1)
196
+ mockServer.clearRequests()
197
+
198
+ const responses = await mockServer.sendCallbackQuery(`nonexistent_action_${Math.random()}`, USER_1)
199
+ expect(responses.length).toBeGreaterThanOrEqual(0)
200
+ })
201
+
202
+ it(`captures outbound requests in mockServer.requests`, async () => {
203
+ const responses = await mockServer.sendText(`/start`, USER_1)
204
+
205
+ expect(responses.length).toBeGreaterThan(0)
206
+ const sendMessageReq = mockServer.lastRequest(`sendMessage`)
207
+ expect(sendMessageReq).toBeDefined()
208
+ expect(sendMessageReq?.body?.text).toContain(`Welcome to the bot`)
209
+ })
210
+
211
+ it(`handles multiple sequential interactions`, async () => {
212
+ const res1 = await mockServer.sendText(`/start`, USER_1)
213
+ expect(res1[0]?.body?.text).toContain(`Welcome to the bot`)
214
+
215
+ mockServer.clearRequests()
216
+
217
+ const res2 = await mockServer.sendCallbackQuery(`help`, USER_1)
218
+ expect(res2[0]?.body?.text).toContain(`Help Message`)
219
+
220
+ mockServer.clearRequests()
221
+
222
+ const res3 = await mockServer.sendCallbackQuery(`random-number`, USER_1)
223
+ expect(res3[0]?.body?.text).toContain(`Random Number`)
224
+ })
225
+
226
+ it(`sendPhoto triggers a response`, async () => {
227
+ const responses = await mockServer.sendPhoto({caption: `test photo`, ...USER_1})
228
+
229
+ expect(responses.length).toBeGreaterThan(0)
230
+ expect(responses[0]?.body?.text).toContain(`Route Not Found`)
231
+ })
232
+
233
+ it(`supports custom user and chat options`, async () => {
234
+ const responses = await mockServer.sendText(`/start`, {
235
+ user: {id: 99999, first_name: `CustomUser`, username: `customuser`},
236
+ chat: {id: 88888, type: `private`, first_name: `CustomChat`}
237
+ })
238
+
239
+ expect(responses.length).toBeGreaterThan(0)
240
+ expect(responses[0]?.body?.text).toContain(`Welcome to the bot`)
241
+
242
+ const lastUpdate = mockServer.updates[mockServer.updates.length - 1]
243
+ expect(lastUpdate).toBeDefined()
244
+ const msg = (lastUpdate as any)?.message
245
+ expect(msg?.from?.id).toBe(99999)
246
+ expect(msg?.chat?.id).toBe(88888)
247
+ })
248
+
249
+ it(`blocks user without username and shows username required page`, async () => {
250
+ const responses = await mockServer.sendText(`/start`, {
251
+ user: {id: 77777, first_name: `NoUsername`},
252
+ chat: {id: 77777, type: `private`, first_name: `NoUsername`}
253
+ })
254
+
255
+ expect(responses.length).toBeGreaterThan(0)
256
+ const reply = responses[0]!
257
+ expect(reply.body?.text).toContain(`Username Required`)
258
+ expect(reply.body?.text).toContain(`set the username first`)
259
+ })
260
+
261
+ it(`validates inline_keyboard structure for all navigation pages`, async () => {
262
+ const res1 = await mockServer.sendText(`/start`, USER_1)
263
+ expect(Array.isArray(res1[0]?.body?.reply_markup?.inline_keyboard)).toBe(true)
264
+
265
+ mockServer.clearRequests()
266
+
267
+ const res2 = await mockServer.sendCallbackQuery(`help`, USER_1)
268
+ const kb2 = res2[0]?.body?.reply_markup?.inline_keyboard
269
+ expect(Array.isArray(kb2)).toBe(true)
270
+ expect(kb2!.length).toBeGreaterThan(0)
271
+ expect(kb2![0]?.[0]?.text).toBeDefined()
272
+ expect(kb2![0]?.[0]?.callback_data).toBeDefined()
273
+
274
+ mockServer.clearRequests()
275
+
276
+ const res3 = await mockServer.sendCallbackQuery(`random-number`, USER_1)
277
+ const kb3 = res3[0]?.body?.reply_markup?.inline_keyboard
278
+ expect(Array.isArray(kb3)).toBe(true)
279
+ expect(kb3!.length).toBeGreaterThan(0)
280
+ for (const row of kb3!) {
281
+ for (const btn of row) {
282
+ expect(btn?.text).toBeDefined()
283
+ expect(btn?.callback_data).toBeDefined()
284
+ }
285
+ }
286
+ })
287
+ })
@@ -32,10 +32,13 @@
32
32
  "start": "bun src/index.ts",
33
33
  "build": "bun build src/index.ts --outdir dist --target bun",
34
34
  "lint": "eslint .",
35
- "lint:fix": "eslint . --fix"
35
+ "lint:fix": "eslint . --fix",
36
+ "test": "bun test test/",
37
+ "test:e2e": "bun test e2e/",
38
+ "test:all": "bun test test/ e2e/"
36
39
  },
37
40
  "dependencies": {
38
- "gramstax": "^0.8.8",
41
+ "gramstax": "^0.8.10",
39
42
  "dayjs": "^1.11.13"
40
43
  },
41
44
  "peerDependencies": {
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-gramstax",
3
- "version": "0.8.9",
3
+ "version": "0.8.11",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public",