create-gramstax 0.11.2 → 0.12.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.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- "use strict";var B=Object.create;var j=Object.defineProperty;var G=Object.getOwnPropertyDescriptor;var Y=Object.getOwnPropertyNames;var V=Object.getPrototypeOf,q=Object.prototype.hasOwnProperty;var l=(s,e)=>j(s,"name",{value:e,configurable:!0});var J=(s,e,n,t)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of Y(e))!q.call(s,i)&&i!==n&&j(s,i,{get:()=>e[i],enumerable:!(t=G(e,i))||t.enumerable});return s};var P=(s,e,n)=>(n=s!=null?B(V(s)):{},J(e||!s||!s.__esModule?j(n,"default",{value:s,enumerable:!0}):n,s));var K=l(()=>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=K();var R=require("logging-pretty"),a=new R.LoggingPretty;var r=P(require("fs"),1),o=P(require("path"),1),L=require("url");var F=require("fs");var p=class s{static{l(this,"FsUt")}static DEFAULT_IGNORED_ENTRIES=new Set([".git",".gitignore",".gitkeep",".DS_Store","Thumbs.db"]);static isDirectoryEffectivelyEmpty(e,n=s.DEFAULT_IGNORED_ENTRIES){return(0,F.readdirSync)(e).filter(c=>!n.has(c)).length===0}static resolvePackageManagerCommand(e,n=process.platform){return n!=="win32"||e==="bun"?e:`${e}.cmd`}static resolveInitialWorkingDirectory(e=process.cwd(),n=process.env.INIT_CWD){return n??e}};var S=require("child_process"),C=P(require("enquirer"),1);var z=(0,L.fileURLToPath)(f),H=o.dirname(z),v=class{constructor(e,n){this.projectDirectory=e;this.version=n?.version,this.cliOptions=n?.cliOptions}projectDirectory;static{l(this,"Create")}config;version;cliOptions;printBanner(){let e=`
2
+ "use strict";var B=Object.create;var j=Object.defineProperty;var G=Object.getOwnPropertyDescriptor;var Y=Object.getOwnPropertyNames;var V=Object.getPrototypeOf,q=Object.prototype.hasOwnProperty;var l=(s,e)=>j(s,"name",{value:e,configurable:!0});var J=(s,e,n,t)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of Y(e))!q.call(s,i)&&i!==n&&j(s,i,{get:()=>e[i],enumerable:!(t=G(e,i))||t.enumerable});return s};var P=(s,e,n)=>(n=s!=null?B(V(s)):{},J(e||!s||!s.__esModule?j(n,"default",{value:s,enumerable:!0}):n,s));var K=l(()=>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=K();var R=require("logging-pretty"),a=new R.LoggingPretty;var r=P(require("fs"),1),o=P(require("path"),1),F=require("url");var L=require("fs");var p=class s{static{l(this,"FsUt")}static DEFAULT_IGNORED_ENTRIES=new Set([".git",".gitignore",".gitkeep",".DS_Store","Thumbs.db"]);static isDirectoryEffectivelyEmpty(e,n=s.DEFAULT_IGNORED_ENTRIES){return(0,L.readdirSync)(e).filter(c=>!n.has(c)).length===0}static resolvePackageManagerCommand(e,n=process.platform){return n!=="win32"||e==="bun"?e:`${e}.cmd`}static resolveInitialWorkingDirectory(e=process.cwd(),n=process.env.INIT_CWD){return n??e}};var S=require("child_process"),C=P(require("enquirer"),1);var z=(0,F.fileURLToPath)(f),H=o.dirname(z),v=class{constructor(e,n){this.projectDirectory=e;this.version=n?.version,this.cliOptions=n?.cliOptions}projectDirectory;static{l(this,"Create")}config;version;cliOptions;printBanner(){let e=`
3
3
  $$$$$$\\ $$\\
4
4
  $$ __$$\\ $$ |
5
5
  $$ / \\__| $$$$$$\\ $$$$$$\\ $$$$$$\\$$$$\\ $$$$$$$\\ $$$$$$\\ $$$$$$\\ $$\\ $$\\
@@ -10,5 +10,7 @@ $$ | $$ |$$ | $$ __$$ |$$ | $$ | $$ | \\____$$\\ $$ |$$\\ $$ __$$ | $$
10
10
  \\______/ \\__| \\_______|\\__| \\__| \\__|\\_______/ \\____/ \\_______|\\__/ \\__|
11
11
 
12
12
  `,n=this.version?`v${this.version}`:"v0.0.0";console.log(`${e}${n}
13
- `)}getPackageManagerCommands(e){return{bun:{install:"bun install",dev:"bun dev",start:"bun start"},npm:{install:"npm install",dev:"npm run dev",start:"npm start"},pnpm:{install:"pnpm install",dev:"pnpm dev",start:"pnpm start"},yarn:{install:"yarn install",dev:"yarn dev",start:"yarn start"}}[e]}copyDirectorySync(e,n,t={}){r.existsSync(n)||r.mkdirSync(n,{recursive:!0});let i=r.readdirSync(e,{withFileTypes:!0});for(let c of i){let h=o.join(e,c.name),d=o.join(n,c.name);if(c.isDirectory())this.copyDirectorySync(h,d,t);else if(c.isFile()){let m=r.readFileSync(h,"utf-8");m=this.replaceTemplateVariables(m,t),r.writeFileSync(d,m)}}}replaceTemplateVariables(e,n){let t=e;for(let[i,c]of Object.entries(n))t=t.replace(new RegExp(`{{${i}}}`,"g"),c);return t}async promptForProjectName(){return(await C.default.prompt({type:"input",name:"projectName",message:"What is your project named?",initial:"",validate:l(n=>n===""||/^[a-z0-9-_]+$/i.test(n)?!0:"Project name can only contain letters, numbers, hyphens, and underscores","validate")})).projectName}async promptForConfiguration(){let e=await C.default.prompt([{type:"select",name:"packageManager",message:"Which package manager would you like to use?",choices:[{name:"bun",message:"Bun (recommended)",value:"bun"},{name:"npm",message:"npm",value:"npm"},{name:"pnpm",message:"pnpm",value:"pnpm"},{name:"yarn",message:"Yarn",value:"yarn"}],initial:0},{type:"select",name:"runtime",message:"Which runtime would you like to use?",choices:[{name:"bun",message:"Bun (recommended)",value:"bun"},{name:"node",message:"Node.js",value:"node"}],initial:0},{type:"confirm",name:"lmdb",message:"Would you like to include @gramstax/lmdb for persistent storage? (recommended)",initial:!0},{type:"confirm",name:"workerThread",message:"Would you like to include @gramstax/worker-thread for background jobs? (recommended)",initial:!0},{type:"confirm",name:"eslint",message:"Would you like to use ESLint?",initial:!0}]);return{packageManager:e.packageManager,runtime:e.runtime,lmdb:e.lmdb,workerThread:e.workerThread,eslint:e.eslint}}async installDependencies(e,n){return await new Promise(t=>{let i=(0,S.spawn)(p.resolvePackageManagerCommand(n),["install"],{cwd:e,stdio:"inherit",shell:!1});i.on("close",c=>c===0?t(!0):(a.fail("Dependencies installation failed"),t(!1))),i.on("error",c=>(a.fail(`Dependencies installation failed: ${c.message}`),t(!1)))})}async gatherProjectConfig(){this.printBanner();let e=this.cliOptions,n=p.resolveInitialWorkingDirectory(),t=this.projectDirectory??"",i,c=!1;if(t)i=o.resolve(n,t);else{if(e&&this._hasAnyCliFlag())throw a.error("Project name is required when using CLI flags"),new Error("Missing project name");t=await this.promptForProjectName(),t===""?(t=o.basename(n),i=n,c=!0):i=o.resolve(n,t)}let d=e&&this._hasAnyCliFlag()?{packageManager:e.packageManager??"bun",runtime:e.runtime??"bun",lmdb:e.lmdb??!0,workerThread:e.workerThread??!0,eslint:e.eslint??!0}:await this.promptForConfiguration(),{packageManager:m,runtime:$,lmdb:b,workerThread:u,eslint:y}=d;if(a.info(`Project: ${t}`),a.info(`Location: ${i}`),a.info(`Package Manager: ${m}`),a.info(`Runtime: ${$}`),a.info(`LMDB Storage: ${b?"Yes":"No"}`),a.info(`Worker Thread: ${u?"Yes":"No"}`),a.info(`ESLint: ${y?"Yes":"No"}`),c){if(!p.isDirectoryEffectivelyEmpty(i))throw new Error("Current directory is not empty")}else if(r.existsSync(i))throw new Error(`Directory "${t}" already exists`);return{projectName:t,projectPath:i,packageManager:m,runtime:$,setupEslint:y,useLmdb:b,useWorkerThread:u}}_hasAnyCliFlag(){let e=this.cliOptions;return!!(e&&(e.packageManager!==void 0||e.runtime!==void 0||e.lmdb!==void 0||e.workerThread!==void 0||e.eslint!==void 0))}async executeProjectGeneration(e){let{projectName:n,projectPath:t,packageManager:i,runtime:c,setupEslint:h,useLmdb:d,useWorkerThread:m}=e,$=this.getPackageManagerCommands(i),b={projectName:n,installCmd:$.install,runDevCmd:$.dev,runStartCmd:$.start};a.info("Creating project directory.."),r.existsSync(t)||r.mkdirSync(t,{recursive:!0}),a.info("Copying template files.."),this.copyDirectorySync(o.join(H,"templates"),t,b),a.info("Creating logs directory.."),r.mkdirSync(o.join(t,"logs"),{recursive:!0}),r.writeFileSync(o.join(t,"logs/.gitkeep"),"");let u=o.join(t,".env"),y=o.join(t,".env.example");if(r.existsSync(y)&&(a.info("Creating .env file.."),r.renameSync(y,u)),c!=="bun"){let g=o.join(t,"bunfig.toml");r.existsSync(g)&&r.unlinkSync(g)}let x=o.join(t,"src","db");if(d){a.info("Keeping @gramstax/lmdb files..");let g=r.readFileSync(u,"utf-8");r.writeFileSync(u,g+`DB_PATH="./data"
14
- `),r.mkdirSync(o.join(t,"data"),{recursive:!0}),r.writeFileSync(o.join(t,"data/.gitkeep"),"")}else a.info("Removing @gramstax/lmdb files.."),r.existsSync(x)&&r.rmSync(x,{recursive:!0,force:!0});let E=o.join(t,"src","workers");if(m?a.info("Keeping @gramstax/worker-thread files.."):(a.info("Removing @gramstax/worker-thread files.."),r.existsSync(E)&&r.rmSync(E,{recursive:!0,force:!0})),a.info("Installing dependencies.."),!await this.installDependencies(t,i))throw new Error("Dependencies installation failed");let k=o.join(t,"eslint.config.ts.template"),U=o.join(t,"eslint.config.ts");if(h){a.info("Installing ESLint dependencies..");let g=p.resolvePackageManagerCommand(i),D=i==="yarn"?["add","-D"]:["install","-D"];D.push("eslint","@eslint/js","globals","typescript-eslint"),await new Promise(T=>{let M=(0,S.spawn)(g,D,{cwd:t,stdio:"inherit",shell:!1});M.on("close",()=>T()),M.on("error",A=>{a.fail(`Failed to install ESLint: ${A.message}`),T()})}),r.existsSync(k)&&r.renameSync(k,U)}else r.existsSync(k)&&r.unlinkSync(k);a.info("Project created successfully!"),a.info(`Next steps: ${e.projectPath!==process.cwd()?`cd ${n} && `:""}${$.install} && ${$.dev}`),a.info("Done..")}async createProject(){try{this.config=await this.gatherProjectConfig(),await this.executeProjectGeneration(this.config)}catch(e){a.error(`Failed to create project: ${e instanceof Error?e.message:String(e)}`),process.exit(1)}}};var I=require("commander"),w=require("fs"),_=require("path"),O=require("url");function Q(s){let e=s;for(let n=0;n<10;n++){let t=(0,_.join)(e,"package.json");if((0,w.existsSync)(t))return t;let i=(0,_.dirname)(e);if(i===e)break;e=i}throw new Error("package.json not found")}l(Q,"findPackageJson");var X=(0,_.dirname)((0,O.fileURLToPath)(f)),N=JSON.parse((0,w.readFileSync)(Q(X),"utf-8")).version,W=new I.Command;W.name("create-gramstax").description("CLI to create a new Gramstax Telegram bot project").version(N).argument("[project-directory]","Directory to create the project in").option("--pm <name>","Package manager (bun, npm, pnpm, yarn)").option("--runtime <name>","Runtime (bun, node)").option("--lmdb","Include @gramstax/lmdb (default: true)").option("--no-lmdb","Skip @gramstax/lmdb").option("--worker-thread","Include @gramstax/worker-thread (default: true)").option("--no-worker-thread","Skip @gramstax/worker-thread").option("--eslint","Include ESLint (default: true)").option("--no-eslint","Skip ESLint").action(async(s,e)=>{try{let n={};e?.pm&&(n.packageManager=e.pm),e?.runtime&&(n.runtime=e.runtime),e?.lmdb!==void 0&&(n.lmdb=e.lmdb),e?.workerThread!==void 0&&(n.workerThread=e.workerThread),e?.eslint!==void 0&&(n.eslint=e.eslint),await new v(s,{version:N,cliOptions:n}).createProject()}catch(n){a.error(`Failed to create project: ${String(n)}`),process.exit(1)}});W.parse();
13
+ `)}getPackageManagerCommands(e){return{bun:{install:"bun install",dev:"bun dev",start:"bun start"},npm:{install:"npm install",dev:"npm run dev",start:"npm start"},pnpm:{install:"pnpm install",dev:"pnpm dev",start:"pnpm start"},yarn:{install:"yarn install",dev:"yarn dev",start:"yarn start"}}[e]}copyDirectorySync(e,n,t={}){r.existsSync(n)||r.mkdirSync(n,{recursive:!0});let i=r.readdirSync(e,{withFileTypes:!0});for(let c of i){let h=o.join(e,c.name),d=o.join(n,c.name);if(c.isDirectory())this.copyDirectorySync(h,d,t);else if(c.isFile()){let m=r.readFileSync(h,"utf-8");m=this.replaceTemplateVariables(m,t),r.writeFileSync(d,m)}}}replaceTemplateVariables(e,n){let t=e;for(let[i,c]of Object.entries(n))t=t.replace(new RegExp(`{{${i}}}`,"g"),c);return t}async promptForProjectName(){return(await C.default.prompt({type:"input",name:"projectName",message:"What is your project named?",initial:"",validate:l(n=>n===""||/^[a-z0-9-_]+$/i.test(n)?!0:"Project name can only contain letters, numbers, hyphens, and underscores","validate")})).projectName}async promptForConfiguration(){let e=await C.default.prompt([{type:"select",name:"packageManager",message:"Which package manager would you like to use?",choices:[{name:"bun",message:"Bun (recommended)",value:"bun"},{name:"npm",message:"npm",value:"npm"},{name:"pnpm",message:"pnpm",value:"pnpm"},{name:"yarn",message:"Yarn",value:"yarn"}],initial:0},{type:"select",name:"runtime",message:"Which runtime would you like to use?",choices:[{name:"bun",message:"Bun (recommended)",value:"bun"},{name:"node",message:"Node.js",value:"node"}],initial:0},{type:"confirm",name:"lmdb",message:"Would you like to include @gramstax/lmdb for persistent storage? (recommended)",initial:!0},{type:"confirm",name:"workerThread",message:"Would you like to include @gramstax/worker-thread for background jobs? (recommended)",initial:!0},{type:"confirm",name:"eslint",message:"Would you like to use ESLint?",initial:!0}]);return{packageManager:e.packageManager,runtime:e.runtime,lmdb:e.lmdb,workerThread:e.workerThread,eslint:e.eslint}}async installDependencies(e,n){return await new Promise(t=>{let i=(0,S.spawn)(p.resolvePackageManagerCommand(n),["install"],{cwd:e,stdio:"inherit",shell:!1});i.on("close",c=>c===0?t(!0):(a.fail("Dependencies installation failed"),t(!1))),i.on("error",c=>(a.fail(`Dependencies installation failed: ${c.message}`),t(!1)))})}async gatherProjectConfig(){this.printBanner();let e=this.cliOptions,n=p.resolveInitialWorkingDirectory(),t=this.projectDirectory??"",i,c=!1;if(t)i=o.resolve(n,t);else{if(e&&this._hasAnyCliFlag())throw a.error("Project name is required when using CLI flags"),new Error("Missing project name");t=await this.promptForProjectName(),t===""?(t=o.basename(n),i=n,c=!0):i=o.resolve(n,t)}let d=e&&this._hasAnyCliFlag()?{packageManager:e.packageManager??"bun",runtime:e.runtime??"bun",lmdb:e.lmdb??!0,workerThread:e.workerThread??!0,eslint:e.eslint??!0}:await this.promptForConfiguration(),{packageManager:m,runtime:$,lmdb:b,workerThread:u,eslint:y}=d;if(a.info(`Project: ${t}`),a.info(`Location: ${i}`),a.info(`Package Manager: ${m}`),a.info(`Runtime: ${$}`),a.info(`LMDB Storage: ${b?"Yes":"No"}`),a.info(`Worker Thread: ${u?"Yes":"No"}`),a.info(`ESLint: ${y?"Yes":"No"}`),c){if(!p.isDirectoryEffectivelyEmpty(i))throw new Error("Current directory is not empty")}else if(r.existsSync(i))throw new Error(`Directory "${t}" already exists`);return{projectName:t,projectPath:i,packageManager:m,runtime:$,setupEslint:y,useLmdb:b,useWorkerThread:u}}_hasAnyCliFlag(){let e=this.cliOptions;return!!(e&&(e.packageManager!==void 0||e.runtime!==void 0||e.lmdb!==void 0||e.workerThread!==void 0||e.eslint!==void 0))}async executeProjectGeneration(e){let{projectName:n,projectPath:t,packageManager:i,runtime:c,setupEslint:h,useLmdb:d,useWorkerThread:m}=e,$=this.getPackageManagerCommands(i),b={projectName:n,installCmd:$.install,runDevCmd:$.dev,runStartCmd:$.start};a.info("Creating project directory.."),r.existsSync(t)||r.mkdirSync(t,{recursive:!0}),a.info("Copying template files.."),this.copyDirectorySync(o.join(H,"templates"),t,b),a.info("Creating logs directory.."),r.mkdirSync(o.join(t,"logs"),{recursive:!0}),r.writeFileSync(o.join(t,"logs/.gitkeep"),"");let u=o.join(t,".env"),y=o.join(t,".env.example");if(r.existsSync(y)&&(a.info("Creating .env file.."),r.renameSync(y,u)),c!=="bun"){let g=o.join(t,"bunfig.toml");r.existsSync(g)&&r.unlinkSync(g)}let x=o.join(t,"src","db");if(d){a.info("Keeping @gramstax/lmdb files..");let g=r.readFileSync(u,"utf-8");r.writeFileSync(u,g+`
14
+
15
+ DB_PATH="./data"
16
+ `)}else a.info("Removing @gramstax/lmdb files.."),r.existsSync(x)&&r.rmSync(x,{recursive:!0,force:!0});let E=o.join(t,"src","workers");if(m?a.info("Keeping @gramstax/worker-thread files.."):(a.info("Removing @gramstax/worker-thread files.."),r.existsSync(E)&&r.rmSync(E,{recursive:!0,force:!0})),a.info("Installing dependencies.."),!await this.installDependencies(t,i))throw new Error("Dependencies installation failed");let k=o.join(t,"eslint.config.ts.template"),U=o.join(t,"eslint.config.ts");if(h){a.info("Installing ESLint dependencies..");let g=p.resolvePackageManagerCommand(i),D=i==="yarn"?["add","-D"]:["install","-D"];D.push("eslint","@eslint/js","globals","typescript-eslint"),await new Promise(T=>{let M=(0,S.spawn)(g,D,{cwd:t,stdio:"inherit",shell:!1});M.on("close",()=>T()),M.on("error",A=>{a.fail(`Failed to install ESLint: ${A.message}`),T()})}),r.existsSync(k)&&r.renameSync(k,U)}else r.existsSync(k)&&r.unlinkSync(k);a.info("Project created successfully!"),a.info(`Next steps: ${e.projectPath!==process.cwd()?`cd ${n} && `:""}${$.install} && ${$.dev}`),a.info("Done..")}async createProject(){try{this.config=await this.gatherProjectConfig(),await this.executeProjectGeneration(this.config)}catch(e){a.error(`Failed to create project: ${e instanceof Error?e.message:String(e)}`),process.exit(1)}}};var I=require("commander"),w=require("fs"),_=require("path"),O=require("url");function Q(s){let e=s;for(let n=0;n<10;n++){let t=(0,_.join)(e,"package.json");if((0,w.existsSync)(t))return t;let i=(0,_.dirname)(e);if(i===e)break;e=i}throw new Error("package.json not found")}l(Q,"findPackageJson");var X=(0,_.dirname)((0,O.fileURLToPath)(f)),N=JSON.parse((0,w.readFileSync)(Q(X),"utf-8")).version,W=new I.Command;W.name("create-gramstax").description("CLI to create a new Gramstax Telegram bot project").version(N).argument("[project-directory]","Directory to create the project in").option("--pm <name>","Package manager (bun, npm, pnpm, yarn)").option("--runtime <name>","Runtime (bun, node)").option("--lmdb","Include @gramstax/lmdb (default: true)").option("--no-lmdb","Skip @gramstax/lmdb").option("--worker-thread","Include @gramstax/worker-thread (default: true)").option("--no-worker-thread","Skip @gramstax/worker-thread").option("--eslint","Include ESLint (default: true)").option("--no-eslint","Skip ESLint").action(async(s,e)=>{try{let n={};e?.pm&&(n.packageManager=e.pm),e?.runtime&&(n.runtime=e.runtime),e?.lmdb!==void 0&&(n.lmdb=e.lmdb),e?.workerThread!==void 0&&(n.workerThread=e.workerThread),e?.eslint!==void 0&&(n.eslint=e.eslint),await new v(s,{version:N,cliOptions:n}).createProject()}catch(n){a.error(`Failed to create project: ${String(n)}`),process.exit(1)}});W.parse();
package/dist/src/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- var R=Object.defineProperty;var $=(c,e)=>R(c,"name",{value:e,configurable:!0});import{LoggingPretty as N}from"logging-pretty";var i=new N;import*as r from"fs";import*as o from"path";import{fileURLToPath as I}from"url";import{readdirSync as O}from"fs";var p=class c{static{$(this,"FsUt")}static DEFAULT_IGNORED_ENTRIES=new Set([".git",".gitignore",".gitkeep",".DS_Store","Thumbs.db"]);static isDirectoryEffectivelyEmpty(e,n=c.DEFAULT_IGNORED_ENTRIES){return O(e).filter(s=>!n.has(s)).length===0}static resolvePackageManagerCommand(e,n=process.platform){return n!=="win32"||e==="bun"?e:`${e}.cmd`}static resolveInitialWorkingDirectory(e=process.cwd(),n=process.env.INIT_CWD){return n??e}};import{spawn as C}from"child_process";import x from"enquirer";var W=I(import.meta.url),A=o.dirname(W),k=class{constructor(e,n){this.projectDirectory=e;this.version=n?.version,this.cliOptions=n?.cliOptions}projectDirectory;static{$(this,"Create")}config;version;cliOptions;printBanner(){let e=`
2
+ var R=Object.defineProperty;var $=(c,e)=>R(c,"name",{value:e,configurable:!0});import{LoggingPretty as N}from"logging-pretty";var i=new N;import*as t from"fs";import*as o from"path";import{fileURLToPath as I}from"url";import{readdirSync as O}from"fs";var p=class c{static{$(this,"FsUt")}static DEFAULT_IGNORED_ENTRIES=new Set([".git",".gitignore",".gitkeep",".DS_Store","Thumbs.db"]);static isDirectoryEffectivelyEmpty(e,n=c.DEFAULT_IGNORED_ENTRIES){return O(e).filter(s=>!n.has(s)).length===0}static resolvePackageManagerCommand(e,n=process.platform){return n!=="win32"||e==="bun"?e:`${e}.cmd`}static resolveInitialWorkingDirectory(e=process.cwd(),n=process.env.INIT_CWD){return n??e}};import{spawn as C}from"child_process";import x from"enquirer";var W=I(import.meta.url),A=o.dirname(W),b=class{constructor(e,n){this.projectDirectory=e;this.version=n?.version,this.cliOptions=n?.cliOptions}projectDirectory;static{$(this,"Create")}config;version;cliOptions;printBanner(){let e=`
3
3
  $$$$$$\\ $$\\
4
4
  $$ __$$\\ $$ |
5
5
  $$ / \\__| $$$$$$\\ $$$$$$\\ $$$$$$\\$$$$\\ $$$$$$$\\ $$$$$$\\ $$$$$$\\ $$\\ $$\\
@@ -10,5 +10,7 @@ $$ | $$ |$$ | $$ __$$ |$$ | $$ | $$ | \\____$$\\ $$ |$$\\ $$ __$$ | $$
10
10
  \\______/ \\__| \\_______|\\__| \\__| \\__|\\_______/ \\____/ \\_______|\\__/ \\__|
11
11
 
12
12
  `,n=this.version?`v${this.version}`:"v0.0.0";console.log(`${e}${n}
13
- `)}getPackageManagerCommands(e){return{bun:{install:"bun install",dev:"bun dev",start:"bun start"},npm:{install:"npm install",dev:"npm run dev",start:"npm start"},pnpm:{install:"pnpm install",dev:"pnpm dev",start:"pnpm start"},yarn:{install:"yarn install",dev:"yarn dev",start:"yarn start"}}[e]}copyDirectorySync(e,n,t={}){r.existsSync(n)||r.mkdirSync(n,{recursive:!0});let a=r.readdirSync(e,{withFileTypes:!0});for(let s of a){let u=o.join(e,s.name),d=o.join(n,s.name);if(s.isDirectory())this.copyDirectorySync(u,d,t);else if(s.isFile()){let l=r.readFileSync(u,"utf-8");l=this.replaceTemplateVariables(l,t),r.writeFileSync(d,l)}}}replaceTemplateVariables(e,n){let t=e;for(let[a,s]of Object.entries(n))t=t.replace(new RegExp(`{{${a}}}`,"g"),s);return t}async promptForProjectName(){return(await x.prompt({type:"input",name:"projectName",message:"What is your project named?",initial:"",validate:$(n=>n===""||/^[a-z0-9-_]+$/i.test(n)?!0:"Project name can only contain letters, numbers, hyphens, and underscores","validate")})).projectName}async promptForConfiguration(){let e=await x.prompt([{type:"select",name:"packageManager",message:"Which package manager would you like to use?",choices:[{name:"bun",message:"Bun (recommended)",value:"bun"},{name:"npm",message:"npm",value:"npm"},{name:"pnpm",message:"pnpm",value:"pnpm"},{name:"yarn",message:"Yarn",value:"yarn"}],initial:0},{type:"select",name:"runtime",message:"Which runtime would you like to use?",choices:[{name:"bun",message:"Bun (recommended)",value:"bun"},{name:"node",message:"Node.js",value:"node"}],initial:0},{type:"confirm",name:"lmdb",message:"Would you like to include @gramstax/lmdb for persistent storage? (recommended)",initial:!0},{type:"confirm",name:"workerThread",message:"Would you like to include @gramstax/worker-thread for background jobs? (recommended)",initial:!0},{type:"confirm",name:"eslint",message:"Would you like to use ESLint?",initial:!0}]);return{packageManager:e.packageManager,runtime:e.runtime,lmdb:e.lmdb,workerThread:e.workerThread,eslint:e.eslint}}async installDependencies(e,n){return await new Promise(t=>{let a=C(p.resolvePackageManagerCommand(n),["install"],{cwd:e,stdio:"inherit",shell:!1});a.on("close",s=>s===0?t(!0):(i.fail("Dependencies installation failed"),t(!1))),a.on("error",s=>(i.fail(`Dependencies installation failed: ${s.message}`),t(!1)))})}async gatherProjectConfig(){this.printBanner();let e=this.cliOptions,n=p.resolveInitialWorkingDirectory(),t=this.projectDirectory??"",a,s=!1;if(t)a=o.resolve(n,t);else{if(e&&this._hasAnyCliFlag())throw i.error("Project name is required when using CLI flags"),new Error("Missing project name");t=await this.promptForProjectName(),t===""?(t=o.basename(n),a=n,s=!0):a=o.resolve(n,t)}let d=e&&this._hasAnyCliFlag()?{packageManager:e.packageManager??"bun",runtime:e.runtime??"bun",lmdb:e.lmdb??!0,workerThread:e.workerThread??!0,eslint:e.eslint??!0}:await this.promptForConfiguration(),{packageManager:l,runtime:m,lmdb:y,workerThread:g,eslint:h}=d;if(i.info(`Project: ${t}`),i.info(`Location: ${a}`),i.info(`Package Manager: ${l}`),i.info(`Runtime: ${m}`),i.info(`LMDB Storage: ${y?"Yes":"No"}`),i.info(`Worker Thread: ${g?"Yes":"No"}`),i.info(`ESLint: ${h?"Yes":"No"}`),s){if(!p.isDirectoryEffectivelyEmpty(a))throw new Error("Current directory is not empty")}else if(r.existsSync(a))throw new Error(`Directory "${t}" already exists`);return{projectName:t,projectPath:a,packageManager:l,runtime:m,setupEslint:h,useLmdb:y,useWorkerThread:g}}_hasAnyCliFlag(){let e=this.cliOptions;return!!(e&&(e.packageManager!==void 0||e.runtime!==void 0||e.lmdb!==void 0||e.workerThread!==void 0||e.eslint!==void 0))}async executeProjectGeneration(e){let{projectName:n,projectPath:t,packageManager:a,runtime:s,setupEslint:u,useLmdb:d,useWorkerThread:l}=e,m=this.getPackageManagerCommands(a),y={projectName:n,installCmd:m.install,runDevCmd:m.dev,runStartCmd:m.start};i.info("Creating project directory.."),r.existsSync(t)||r.mkdirSync(t,{recursive:!0}),i.info("Copying template files.."),this.copyDirectorySync(o.join(A,"templates"),t,y),i.info("Creating logs directory.."),r.mkdirSync(o.join(t,"logs"),{recursive:!0}),r.writeFileSync(o.join(t,"logs/.gitkeep"),"");let g=o.join(t,".env"),h=o.join(t,".env.example");if(r.existsSync(h)&&(i.info("Creating .env file.."),r.renameSync(h,g)),s!=="bun"){let f=o.join(t,"bunfig.toml");r.existsSync(f)&&r.unlinkSync(f)}let b=o.join(t,"src","db");if(d){i.info("Keeping @gramstax/lmdb files..");let f=r.readFileSync(g,"utf-8");r.writeFileSync(g,f+`DB_PATH="./data"
14
- `),r.mkdirSync(o.join(t,"data"),{recursive:!0}),r.writeFileSync(o.join(t,"data/.gitkeep"),"")}else i.info("Removing @gramstax/lmdb files.."),r.existsSync(b)&&r.rmSync(b,{recursive:!0,force:!0});let v=o.join(t,"src","workers");if(l?i.info("Keeping @gramstax/worker-thread files.."):(i.info("Removing @gramstax/worker-thread files.."),r.existsSync(v)&&r.rmSync(v,{recursive:!0,force:!0})),i.info("Installing dependencies.."),!await this.installDependencies(t,a))throw new Error("Dependencies installation failed");let _=o.join(t,"eslint.config.ts.template"),M=o.join(t,"eslint.config.ts");if(u){i.info("Installing ESLint dependencies..");let f=p.resolvePackageManagerCommand(a),w=a==="yarn"?["add","-D"]:["install","-D"];w.push("eslint","@eslint/js","globals","typescript-eslint"),await new Promise(j=>{let P=C(f,w,{cwd:t,stdio:"inherit",shell:!1});P.on("close",()=>j()),P.on("error",F=>{i.fail(`Failed to install ESLint: ${F.message}`),j()})}),r.existsSync(_)&&r.renameSync(_,M)}else r.existsSync(_)&&r.unlinkSync(_);i.info("Project created successfully!"),i.info(`Next steps: ${e.projectPath!==process.cwd()?`cd ${n} && `:""}${m.install} && ${m.dev}`),i.info("Done..")}async createProject(){try{this.config=await this.gatherProjectConfig(),await this.executeProjectGeneration(this.config)}catch(e){i.error(`Failed to create project: ${e instanceof Error?e.message:String(e)}`),process.exit(1)}}};import{Command as B}from"commander";import{readFileSync as G,existsSync as U}from"fs";import{join as Y,dirname as D}from"path";import{fileURLToPath as V}from"url";function q(c){let e=c;for(let n=0;n<10;n++){let t=Y(e,"package.json");if(U(t))return t;let a=D(e);if(a===e)break;e=a}throw new Error("package.json not found")}$(q,"findPackageJson");var J=D(V(import.meta.url)),E=JSON.parse(G(q(J),"utf-8")).version,T=new B;T.name("create-gramstax").description("CLI to create a new Gramstax Telegram bot project").version(E).argument("[project-directory]","Directory to create the project in").option("--pm <name>","Package manager (bun, npm, pnpm, yarn)").option("--runtime <name>","Runtime (bun, node)").option("--lmdb","Include @gramstax/lmdb (default: true)").option("--no-lmdb","Skip @gramstax/lmdb").option("--worker-thread","Include @gramstax/worker-thread (default: true)").option("--no-worker-thread","Skip @gramstax/worker-thread").option("--eslint","Include ESLint (default: true)").option("--no-eslint","Skip ESLint").action(async(c,e)=>{try{let n={};e?.pm&&(n.packageManager=e.pm),e?.runtime&&(n.runtime=e.runtime),e?.lmdb!==void 0&&(n.lmdb=e.lmdb),e?.workerThread!==void 0&&(n.workerThread=e.workerThread),e?.eslint!==void 0&&(n.eslint=e.eslint),await new k(c,{version:E,cliOptions:n}).createProject()}catch(n){i.error(`Failed to create project: ${String(n)}`),process.exit(1)}});T.parse();
13
+ `)}getPackageManagerCommands(e){return{bun:{install:"bun install",dev:"bun dev",start:"bun start"},npm:{install:"npm install",dev:"npm run dev",start:"npm start"},pnpm:{install:"pnpm install",dev:"pnpm dev",start:"pnpm start"},yarn:{install:"yarn install",dev:"yarn dev",start:"yarn start"}}[e]}copyDirectorySync(e,n,r={}){t.existsSync(n)||t.mkdirSync(n,{recursive:!0});let a=t.readdirSync(e,{withFileTypes:!0});for(let s of a){let u=o.join(e,s.name),d=o.join(n,s.name);if(s.isDirectory())this.copyDirectorySync(u,d,r);else if(s.isFile()){let l=t.readFileSync(u,"utf-8");l=this.replaceTemplateVariables(l,r),t.writeFileSync(d,l)}}}replaceTemplateVariables(e,n){let r=e;for(let[a,s]of Object.entries(n))r=r.replace(new RegExp(`{{${a}}}`,"g"),s);return r}async promptForProjectName(){return(await x.prompt({type:"input",name:"projectName",message:"What is your project named?",initial:"",validate:$(n=>n===""||/^[a-z0-9-_]+$/i.test(n)?!0:"Project name can only contain letters, numbers, hyphens, and underscores","validate")})).projectName}async promptForConfiguration(){let e=await x.prompt([{type:"select",name:"packageManager",message:"Which package manager would you like to use?",choices:[{name:"bun",message:"Bun (recommended)",value:"bun"},{name:"npm",message:"npm",value:"npm"},{name:"pnpm",message:"pnpm",value:"pnpm"},{name:"yarn",message:"Yarn",value:"yarn"}],initial:0},{type:"select",name:"runtime",message:"Which runtime would you like to use?",choices:[{name:"bun",message:"Bun (recommended)",value:"bun"},{name:"node",message:"Node.js",value:"node"}],initial:0},{type:"confirm",name:"lmdb",message:"Would you like to include @gramstax/lmdb for persistent storage? (recommended)",initial:!0},{type:"confirm",name:"workerThread",message:"Would you like to include @gramstax/worker-thread for background jobs? (recommended)",initial:!0},{type:"confirm",name:"eslint",message:"Would you like to use ESLint?",initial:!0}]);return{packageManager:e.packageManager,runtime:e.runtime,lmdb:e.lmdb,workerThread:e.workerThread,eslint:e.eslint}}async installDependencies(e,n){return await new Promise(r=>{let a=C(p.resolvePackageManagerCommand(n),["install"],{cwd:e,stdio:"inherit",shell:!1});a.on("close",s=>s===0?r(!0):(i.fail("Dependencies installation failed"),r(!1))),a.on("error",s=>(i.fail(`Dependencies installation failed: ${s.message}`),r(!1)))})}async gatherProjectConfig(){this.printBanner();let e=this.cliOptions,n=p.resolveInitialWorkingDirectory(),r=this.projectDirectory??"",a,s=!1;if(r)a=o.resolve(n,r);else{if(e&&this._hasAnyCliFlag())throw i.error("Project name is required when using CLI flags"),new Error("Missing project name");r=await this.promptForProjectName(),r===""?(r=o.basename(n),a=n,s=!0):a=o.resolve(n,r)}let d=e&&this._hasAnyCliFlag()?{packageManager:e.packageManager??"bun",runtime:e.runtime??"bun",lmdb:e.lmdb??!0,workerThread:e.workerThread??!0,eslint:e.eslint??!0}:await this.promptForConfiguration(),{packageManager:l,runtime:m,lmdb:y,workerThread:g,eslint:h}=d;if(i.info(`Project: ${r}`),i.info(`Location: ${a}`),i.info(`Package Manager: ${l}`),i.info(`Runtime: ${m}`),i.info(`LMDB Storage: ${y?"Yes":"No"}`),i.info(`Worker Thread: ${g?"Yes":"No"}`),i.info(`ESLint: ${h?"Yes":"No"}`),s){if(!p.isDirectoryEffectivelyEmpty(a))throw new Error("Current directory is not empty")}else if(t.existsSync(a))throw new Error(`Directory "${r}" already exists`);return{projectName:r,projectPath:a,packageManager:l,runtime:m,setupEslint:h,useLmdb:y,useWorkerThread:g}}_hasAnyCliFlag(){let e=this.cliOptions;return!!(e&&(e.packageManager!==void 0||e.runtime!==void 0||e.lmdb!==void 0||e.workerThread!==void 0||e.eslint!==void 0))}async executeProjectGeneration(e){let{projectName:n,projectPath:r,packageManager:a,runtime:s,setupEslint:u,useLmdb:d,useWorkerThread:l}=e,m=this.getPackageManagerCommands(a),y={projectName:n,installCmd:m.install,runDevCmd:m.dev,runStartCmd:m.start};i.info("Creating project directory.."),t.existsSync(r)||t.mkdirSync(r,{recursive:!0}),i.info("Copying template files.."),this.copyDirectorySync(o.join(A,"templates"),r,y),i.info("Creating logs directory.."),t.mkdirSync(o.join(r,"logs"),{recursive:!0}),t.writeFileSync(o.join(r,"logs/.gitkeep"),"");let g=o.join(r,".env"),h=o.join(r,".env.example");if(t.existsSync(h)&&(i.info("Creating .env file.."),t.renameSync(h,g)),s!=="bun"){let f=o.join(r,"bunfig.toml");t.existsSync(f)&&t.unlinkSync(f)}let k=o.join(r,"src","db");if(d){i.info("Keeping @gramstax/lmdb files..");let f=t.readFileSync(g,"utf-8");t.writeFileSync(g,f+`
14
+
15
+ DB_PATH="./data"
16
+ `)}else i.info("Removing @gramstax/lmdb files.."),t.existsSync(k)&&t.rmSync(k,{recursive:!0,force:!0});let v=o.join(r,"src","workers");if(l?i.info("Keeping @gramstax/worker-thread files.."):(i.info("Removing @gramstax/worker-thread files.."),t.existsSync(v)&&t.rmSync(v,{recursive:!0,force:!0})),i.info("Installing dependencies.."),!await this.installDependencies(r,a))throw new Error("Dependencies installation failed");let _=o.join(r,"eslint.config.ts.template"),M=o.join(r,"eslint.config.ts");if(u){i.info("Installing ESLint dependencies..");let f=p.resolvePackageManagerCommand(a),w=a==="yarn"?["add","-D"]:["install","-D"];w.push("eslint","@eslint/js","globals","typescript-eslint"),await new Promise(j=>{let P=C(f,w,{cwd:r,stdio:"inherit",shell:!1});P.on("close",()=>j()),P.on("error",F=>{i.fail(`Failed to install ESLint: ${F.message}`),j()})}),t.existsSync(_)&&t.renameSync(_,M)}else t.existsSync(_)&&t.unlinkSync(_);i.info("Project created successfully!"),i.info(`Next steps: ${e.projectPath!==process.cwd()?`cd ${n} && `:""}${m.install} && ${m.dev}`),i.info("Done..")}async createProject(){try{this.config=await this.gatherProjectConfig(),await this.executeProjectGeneration(this.config)}catch(e){i.error(`Failed to create project: ${e instanceof Error?e.message:String(e)}`),process.exit(1)}}};import{Command as B}from"commander";import{readFileSync as G,existsSync as U}from"fs";import{join as Y,dirname as D}from"path";import{fileURLToPath as V}from"url";function q(c){let e=c;for(let n=0;n<10;n++){let r=Y(e,"package.json");if(U(r))return r;let a=D(e);if(a===e)break;e=a}throw new Error("package.json not found")}$(q,"findPackageJson");var J=D(V(import.meta.url)),E=JSON.parse(G(q(J),"utf-8")).version,T=new B;T.name("create-gramstax").description("CLI to create a new Gramstax Telegram bot project").version(E).argument("[project-directory]","Directory to create the project in").option("--pm <name>","Package manager (bun, npm, pnpm, yarn)").option("--runtime <name>","Runtime (bun, node)").option("--lmdb","Include @gramstax/lmdb (default: true)").option("--no-lmdb","Skip @gramstax/lmdb").option("--worker-thread","Include @gramstax/worker-thread (default: true)").option("--no-worker-thread","Skip @gramstax/worker-thread").option("--eslint","Include ESLint (default: true)").option("--no-eslint","Skip ESLint").action(async(c,e)=>{try{let n={};e?.pm&&(n.packageManager=e.pm),e?.runtime&&(n.runtime=e.runtime),e?.lmdb!==void 0&&(n.lmdb=e.lmdb),e?.workerThread!==void 0&&(n.workerThread=e.workerThread),e?.eslint!==void 0&&(n.eslint=e.eslint),await new b(c,{version:E,cliOptions:n}).createProject()}catch(n){i.error(`Failed to create project: ${String(n)}`),process.exit(1)}});T.parse();
@@ -22,7 +22,7 @@ describe(`E2E: GeneralStatusPage`, () => {
22
22
  const replyId = responsesId[0]!
23
23
  expect(reply).toBeDefined()
24
24
  expect(replyId).toBeDefined()
25
- expect(reply.body?.text).toBe(e2e.gmsg(intent, `d`, {isNotFound: true}))
26
- expect(replyId.body?.text).toBe(e2e.gmsg(intent, `d`, {isNotFound: true}, `id`))
25
+ expect(reply.body?.text).toBe(e2e.gmsg(intent, `default`, {isNotFound: true}))
26
+ expect(replyId.body?.text).toBe(e2e.gmsg(intent, `default`, {isNotFound: true}, `id`))
27
27
  })
28
28
  })
@@ -20,9 +20,9 @@ describe(`E2E: HelpPage`, () => {
20
20
  expect(responsesId.length).toBeGreaterThan(0)
21
21
  const edit = responses[0]!
22
22
  const editId = responsesId[0]!
23
- expect(edit.body?.text).toBe(e2e.gmsg(intent, `s1`, {}))
24
- expect(editId.body?.text).toBe(e2e.gmsg(intent, `s1`, {}, `id`))
25
- expect(e2e.extractButtonLabels(edit)).toEqual(e2e.gkb(intent, `s1`, `inline`, {}))
26
- expect(e2e.extractButtonLabels(editId)).toEqual(e2e.gkb(intent, `s1`, `inline`, {}, `id`))
23
+ expect(edit.body?.rich_message.html).toBe(e2e.gmsg(intent, `default`, {}))
24
+ expect(editId.body?.rich_message.html).toBe(e2e.gmsg(intent, `default`, {}, `id`))
25
+ expect(e2e.extractButtonLabels(edit)).toEqual(e2e.gkb(intent, `default`, `inline`, {}))
26
+ expect(e2e.extractButtonLabels(editId)).toEqual(e2e.gkb(intent, `default`, `inline`, {}, `id`))
27
27
  })
28
28
  })
@@ -22,7 +22,7 @@ describe(`E2E: RandomNumberPage`, () => {
22
22
  const editId = responsesId[0]!
23
23
  expect(edit.body?.text).toContain(`Random Number`)
24
24
  expect(editId.body?.text).toContain(`Angka Acak`)
25
- expect(e2e.extractButtonLabels(edit)).toEqual(e2e.gkb(intent, `s1`, `inline`, {}))
26
- expect(e2e.extractButtonLabels(editId)).toEqual(e2e.gkb(intent, `s1`, `inline`, {}, `id`))
25
+ expect(e2e.extractButtonLabels(edit)).toEqual(e2e.gkb(intent, `default`, `inline`, {}))
26
+ expect(e2e.extractButtonLabels(editId)).toEqual(e2e.gkb(intent, `default`, `inline`, {}, `id`))
27
27
  })
28
28
  })
@@ -17,17 +17,17 @@ describe(`E2E: StartPage`, () => {
17
17
  expect(responsesId.length).toBeGreaterThan(0)
18
18
  const reply = responses[0]!
19
19
  const replyId = responsesId[0]!
20
- expect(reply.body?.text).toContain(`Welcome`)
21
- expect(replyId.body?.text).toContain(`Selamat datang`)
22
- expect(e2e.extractButtonLabels(reply)).toEqual(e2e.gkb(intent, `s1`, `inline`, {}))
23
- expect(e2e.extractButtonLabels(replyId)).toEqual(e2e.gkb(intent, `s1`, `inline`, {}, `id`))
20
+ expect(reply.body?.rich_message.html).toContain(`Welcome`)
21
+ expect(replyId.body?.rich_message.html).toContain(`Selamat datang`)
22
+ expect(e2e.extractButtonLabels(reply)).toEqual(e2e.gkb(intent, `default`, `inline`, {}))
23
+ expect(e2e.extractButtonLabels(replyId)).toEqual(e2e.gkb(intent, `default`, `inline`, {}, `id`))
24
24
  })
25
25
 
26
26
  it(`capture outbound requests in e2e.calls`, async () => {
27
27
  await e2e.sendText(`/start`, asUser)
28
28
 
29
- const sendMessageReq = e2e.lastCall(`sendMessage`)
30
- expect(sendMessageReq).toBeDefined()
31
- expect(sendMessageReq?.body?.text).toContain(`Welcome`)
29
+ const sendRichMsgReq = e2e.lastCall(`sendRichMessage`)
30
+ expect(sendRichMsgReq).toBeDefined()
31
+ expect(sendRichMsgReq?.body?.rich_message.html).toContain(`Welcome`)
32
32
  })
33
33
  })
@@ -15,19 +15,21 @@ describe(`E2E: SumNumberPage`, () => {
15
15
 
16
16
  const step1 = await e2e.sendCallbackQuery(intent, asUser)
17
17
  expect(step1.length).toBeGreaterThan(0)
18
- expect(step1[0]!.body?.text).toBe(e2e.gmsg(intent, `s1`, {}))
18
+ expect(step1[0]!.body?.text).toBe(e2e.gmsg(intent, `l`, {}))
19
19
  e2e.clearCalls()
20
20
 
21
+ // input 1
21
22
  const step2 = await e2e.sendText(`23`, asUser)
22
23
  expect(step2.length).toBeGreaterThan(0)
23
- expect(step2[0]!.body?.text).toBe(e2e.gmsg(intent, `s2`, {}))
24
+ expect(step2[0]!.body?.text).toBe(e2e.gmsg(intent, `r`, {}))
24
25
  e2e.clearCalls()
25
26
 
27
+ // input 2
26
28
  const result = await e2e.sendText(`10`, asUser)
27
29
  expect(result.length).toBeGreaterThan(0)
28
30
  expect(result[0]!.body?.text).toContain(`Result Sum`)
29
31
  expect(result[0]!.body?.text).toContain(`33`)
30
- expect(e2e.extractButtonLabels(result[0])).toEqual(e2e.gkb(intent, `s`, `inline`, {}))
32
+ expect(e2e.extractButtonLabels(result[0]!)).toEqual(e2e.gkb(intent, `s`, `inline`, {}))
31
33
  })
32
34
 
33
35
  it(`re-prompt step 1 on invalid (non-numeric) input`, async () => {
@@ -39,6 +41,6 @@ describe(`E2E: SumNumberPage`, () => {
39
41
 
40
42
  const retry = await e2e.sendText(`not-a-number`, asUser)
41
43
  expect(retry.length).toBeGreaterThan(0)
42
- expect(retry[0]!.body?.text).toBe(e2e.gmsg(intent, `s1`, {}))
44
+ expect(retry[0]!.body?.text).toBe(e2e.gmsg(intent, `l`, {}))
43
45
  })
44
46
  })
@@ -17,8 +17,8 @@ describe(`E2E: UserNameNotFoundPage`, () => {
17
17
  expect(responses.length).toBeGreaterThan(0)
18
18
  const reply = responses[0]!
19
19
  expect(reply).toBeDefined()
20
- expect(reply.body?.text).toBe(e2e.gmsg(intent, `s1`, {}))
20
+ expect(reply.body?.text).toBe(e2e.gmsg(intent, `default`, {}))
21
21
  expect(reply.body?.text).toContain(`Username Required`)
22
- expect(e2e.extractButtonLabels(reply)).toEqual(e2e.gkb(intent, `s1`, `inline`, {}))
22
+ expect(e2e.extractButtonLabels(reply)).toEqual(e2e.gkb(intent, `default`, `inline`, {}))
23
23
  })
24
24
  })
@@ -38,8 +38,8 @@
38
38
  "test:all": "bun test test/ e2e/"
39
39
  },
40
40
  "dependencies": {
41
- "@gramstax/lmdb": "^0.11.1",
42
- "gramstax": "^0.11.1",
41
+ "@gramstax/lmdb": "^0.11.3",
42
+ "gramstax": "^0.11.3",
43
43
  "dayjs": "^1.11.13"
44
44
  },
45
45
  "peerDependencies": {
@@ -13,10 +13,10 @@ export class BotCore extends Gramstax {
13
13
  }
14
14
 
15
15
  async hookErrorPage(ctx: CtxCore, data: NonNullable<Parameters<NonNullable<Gramstax[`hookErrorPage`]>>[1]>) {
16
- await new GeneralStatusPage(ctx).showError(`${data.listenerName}:hookErrorPage`, data.error, data.isEdit)
16
+ await new GeneralStatusPage(ctx).berror.show(`${data.listenerName}:hookErrorPage`, data.error, data.isEdit)
17
17
  }
18
18
 
19
19
  async hookErrorInputNotFoundPage(ctx: CtxCore) {
20
- await new GeneralStatusPage(ctx).showErrorInputNotFound()
20
+ await new GeneralStatusPage(ctx).berrorNotFound.show()
21
21
  }
22
22
  }
@@ -11,7 +11,6 @@ export class Db extends LmdbEnv {
11
11
  }
12
12
 
13
13
  private async _init() {
14
- await this.open()
15
14
  this.user = this.register(UserRepo)
16
15
  }
17
16
 
@@ -1,7 +1,6 @@
1
1
  import {LmdbRepository} from "@gramstax/lmdb"
2
2
 
3
3
  export type IKUser = number
4
-
5
4
  export type IVUser = {
6
5
  tgId: number
7
6
  username: string
@@ -16,25 +15,25 @@ export class UserRepo extends LmdbRepository<IVUser, IKUser> {
16
15
  static dbName = `user`
17
16
 
18
17
  async findOrCreate(tgId: number, data: {username: string; firstName: string; lastName: string; languageCode: string}): Promise<{user: IVUser; created: boolean}> {
19
- return this.env.transaction(async () => {
20
- const existing = await this.get(tgId)
18
+ return this.env.transaction(() => {
19
+ const existing = this.get(tgId)
21
20
  if (existing) {
22
21
  const updated: IVUser = {...existing, ...data, updatedAt: Date.now()}
23
- await this.set(tgId, updated)
22
+ this.set(tgId, updated)
24
23
  return {user: updated, created: false}
25
24
  }
26
25
  const now = Date.now()
27
26
  const user: IVUser = {tgId, ...data, createdAt: now, updatedAt: now}
28
- await this.set(tgId, user)
27
+ this.set(tgId, user)
29
28
  return {user, created: true}
30
29
  })
31
30
  }
32
31
 
33
32
  async touch(tgId: number) {
34
- return this.env.transaction(async () => {
35
- const user = await this.get(tgId)
33
+ return this.env.transaction(() => {
34
+ const user = this.get(tgId)
36
35
  if (user) {
37
- await this.set(tgId, {...user, updatedAt: Date.now()})
36
+ this.set(tgId, {...user, updatedAt: Date.now()})
38
37
  }
39
38
  })
40
39
  }
@@ -4,14 +4,14 @@ import {UserNameNotFoundPage} from "~/pages/username-notfound"
4
4
 
5
5
  export class UserGuard extends GuardBase {
6
6
  async _catch(err: any, func: any, isEdit: boolean) {
7
- await new GeneralStatusPage(this).showError(func, err, isEdit)
7
+ await new GeneralStatusPage(this).berror.show(func, err, isEdit)
8
8
  return null
9
9
  }
10
10
 
11
11
  async ensureUsername(edit: boolean) {
12
12
  try {
13
- if (!this.userName || this.userName.length == 0) {
14
- await new UserNameNotFoundPage(this).showStep1Form(edit)
13
+ if (!this.from?.username || this.from?.username.length == 0) {
14
+ await new UserNameNotFoundPage(this).bdefault.show(edit)
15
15
  return null
16
16
  }
17
17
  return {status: true}
@@ -13,7 +13,7 @@ class Index {
13
13
  deploy: Env.TELEGRAM_BOT_DEPLOY,
14
14
  cacheSession: new CacheExternal(Env.TELEGRAM_CACHE_SESSION, 4 * 60 * 60 * 1000, `s`), // 4 hours
15
15
  optionsPage: {
16
- shortCallbackData: true
16
+ shortCallbackQueryData: true
17
17
  }
18
18
  })
19
19
  }
@@ -1,9 +1,9 @@
1
1
  import {PageBase} from "~/base/page"
2
2
 
3
3
  export class BlankPage extends PageBase {
4
- async handleCallbackData() {
5
- await this.callbackQueryAnswer()
4
+ async handleCallbackQueryData() {
5
+ await this.answerCallbackQuery()
6
6
  }
7
7
 
8
- static template = `<g-base></g-base>`
8
+ static template = ``
9
9
  }
@@ -2,41 +2,6 @@ import {LogHe} from "~/helpers/log"
2
2
  import {PageBase} from "~/base/page"
3
3
  import {StartPage} from "./start"
4
4
 
5
- class Base {
6
- static dynamic = `d`
7
- }
8
-
9
- class Helper {
10
- static kb(ctx: GeneralStatusPage) {
11
- const bi = Base.dynamic
12
- const kb = ctx.inlineKeyboardT([StartPage], `1`, bi)
13
- return kb
14
- }
15
- }
16
-
17
- class Main {
18
- static async showError(ctx: GeneralStatusPage, funcName: string, error: any, edit = true) {
19
- LogHe.errorMake(error, ctx.constructorName, funcName)
20
-
21
- await ctx.clearSession()
22
- const bi = Base.dynamic
23
- const kb = Helper.kb(ctx)
24
- const da = {isNotFound: false}
25
- if (edit) {
26
- await ctx.edit(kb, da, bi)
27
- } else {
28
- await ctx.reply(kb, da, bi)
29
- }
30
- }
31
-
32
- static async showErrorInputNotFound(ctx: GeneralStatusPage) {
33
- const bi = Base.dynamic
34
- const kb = Helper.kb(ctx)
35
- const da = {isNotFound: true}
36
- await ctx.reply(kb, da, bi)
37
- }
38
- }
39
-
40
5
  export class GeneralStatusPage extends PageBase {
41
6
  constructorName?: string
42
7
 
@@ -45,16 +10,27 @@ export class GeneralStatusPage extends PageBase {
45
10
  this.constructorName = p?.constructor?.name
46
11
  }
47
12
 
48
- async showError(...args: Parameters<typeof Main.showError> extends [any, ...infer R] ? R : []) {
49
- await Main.showError(this, ...args)
13
+ berror = {
14
+ show: async (funcName: string, error: any, isEdit = true) => {
15
+ LogHe.errorMake(error, this.constructorName, funcName)
16
+
17
+ await this.clearSession()
18
+ const kb = this.inlineKeyboardT([StartPage])
19
+ const da = {isNotFound: false}
20
+ await this.renderAuto(kb, da, null, null, isEdit)
21
+ }
50
22
  }
51
23
 
52
- async showErrorInputNotFound() {
53
- await Main.showErrorInputNotFound(this)
24
+ berrorNotFound = {
25
+ show: async () => {
26
+ const kb = this.inlineKeyboardT([StartPage])
27
+ const da = {isNotFound: true}
28
+ await this.render(kb, da)
29
+ }
54
30
  }
55
31
 
56
32
  static template = `
57
- <g-base id="${Base.dynamic}">
33
+ <g-base>
58
34
  ${this.kbk.home()}
59
35
  <g-message>
60
36
  <b>{~isNotFound ? "🔎 Route Not Found" : "❌ Error Occurred"~}</b>
@@ -1,38 +1,24 @@
1
1
  import {PageBase} from "~/base/page"
2
2
  import {StartPage} from "./start"
3
3
 
4
- class Base {
5
- static step1 = `s1`
6
- }
7
-
8
- class Main {
9
- static async showStep1Form(ctx: HelpPage, edit = true) {
10
- const bi = Base.step1
11
- const kb = ctx.inlineKeyboardT([StartPage], `1`, bi)
12
- const da = {}
13
- if (edit) {
14
- await ctx.edit(kb, da, bi)
15
- } else {
16
- await ctx.reply(kb, da, bi)
17
- }
18
- }
19
-
20
- static async processStep1Answer(ctx: HelpPage) {
21
- throw ctx
22
- }
23
- }
24
-
25
4
  export class HelpPage extends PageBase {
26
- async handleCallbackData() {
27
- await Main.showStep1Form(this, true)
5
+ async handleCallbackQueryData() {
6
+ await this.bdefault.show(true)
28
7
  }
29
8
 
30
9
  async handleCommandText() {
31
- await Main.showStep1Form(this, false)
10
+ await this.bdefault.show(false)
11
+ }
12
+
13
+ bdefault = {
14
+ show: async (isEdit = true) => {
15
+ const kb = this.inlineKeyboardT([StartPage])
16
+ await this.renderAutoRich(kb, null, null, null, null, isEdit)
17
+ }
32
18
  }
33
19
 
34
20
  static template = `
35
- <g-base id="${Base.step1}">
21
+ <g-base>
36
22
  ${this.kbk.back()}
37
23
  <g-message>
38
24
  <b>🆘 Help Message</b>
@@ -42,7 +28,8 @@ export class HelpPage extends PageBase {
42
28
  <b>🎰 Random Number</b> - Generate a random number
43
29
  <b>➕ Sum Number</b> - Add two numbers together
44
30
 
45
- <details><summary>📋 More commands</summary>
31
+ <details>
32
+ <summary>📋 More commands</summary>
46
33
  /start - Show welcome message
47
34
  /help - Show this help
48
35
  /random - Generate a random number
@@ -57,7 +44,8 @@ export class HelpPage extends PageBase {
57
44
  <b>🎰 Angka Acak</b> - Menghasilkan angka secara acak
58
45
  <b>➕ Jumlah Angka</b> - Menjumlahkan dua angka
59
46
 
60
- <details><summary>📋 Perintah lainnya</summary>
47
+ <details>
48
+ <summary>📋 Perintah lainnya</summary>
61
49
  /start - Tampilkan pesan selamat datang
62
50
  /help - Tampilkan bantuan ini
63
51
  /random - Hasilkan angka acak
@@ -1,44 +1,27 @@
1
1
  import {PageBase} from "~/base/page"
2
2
  import {StartPage} from "./start"
3
3
 
4
- class Base {
5
- static step1 = `s1`
6
- }
7
-
8
- class Main {
9
- static async showStep1Form(ctx: RandomNumberPage, edit = true) {
10
- const bi = Base.step1
11
- const kb = ctx.inlineKeyboardT([ctx, StartPage], `1`, bi)
12
- const da = {result: Math.random() * 10 ** 18}
13
- if (edit) {
14
- await ctx.edit(kb, da, bi)
15
- } else {
16
- await ctx.reply(kb, da, bi)
17
- }
18
- }
19
-
20
- static async processStep1Answer(ctx: RandomNumberPage) {
21
- throw ctx
22
- }
23
- }
24
-
25
4
  export class RandomNumberPage extends PageBase {
26
- async handleCallbackData() {
27
- await Main.showStep1Form(this, true)
5
+ async handleCallbackQueryData() {
6
+ await this.bdefault.show(true)
28
7
  }
29
8
 
30
9
  async handleCommandText() {
31
- await Main.showStep1Form(this, false)
10
+ await this.bdefault.show(false)
32
11
  }
33
12
 
34
- async showStep1Form(edit = true) {
35
- await Main.showStep1Form(this, edit)
13
+ bdefault = {
14
+ show: async (isEdit = true) => {
15
+ const kb = this.inlineKeyboardT([this, StartPage])
16
+ const da = {result: Math.random() * 10 ** 18}
17
+ await this.renderAuto(kb, da, null, null, isEdit)
18
+ }
36
19
  }
37
20
 
38
21
  static template = `
39
- <g-base id="${Base.step1}">
40
- <g-keyboard>🎲 Shake, ${this.kbl.back.default}</g-keyboard>
41
- <g-keyboard lang="id">🎲 Kocok, ${this.kbl.back.id}</g-keyboard>
22
+ <g-base>
23
+ <g-keyboard type="inline">🎲 Shake, ${this.kbl.back.default}</g-keyboard>
24
+ <g-keyboard type="inline" lang="id">🎲 Kocok, ${this.kbl.back.id}</g-keyboard>
42
25
  <g-message>
43
26
  <b>🎰 Random Number</b>
44
27
 
@@ -3,45 +3,28 @@ import {PageBase} from "~/base/page"
3
3
  import {RandomNumberPage} from "./random-number"
4
4
  import {SumNumberPage} from "./sum-number"
5
5
 
6
- class Base {
7
- static step1 = `s1`
8
- }
9
-
10
- class Main {
11
- static async showStep1Form(ctx: StartPage, edit = true) {
12
- await ctx.clearSession()
13
- const bi = Base.step1
14
- const kb = ctx.inlineKeyboardT([RandomNumberPage, SumNumberPage, HelpPage], `2`, bi)
15
- const da = {userName: ctx.userName}
16
- if (edit) {
17
- await ctx.edit(kb, da, bi)
18
- } else {
19
- await ctx.reply(kb, da, bi)
20
- }
21
- }
22
-
23
- static async processStep1Answer(ctx: StartPage) {
24
- throw ctx
25
- }
26
- }
27
-
28
6
  export class StartPage extends PageBase {
29
- async handleCallbackData() {
30
- await Main.showStep1Form(this, true)
7
+ async handleCallbackQueryData() {
8
+ await this.bdefault.show(true)
31
9
  }
32
10
 
33
11
  async handleCommandText() {
34
- await Main.showStep1Form(this, false)
12
+ await this.bdefault.show(false)
35
13
  }
36
14
 
37
- async showStep1Form(edit = true) {
38
- await Main.showStep1Form(this, edit)
15
+ bdefault = {
16
+ show: async (isEdit = true) => {
17
+ await this.clearSession()
18
+ const kb = this.inlineKeyboardT([RandomNumberPage, SumNumberPage, HelpPage], `2`)
19
+ const da = {userName: this.from?.username}
20
+ await this.renderAutoRich(kb, da, null, null, null, isEdit)
21
+ }
39
22
  }
40
23
 
41
24
  static template = `
42
- <g-base id="${Base.step1}">
43
- <g-keyboard>🎰 Random Number, ➕ Sum Number, 🆘 Help</g-keyboard>
44
- <g-keyboard lang="id">🎰 Angka Acak, ➕ Jumlah Angka, 🆘 Bantuan</g-keyboard>
25
+ <g-base>
26
+ <g-keyboard type="inline">🎰 Random Number, ➕ Sum Number, 🆘 Help</g-keyboard>
27
+ <g-keyboard type="inline" lang="id">🎰 Angka Acak, ➕ Jumlah Angka, 🆘 Bantuan</g-keyboard>
45
28
  <g-message>
46
29
  <b>👋 Welcome to the bot {{userName}}</b>
47
30
 
@@ -2,8 +2,8 @@ import {PageBase} from "~/base/page"
2
2
  import {StartPage} from "./start"
3
3
 
4
4
  class Base {
5
- static step1 = `s1`
6
- static step2 = `s2`
5
+ static left = `l`
6
+ static right = `r`
7
7
  static success = `s`
8
8
  }
9
9
 
@@ -17,76 +17,71 @@ class Helper {
17
17
  }
18
18
  }
19
19
 
20
- class Main {
21
- static async showStep1Form(ctx: SumNumberPage, edit: boolean) {
22
- const bi = Base.step1
23
- const kb = Helper.kb(ctx, bi)
24
- const da = {}
25
- await ctx.setSessionFreeText({step: bi})
26
- if (edit) {
27
- await ctx.edit(kb, da, bi)
28
- } else {
29
- await ctx.reply(kb, da, bi)
30
- }
31
- }
32
-
33
- static async processStep1Answer(ctx: SumNumberPage, left: number) {
34
- if (!Number.isFinite(left)) {
35
- await Main.showStep1Form(ctx, false)
36
- } else {
37
- await this.showStep2Form(ctx, left)
38
- }
20
+ export class SumNumberPage extends PageBase {
21
+ async handleCallbackQueryData() {
22
+ await this.bleft.show(true)
39
23
  }
40
24
 
41
- static async showStep2Form(ctx: SumNumberPage, left: number) {
42
- const bi = Base.step2
43
- const kb = Helper.kb(ctx, bi)
44
- const da = {}
45
- await ctx.setSessionFreeText({step: bi, left})
46
- await ctx.reply(kb, da, bi)
25
+ async handleCommandText() {
26
+ await this.bleft.show(false)
47
27
  }
48
28
 
49
- static async processStep2Answer(ctx: SumNumberPage, left: number, right: number) {
50
- if (!Number.isFinite(right)) {
51
- await Main.showStep2Form(ctx, right)
52
- } else if (!Number.isFinite(right ?? NaN)) {
53
- await Main.showStep1Form(ctx, false)
54
- } else {
55
- await Main.showSuccess(ctx, left, right)
29
+ async handleSessionFreeText() {
30
+ const {step, left} = Helper.getSessionParams(this)
31
+ const input = this.msg!.text!.trim()
32
+ const value = input ? Number(input) : NaN
33
+ if (step === Base.left) {
34
+ await this.bleft.process(value)
35
+ } else if (step === Base.right) {
36
+ await this.bright.process(value, left!)
56
37
  }
57
38
  }
58
39
 
59
- static async showSuccess(ctx: SumNumberPage, left: number, right: number) {
60
- await ctx.clearSession()
61
- const bi = Base.success
62
- const kb = Helper.kb(ctx, bi)
63
- const da = {result: left! + right}
64
- await ctx.reply(kb, da, bi)
65
- }
66
- }
67
-
68
- export class SumNumberPage extends PageBase {
69
- async handleCallbackData() {
70
- await Main.showStep1Form(this, true)
40
+ bleft = {
41
+ show: async (edit: boolean) => {
42
+ const kb = Helper.kb(this, Base.left)
43
+ await this.setSessionFreeText({step: Base.left})
44
+ if (edit) {
45
+ await this.renderEdit(kb, {}, Base.left)
46
+ } else {
47
+ await this.render(kb, {}, Base.left)
48
+ }
49
+ },
50
+ process: async (left: number) => {
51
+ if (!Number.isFinite(left)) {
52
+ await this.bleft.show(false)
53
+ } else {
54
+ await this.bright.show(left)
55
+ }
56
+ }
71
57
  }
72
58
 
73
- async handleCommandText() {
74
- await Main.showStep1Form(this, false)
59
+ bright = {
60
+ show: async (left: number) => {
61
+ const kb = Helper.kb(this, Base.right)
62
+ await this.setSessionFreeText({step: Base.right, left})
63
+ await this.render(kb, {}, Base.right)
64
+ },
65
+ process: async (value: number, left: number) => {
66
+ if (!Number.isFinite(value)) {
67
+ await this.bright.show(left)
68
+ } else {
69
+ await this.clearSession()
70
+ await this.bsuccess.show(left, value)
71
+ }
72
+ }
75
73
  }
76
74
 
77
- async handleSessionFreeText() {
78
- const {step, left} = Helper.getSessionParams(this)
79
- const input = this.msgText?.trim()
80
- const value = input ? Number(input) : NaN
81
- if (step === Base.step1) {
82
- await Main.processStep1Answer(this, value)
83
- } else if (step === Base.step2) {
84
- await Main.processStep2Answer(this, left!, value)
75
+ bsuccess = {
76
+ show: async (left: number, right: number) => {
77
+ const kb = Helper.kb(this, Base.success)
78
+ const da = {result: left + right}
79
+ await this.render(kb, da, Base.success)
85
80
  }
86
81
  }
87
82
 
88
83
  static template = `
89
- <g-base id="${Base.step1}">
84
+ <g-base id="${Base.left}">
90
85
  ${this.kbk.cancel()}
91
86
  <g-message>
92
87
  <b>📚 Input Left Number</b>
@@ -99,8 +94,7 @@ export class SumNumberPage extends PageBase {
99
94
  Contoh: 23
100
95
  </g-message>
101
96
  </g-base>
102
-
103
- <g-base id="${Base.step2}">
97
+ <g-base id="${Base.right}">
104
98
  ${this.kbk.cancel()}
105
99
  <g-message>
106
100
  <b>📚 Input Right Number</b>
@@ -113,7 +107,6 @@ export class SumNumberPage extends PageBase {
113
107
  Contoh: 10
114
108
  </g-message>
115
109
  </g-base>
116
-
117
110
  <g-base id="${Base.success}">
118
111
  ${this.kbk.home()}
119
112
  <g-message>
@@ -1,34 +1,16 @@
1
1
  import {HelpPage} from "./help"
2
2
  import {PageBase} from "~/base/page"
3
3
 
4
- class Base {
5
- static step1 = `s1`
6
- }
7
-
8
- class Main {
9
- static async showStep1Form(ctx: UserNameNotFoundPage, edit = true) {
10
- const bi = Base.step1
11
- const kb = ctx.inlineKeyboardT([HelpPage], `1`, bi)
12
- const da = {}
13
- if (edit) {
14
- await ctx.edit(kb, da, bi)
15
- } else {
16
- await ctx.reply(kb, da, bi)
17
- }
18
- }
19
-
20
- static async processStep1Answer(ctx: UserNameNotFoundPage) {
21
- throw ctx
22
- }
23
- }
24
-
25
4
  export class UserNameNotFoundPage extends PageBase {
26
- async showStep1Form(edit = true) {
27
- await Main.showStep1Form(this, edit)
5
+ bdefault = {
6
+ show: async (isEdit = true) => {
7
+ const kb = this.inlineKeyboardT([HelpPage])
8
+ await this.renderAuto(kb, {}, null, null, isEdit)
9
+ }
28
10
  }
29
11
 
30
12
  static template = `
31
- <g-base id="${Base.step1}">
13
+ <g-base>
32
14
  ${this.kbk.home()}
33
15
  <g-message>
34
16
  <b>⚠️ Username Required</b>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-gramstax",
3
- "version": "0.11.2",
3
+ "version": "0.12.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public",