feat(create-bot): various improvements
- improved bun support - deno support - use antfu/eslint-config - fixed some issues - removed pre-commit hooks
This commit is contained in:
parent
7f7f513bc8
commit
64da48926f
29 changed files with 324 additions and 345 deletions
|
@ -5,22 +5,24 @@ Starter kit for creating bots using `@mtcute/node` or `@mtcute/bun`.
|
|||
[Learn more](https://mtcute.dev/guide/)
|
||||
|
||||
## Features
|
||||
- **Linters**: can generate linting configs for `eslint` and `prettier`
|
||||
- **Linters**: can set up linters using [@antfu/eslint-config](https://github.com/antfu/eslint-config)
|
||||
- **TypeScript**: can generate a TypeScript project
|
||||
- **Docker**: can generate a Dockerfile for easy deployment
|
||||
- **I18n**: can generate a basic i18n setup using `@mtcute/i18n`
|
||||
|
||||
## Usage
|
||||
|
||||
Depending on your preferred package manager, run one of the following commands:
|
||||
Depending on your preferred package manager and runtime, run one of the following commands:
|
||||
```bash
|
||||
pnpm create @mtcute/bot
|
||||
pnpm create @mtcute/bot my-awesome-bot
|
||||
# or
|
||||
yarn create @mtcute/bot
|
||||
yarn create @mtcute/bot my-awesome-bot
|
||||
# or
|
||||
npm create @mtcute/bot
|
||||
npm create @mtcute/bot my-awesome-bot
|
||||
# or
|
||||
bun create @mtcute/bot
|
||||
bun create @mtcute/bot my-awesome-bot
|
||||
# or
|
||||
deno run -A npm:@mtcute/create-bot my-awesome-bot
|
||||
```
|
||||
|
||||
and follow the instructions
|
||||
|
|
|
@ -8,7 +8,8 @@
|
|||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "pnpm run -w build-package create-bot",
|
||||
"run": "ts-node-esm src/main.ts"
|
||||
"run": "ts-node-esm src/main.ts",
|
||||
"run:deno": "node scripts/generate-import-map.js && deno run --import-map=./scripts/import-map.json -A --unstable-sloppy-imports src/main.ts"
|
||||
},
|
||||
"bin": {
|
||||
"create-bot": "./src/main.js"
|
||||
|
|
1
packages/create-bot/scripts/.gitignore
vendored
Normal file
1
packages/create-bot/scripts/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
import-map.json
|
11
packages/create-bot/scripts/generate-import-map.js
Normal file
11
packages/create-bot/scripts/generate-import-map.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
import * as fs from 'fs'
|
||||
|
||||
const packageJson = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url)))
|
||||
|
||||
const importMap = {}
|
||||
|
||||
for (const [name, version] of Object.entries(packageJson.dependencies)) {
|
||||
importMap[name] = `npm:${name}@${version}`
|
||||
}
|
||||
|
||||
fs.writeFileSync(new URL('import-map.json', import.meta.url), JSON.stringify({ imports: importMap }, null, 2))
|
|
@ -1,6 +1,7 @@
|
|||
import { promises as fs } from 'fs'
|
||||
import { homedir } from 'os'
|
||||
import * as path from 'path'
|
||||
import { promises as fs } from 'node:fs'
|
||||
import { homedir } from 'node:os'
|
||||
import * as path from 'node:path'
|
||||
import process from 'node:process'
|
||||
|
||||
export interface UserConfigPersisted {
|
||||
apiId: number
|
||||
|
|
|
@ -1,16 +1,27 @@
|
|||
import { join } from 'node:path'
|
||||
|
||||
import { UserConfig } from './cli.js'
|
||||
import { MtcuteFeature } from './features/types.js'
|
||||
import { fetchAllLatestVersionsJsr } from './jsr.js'
|
||||
import { getInstallCommand, PackageManager } from './package-manager.js'
|
||||
import { exec } from './utils.js'
|
||||
|
||||
export interface DependenciesList {
|
||||
dependencies: string[]
|
||||
devDepdenencies: string[]
|
||||
}
|
||||
|
||||
export function buildDependenciesList(config: UserConfig) {
|
||||
const dependencies = []
|
||||
const devDepdenencies = ['dotenv-cli']
|
||||
const devDepdenencies = []
|
||||
|
||||
if (config.packageManager === PackageManager.Bun) {
|
||||
dependencies.push('@mtcute/bun')
|
||||
} else if (config.packageManager === PackageManager.Deno) {
|
||||
dependencies.push('@std/dotenv', '@mtcute/deno')
|
||||
} else {
|
||||
dependencies.push('@mtcute/node')
|
||||
// node
|
||||
dependencies.push('dotenv-cli', '@mtcute/node')
|
||||
}
|
||||
|
||||
if (config.features.includes(MtcuteFeature.Dispatcher)) {
|
||||
|
@ -25,29 +36,19 @@ export function buildDependenciesList(config: UserConfig) {
|
|||
dependencies.push('@mtcute/crypto-node')
|
||||
}
|
||||
|
||||
if (config.features.includes(MtcuteFeature.TypeScript)) {
|
||||
if (config.features.includes(MtcuteFeature.TypeScript) && config.packageManager !== PackageManager.Deno) {
|
||||
devDepdenencies.push('typescript', '@types/node')
|
||||
|
||||
if (config.packageManager === PackageManager.Bun) {
|
||||
devDepdenencies.push('@types/bun')
|
||||
} else {
|
||||
// node can't handle typescript natively
|
||||
devDepdenencies.push('tsx')
|
||||
}
|
||||
}
|
||||
|
||||
if (config.features.includes(MtcuteFeature.Linters)) {
|
||||
devDepdenencies.push(
|
||||
'husky',
|
||||
'lint-staged',
|
||||
'eslint',
|
||||
'eslint-config-prettier',
|
||||
'eslint-plugin-ascii',
|
||||
'eslint-plugin-import',
|
||||
'eslint-plugin-simple-import-sort',
|
||||
'prettier',
|
||||
)
|
||||
|
||||
if (config.features.includes(MtcuteFeature.TypeScript)) {
|
||||
devDepdenencies.push(
|
||||
'eslint-import-resolver-typescript',
|
||||
'@typescript-eslint/eslint-plugin',
|
||||
'@typescript-eslint/parser',
|
||||
)
|
||||
}
|
||||
devDepdenencies.push('@antfu/eslint-config')
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -59,6 +60,35 @@ export function buildDependenciesList(config: UserConfig) {
|
|||
export async function installDependencies(cwd: string, config: UserConfig) {
|
||||
const { dependencies, devDepdenencies } = buildDependenciesList(config)
|
||||
|
||||
if (config.packageManager === PackageManager.Deno) {
|
||||
// deno doesn't have a package manager per se, but we can generate an import map
|
||||
// to basically achieve the same thing
|
||||
//
|
||||
// since we don't have a package manager, we have to resolve "latest" versions ourselves :c
|
||||
// additionally, there's no notion of "dev" dependencies is deno. though fairly enough,
|
||||
// there should be no dev deps in our deno template (but let's just keep it for consistency)
|
||||
|
||||
const allDeps = [...dependencies, ...devDepdenencies]
|
||||
const versions = await fetchAllLatestVersionsJsr(allDeps)
|
||||
|
||||
const denoJson = {
|
||||
imports: {} as Record<string, string>,
|
||||
tasks: {
|
||||
start: `deno run -A --unstable-ffi ./src/main.${
|
||||
config.features.includes(MtcuteFeature.TypeScript) ? 'ts' : 'js'
|
||||
}`,
|
||||
},
|
||||
}
|
||||
|
||||
for (const dep of allDeps) {
|
||||
denoJson.imports[dep] = `jsr:${dep}@^${versions.get(dep)!}`
|
||||
}
|
||||
|
||||
await Deno.writeTextFile(join(cwd, 'deno.json'), JSON.stringify(denoJson, null, 4))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
await exec(cwd, ...getInstallCommand({ mgr: config.packageManager, packages: dependencies }))
|
||||
await exec(cwd, ...getInstallCommand({ mgr: config.packageManager, packages: devDepdenencies, dev: true }))
|
||||
}
|
||||
|
|
|
@ -23,14 +23,23 @@ export function getFeatureChoices(packageMananger: PackageManager): CheckboxChoi
|
|||
checked: true,
|
||||
},
|
||||
{
|
||||
name: ' 🥰 Setup Prettier & ESLint',
|
||||
short: 'Linters',
|
||||
value: MtcuteFeature.Linters,
|
||||
name: ' 📦 Initialize git repository',
|
||||
short: 'Git',
|
||||
value: MtcuteFeature.Git,
|
||||
checked: true,
|
||||
},
|
||||
]
|
||||
|
||||
if (packageMananger !== PackageManager.Bun) {
|
||||
if (packageMananger !== PackageManager.Deno) {
|
||||
arr.unshift({
|
||||
name: ' 🥰 Setup ESLint with @antfu/eslint-config',
|
||||
short: 'Linters',
|
||||
value: MtcuteFeature.Linters,
|
||||
checked: true,
|
||||
})
|
||||
}
|
||||
|
||||
if (packageMananger !== PackageManager.Bun && packageMananger !== PackageManager.Deno) {
|
||||
arr.unshift({
|
||||
name: ' 🚀 Native addon (better performance)',
|
||||
short: 'Native addon',
|
||||
|
|
|
@ -5,4 +5,5 @@ export enum MtcuteFeature {
|
|||
Docker = 'docker',
|
||||
TypeScript = 'typescript',
|
||||
Linters = 'linters',
|
||||
Git = 'git',
|
||||
}
|
||||
|
|
25
packages/create-bot/src/jsr.ts
Normal file
25
packages/create-bot/src/jsr.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
export async function fetchLatestVersionJsr(pkg: string): Promise<string> {
|
||||
const res = await fetch(`https://jsr.io/${pkg}/meta.json`)
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to fetch ${pkg} metadata: ${await res.text()}`)
|
||||
}
|
||||
|
||||
const meta = (await res.json()) as {
|
||||
latest: string
|
||||
}
|
||||
|
||||
return meta.latest
|
||||
}
|
||||
|
||||
export async function fetchAllLatestVersionsJsr(pkgs: string[]): Promise<Map<string, string>> {
|
||||
const res = new Map<string, string>()
|
||||
|
||||
await Promise.all(
|
||||
pkgs.map(async (pkg) => {
|
||||
res.set(pkg, await fetchLatestVersionJsr(pkg))
|
||||
}),
|
||||
)
|
||||
|
||||
return res
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
#!/usr/bin/env node
|
||||
import * as colors from 'colorette'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { basename, dirname, join } from 'node:path'
|
||||
import process from 'node:process'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
import { askForConfig } from './cli.js'
|
||||
|
@ -23,9 +24,18 @@ if (packageManager === PackageManager.Bun) {
|
|||
console.log(`${colors.red('‼️ Warning:')} ${colors.yellow('Bun')} support is ${colors.bold('experimental')}`)
|
||||
}
|
||||
|
||||
if (packageManager === PackageManager.Deno) {
|
||||
console.log(`${colors.red('‼️ Warning:')} ${colors.yellow('Deno')} support is ${colors.bold('experimental')}`)
|
||||
}
|
||||
|
||||
const config = await askForConfig(packageManager)
|
||||
config.name = projectName
|
||||
const outDir = process.env.TARGET_DIR || join(process.cwd(), projectName)
|
||||
config.name = basename(projectName)
|
||||
let outDir = process.env.TARGET_DIR || projectName
|
||||
|
||||
if (!outDir.match(/^([A-Za-z]:)?[/\\]/)) {
|
||||
// assume it's a relative path
|
||||
outDir = join(process.cwd(), outDir)
|
||||
}
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
|
@ -33,20 +43,25 @@ await runTemplater(join(__dirname, '../template'), outDir, config)
|
|||
|
||||
await installDependencies(outDir, config)
|
||||
|
||||
await exec(outDir, 'git', 'init')
|
||||
|
||||
if (config.features.includes(MtcuteFeature.Linters)) {
|
||||
if (process.platform === 'win32') {
|
||||
// windows doesn't track executable bit, but git does
|
||||
await exec(outDir, 'git', 'update-index', '--chmod=+x', '.husky/pre-commit')
|
||||
} else {
|
||||
await exec(outDir, 'chmod', '+x', '.husky/pre-commit')
|
||||
}
|
||||
await exec(outDir, ...getExecCommand(config.packageManager, 'eslint', '--fix', '.'))
|
||||
}
|
||||
|
||||
await exec(outDir, ...getExecCommand(config.packageManager, 'husky'))
|
||||
if (config.features.includes(MtcuteFeature.Git)) {
|
||||
await exec(outDir, 'git', 'init', '.', '--initial-branch', 'main')
|
||||
await exec(outDir, 'git', 'add', '.')
|
||||
await exec(outDir, 'git', 'commit', '-m', 'Initial commit')
|
||||
}
|
||||
|
||||
console.log(`✅ Scaffolded new project at ${colors.blue(outDir)}`)
|
||||
console.log('🚀 Run it with:')
|
||||
console.log(` ${colors.blue('$')} cd ${projectName}`)
|
||||
console.log(` ${colors.blue('$')} ${config.packageManager} start`)
|
||||
|
||||
if (config.packageManager === PackageManager.Deno) {
|
||||
console.log(` ${colors.blue('$')} deno task start`)
|
||||
// for whatever reason, deno keeps hanging after the we finish
|
||||
// and doesn't even handle SIGINT. just exit lol
|
||||
process.exit(0)
|
||||
} else {
|
||||
console.log(` ${colors.blue('$')} ${config.packageManager} start`)
|
||||
}
|
||||
|
|
|
@ -1,20 +1,48 @@
|
|||
import * as colors from 'colorette'
|
||||
import process from 'node:process'
|
||||
|
||||
export function getPackageManagerVersion(): [string, string] | null {
|
||||
if (typeof Deno !== 'undefined') {
|
||||
return null
|
||||
}
|
||||
|
||||
const userAgent = process.env.npm_config_user_agent
|
||||
|
||||
if (!userAgent) {
|
||||
return null
|
||||
}
|
||||
|
||||
const software = userAgent.split(' ')[0]
|
||||
const manager = software.split('/')[0]
|
||||
const version = software.split('/')[1]
|
||||
|
||||
if (!version.match(/^\d+\.\d+\.\d+$/)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return [manager, version]
|
||||
}
|
||||
|
||||
export enum PackageManager {
|
||||
Npm = 'npm',
|
||||
Yarn = 'yarn',
|
||||
Pnpm = 'pnpm',
|
||||
Bun = 'bun',
|
||||
Deno = 'deno',
|
||||
}
|
||||
|
||||
export function getPackageManager(): PackageManager {
|
||||
const userAgent = process.env.npm_config_user_agent
|
||||
const parsed = getPackageManagerVersion()
|
||||
|
||||
if (!parsed) {
|
||||
if (typeof Deno !== 'undefined') return PackageManager.Deno
|
||||
|
||||
console.warn(colors.yellow('[warning] could not detect package manager, falling back to pnpm'))
|
||||
|
||||
if (!userAgent) {
|
||||
return PackageManager.Pnpm // fall back to the most based one
|
||||
}
|
||||
|
||||
const name = userAgent.split('/')[0]
|
||||
|
||||
switch (name) {
|
||||
switch (parsed[0]) {
|
||||
case 'pnpm':
|
||||
return PackageManager.Pnpm
|
||||
case 'yarn':
|
||||
|
@ -24,7 +52,7 @@ export function getPackageManager(): PackageManager {
|
|||
case 'bun':
|
||||
return PackageManager.Bun
|
||||
default:
|
||||
throw new Error(`Unsupported package manager: ${name}`)
|
||||
throw new Error(`Unsupported package manager: ${parsed[0]}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,5 +94,20 @@ export function getExecCommand(mgr: PackageManager, ...cmd: string[]) {
|
|||
return ['pnpm', 'exec', ...cmd]
|
||||
case PackageManager.Bun:
|
||||
return ['bun', 'run', ...cmd]
|
||||
case PackageManager.Deno:
|
||||
throw new Error('Deno does not support exec commands')
|
||||
}
|
||||
}
|
||||
|
||||
export function packageManagerToRuntime(mgr: PackageManager) {
|
||||
switch (mgr) {
|
||||
case PackageManager.Npm:
|
||||
case PackageManager.Yarn:
|
||||
case PackageManager.Pnpm:
|
||||
return 'node'
|
||||
case PackageManager.Bun:
|
||||
return 'bun'
|
||||
case PackageManager.Deno:
|
||||
return 'deno'
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import * as path from 'node:path'
|
|||
|
||||
import type { UserConfig } from './cli.js'
|
||||
import { MtcuteFeature } from './features/types.js'
|
||||
import { getPackageManagerVersion, packageManagerToRuntime } from './package-manager.js'
|
||||
|
||||
const templater = Handlebars.create()
|
||||
|
||||
|
@ -37,6 +38,8 @@ export async function runTemplaterForFile(file: string, config: UserConfig): Pro
|
|||
|
||||
const result = execute({
|
||||
...config,
|
||||
runtime: packageManagerToRuntime(config.packageManager),
|
||||
packageManagerVersion: getPackageManagerVersion()?.join('@'),
|
||||
features: config.features.reduce<Record<MtcuteFeature, boolean>>(
|
||||
(acc, f) => {
|
||||
acc[f] = true
|
||||
|
|
|
@ -3,7 +3,7 @@ import { spawn } from 'cross-spawn'
|
|||
|
||||
export function exec(cwd: string, ...cmd: string[]) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
console.log(`${colors.blue('$')} ${cmd.join(' ')}`)
|
||||
console.log(`${colors.blue('$')} ${cmd.map((it) => (it.includes(' ') ? JSON.stringify(it) : it)).join(' ')}`)
|
||||
|
||||
const proc = spawn(cmd[0], cmd.slice(1), {
|
||||
stdio: 'inherit',
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
{{emit_if features.linters}}
|
||||
node_modules/
|
||||
.husky/
|
||||
.idea/
|
||||
|
||||
dist/
|
||||
|
||||
*.json
|
|
@ -1,210 +0,0 @@
|
|||
{{emit_if features.linters}}
|
||||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
es2021: true,
|
||||
node: true,
|
||||
},
|
||||
extends: ['eslint:recommended', 'plugin:import/recommended', 'prettier'],
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: ['ascii', 'import', 'simple-import-sort'],
|
||||
reportUnusedDisableDirectives: true,
|
||||
rules: {
|
||||
// see https://github.com/airbnb/javascript/blob/master/packages/eslint-config-airbnb-base/rules/style.js#L122
|
||||
indent: [
|
||||
2,
|
||||
4,
|
||||
{
|
||||
SwitchCase: 1,
|
||||
VariableDeclarator: 1,
|
||||
outerIIFEBody: 1,
|
||||
// MemberExpression: null,
|
||||
FunctionDeclaration: {
|
||||
parameters: 1,
|
||||
body: 1,
|
||||
},
|
||||
FunctionExpression: {
|
||||
parameters: 1,
|
||||
body: 1,
|
||||
},
|
||||
CallExpression: {
|
||||
arguments: 1,
|
||||
},
|
||||
ArrayExpression: 1,
|
||||
ObjectExpression: 1,
|
||||
ImportDeclaration: 1,
|
||||
flatTernaryExpressions: false,
|
||||
// list derived from https://github.com/benjamn/ast-types/blob/HEAD/def/jsx.js
|
||||
ignoredNodes: [
|
||||
'JSXElement',
|
||||
'JSXElement > *',
|
||||
'JSXAttribute',
|
||||
'JSXIdentifier',
|
||||
'JSXNamespacedName',
|
||||
'JSXMemberExpression',
|
||||
'JSXSpreadAttribute',
|
||||
'JSXExpressionContainer',
|
||||
'JSXOpeningElement',
|
||||
'JSXClosingElement',
|
||||
'JSXText',
|
||||
'JSXEmptyExpression',
|
||||
'JSXSpreadChild',
|
||||
],
|
||||
ignoreComments: false,
|
||||
},
|
||||
],
|
||||
|
||||
semi: [2, 'never', { beforeStatementContinuationChars: 'never' }],
|
||||
'semi-spacing': [2, { before: false, after: true }],
|
||||
'wrap-iife': [2, 'inside'],
|
||||
'no-caller': 2,
|
||||
'no-cond-assign': [2, 'except-parens'],
|
||||
'no-constant-condition': 0,
|
||||
'no-debugger': 2,
|
||||
'no-dupe-args': 2,
|
||||
'no-dupe-keys': 2,
|
||||
'no-duplicate-case': 2,
|
||||
'no-empty': [2, { allowEmptyCatch: true }],
|
||||
'no-empty-function': 'off',
|
||||
'no-extra-boolean-cast': 2,
|
||||
'no-extra-semi': 2,
|
||||
'no-func-assign': 2,
|
||||
'no-new': 2,
|
||||
'no-sparse-arrays': 2,
|
||||
'no-unexpected-multiline': 2,
|
||||
'no-unreachable': 2,
|
||||
|
||||
'max-params': [1, 5],
|
||||
'max-depth': [1, 4],
|
||||
'no-eq-null': 0,
|
||||
'no-unused-expressions': 0,
|
||||
'dot-notation': 2,
|
||||
'use-isnan': 2,
|
||||
|
||||
// Best practices
|
||||
'block-scoped-var': 2,
|
||||
complexity: [0, 11],
|
||||
curly: [2, 'multi-line'],
|
||||
eqeqeq: [2, 'always', { null: 'ignore' }],
|
||||
'no-else-return': 2,
|
||||
'no-extra-bind': 2,
|
||||
'no-implicit-coercion': 2,
|
||||
'no-return-assign': 0,
|
||||
'no-sequences': 2,
|
||||
yoda: 2,
|
||||
|
||||
// Variables
|
||||
'no-restricted-globals': ['error'],
|
||||
'no-var': 1,
|
||||
|
||||
// Codestyle
|
||||
'arrow-parens': [2, 'always'],
|
||||
'array-bracket-spacing': [2, 'never'],
|
||||
'brace-style': [2, '1tbs', { allowSingleLine: true }],
|
||||
camelcase: [2, { properties: 'never' }],
|
||||
'comma-dangle': ['warn', 'always-multiline'],
|
||||
'comma-spacing': [2, { before: false, after: true }],
|
||||
'eol-last': 2,
|
||||
'func-call-spacing': [2, 'never'],
|
||||
'block-spacing': 2,
|
||||
'keyword-spacing': [2, { before: true, after: true }],
|
||||
'max-len': [
|
||||
2,
|
||||
{
|
||||
code: 120,
|
||||
ignoreUrls: true,
|
||||
ignoreComments: false,
|
||||
ignoreRegExpLiterals: true,
|
||||
ignoreStrings: true,
|
||||
ignoreTemplateLiterals: true,
|
||||
ignorePattern: 'require',
|
||||
},
|
||||
],
|
||||
'no-lonely-if': 2,
|
||||
'no-mixed-spaces-and-tabs': 2,
|
||||
'no-multi-spaces': 2,
|
||||
'no-multiple-empty-lines': [2, { max: 1, maxBOF: 0, maxEOF: 0 }],
|
||||
'no-trailing-spaces': 2,
|
||||
'ascii/valid-name': 2,
|
||||
'no-unneeded-ternary': 2,
|
||||
'no-nested-ternary': 2,
|
||||
'object-curly-spacing': [2, 'always'],
|
||||
'one-var-declaration-per-line': [2, 'initializations'],
|
||||
'one-var': [2, { let: 'never', const: 'never' }],
|
||||
'operator-linebreak': [2, 'after'],
|
||||
'padded-blocks': [2, 'never'],
|
||||
'quote-props': [2, 'as-needed', { numbers: true }],
|
||||
quotes: [2, 'single', { avoidEscape: true }],
|
||||
'space-before-blocks': [2, 'always'],
|
||||
'space-before-function-paren': [
|
||||
2,
|
||||
{
|
||||
named: 'never',
|
||||
anonymous: 'always',
|
||||
},
|
||||
],
|
||||
'space-in-parens': 2,
|
||||
'key-spacing': [2, { beforeColon: false, afterColon: true, mode: 'strict' }],
|
||||
'space-infix-ops': 2,
|
||||
'padding-line-between-statements': [
|
||||
'error',
|
||||
{ blankLine: 'always', prev: '*', next: 'return' },
|
||||
{ blankLine: 'always', prev: '*', next: 'block-like' },
|
||||
{ blankLine: 'any', prev: 'block-like', next: 'block-like' },
|
||||
{ blankLine: 'any', prev: 'case', next: 'case' },
|
||||
],
|
||||
|
||||
'simple-import-sort/imports': [
|
||||
'error',
|
||||
{
|
||||
groups: [['^@mtcute'], ['^[a-z]'], ['^#']],
|
||||
},
|
||||
],
|
||||
'simple-import-sort/exports': 'error',
|
||||
'import/no-relative-packages': 'error',
|
||||
'import/no-mutable-exports': 'error',
|
||||
'import/no-default-export': 'error',
|
||||
// https://github.com/import-js/eslint-plugin-import/issues/2903
|
||||
'import/named': 'off',
|
||||
},
|
||||
{{#if features.typescript}}
|
||||
overrides: [
|
||||
{
|
||||
files: ['**/*.ts'],
|
||||
env: { browser: true, es6: true, node: true },
|
||||
extends: ['plugin:@typescript-eslint/recommended'],
|
||||
globals: { Atomics: 'readonly', SharedArrayBuffer: 'readonly' },
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['@typescript-eslint'],
|
||||
rules: {
|
||||
'@typescript-eslint/consistent-type-assertions': 2,
|
||||
'@typescript-eslint/no-explicit-any': 2,
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
2,
|
||||
{
|
||||
args: 'after-used',
|
||||
argsIgnorePattern: '^_',
|
||||
ignoreRestSiblings: true,
|
||||
vars: 'all',
|
||||
varsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
},
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
node: true,
|
||||
typescript: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
{{/if}}
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
node: true,
|
||||
},
|
||||
},
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
{{#if (ne runtime "deno")}}
|
||||
node_modules/
|
||||
{{/if}}
|
||||
dist/
|
||||
private/
|
||||
.nyc_output/
|
||||
**/.DS_Store
|
||||
.idea
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
{{emit_if features.linters}}
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
pnpm exec lint-staged
|
|
@ -1,7 +0,0 @@
|
|||
{{emit_if features.linters}}
|
||||
module.exports = {
|
||||
'*.{js,jsx,ts,tsx}': [
|
||||
`prettier --write`,
|
||||
`eslint --fix`,
|
||||
]
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
{{emit_if features.linters}}
|
||||
{
|
||||
"arrowParens": "always",
|
||||
"bracketSpacing": true,
|
||||
"embeddedLanguageFormatting": "auto",
|
||||
"htmlWhitespaceSensitivity": "css",
|
||||
"insertPragma": false,
|
||||
"jsxBracketSameLine": false,
|
||||
"jsxSingleQuote": false,
|
||||
"printWidth": 80,
|
||||
"proseWrap": "preserve",
|
||||
"quoteProps": "as-needed",
|
||||
"requirePragma": false,
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 4,
|
||||
"trailingComma": "es5",
|
||||
"useTabs": false,
|
||||
"vueIndentScriptAndStyle": false
|
||||
}
|
|
@ -12,4 +12,4 @@ RUN pnpm install --frozen-lockfile
|
|||
COPY src /app/src
|
||||
RUN pnpm run build
|
||||
|
||||
CMD [ "node", "/app/dist/index.js" ]
|
||||
CMD [ "node", "/app/dist/main.js" ]
|
||||
|
|
|
@ -1,14 +1,24 @@
|
|||
# {{name}}
|
||||
|
||||
MTCute powered Telegram bot
|
||||
mtcute powered Telegram bot
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
pnpm install --frozen-lockfile
|
||||
{{#if (ne runtime "deno")}}
|
||||
{{#if (eq packageManager "npm")}}
|
||||
npm ci
|
||||
{{else}}
|
||||
{{packageManager}} install --frozen-lockfile
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
cp .env.example .env
|
||||
# edit .env
|
||||
pnpm start
|
||||
{{#if (eq runtime "deno")}}
|
||||
deno task start
|
||||
{{else}}
|
||||
{{packageManager}} start
|
||||
{{/if}}
|
||||
```
|
||||
|
||||
*generated with @mtcute/create-bot*
|
21
packages/create-bot/template/eslint.config.js.hbs
Normal file
21
packages/create-bot/template/eslint.config.js.hbs
Normal file
|
@ -0,0 +1,21 @@
|
|||
{{emit_if features.linters}}
|
||||
import antfu from '@antfu/eslint-config'
|
||||
|
||||
export default antfu({
|
||||
stylistic: {
|
||||
indent: 4,
|
||||
},
|
||||
{{#if features.typescript}}
|
||||
typescript: true,
|
||||
{{/if}}
|
||||
yaml: false,
|
||||
rules: {
|
||||
'curly': ['error', 'multi-line'],
|
||||
'style/brace-style': ['error', '1tbs', { allowSingleLine: true }],
|
||||
'style/quotes': ['error', 'single', { avoidEscape: true }],
|
||||
'import/order': ['error', { 'newlines-between': 'always' }],
|
||||
'antfu/if-newline': 'off',
|
||||
'style/max-statements-per-line': ['error', { max: 2 }],
|
||||
'no-console': 'off',
|
||||
},
|
||||
})
|
|
@ -1,27 +1,31 @@
|
|||
{{emit_if (ne runtime "deno")}}
|
||||
{
|
||||
"name": "{{ name }}",
|
||||
"license": "MIT",
|
||||
"version": "0.0.0",
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
{{#if packageManagerVersion}}
|
||||
"packageManager": "{{packageManagerVersion}}",
|
||||
{{/if}}
|
||||
"scripts": {
|
||||
{{#if features.linters}}
|
||||
"prepare": "husky || true",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint --fix .",
|
||||
"format": "prettier --write \"src/**/*.ts\"",
|
||||
{{/if}}
|
||||
{{#if (eq packageManager "bun")}}
|
||||
{{#if (eq runtime "bun")}}
|
||||
{{#if features.typescript}}
|
||||
"start": "bun ./src/index.ts"
|
||||
"start": "bun ./src/main.ts",
|
||||
"build": "tsc && bun build src/main.ts --target=bun --outdir=dist"
|
||||
{{else}}
|
||||
"start": "bun ./src/index.js"
|
||||
"start": "bun ./src/main.js"
|
||||
{{/if}}
|
||||
{{else}}
|
||||
{{#if features.typescript}}
|
||||
"start": "tsc && dotenv node ./dist/index.js",
|
||||
"start": "dotenv tsx ./src/main.ts",
|
||||
"build": "tsc"
|
||||
{{else}}
|
||||
"start": "dotenv node ./src/index.js"
|
||||
"start": "dotenv node ./src/main.js"
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
}
|
||||
|
|
24
packages/create-bot/template/src/env.js.hbs
Normal file
24
packages/create-bot/template/src/env.js.hbs
Normal file
|
@ -0,0 +1,24 @@
|
|||
{{emit_if (not features.typescript)}}
|
||||
{{#if (eq runtime "deno")}}
|
||||
import '@std/dotenv/load'
|
||||
|
||||
const API_ID = Number.parseInt(Deno.env.get('API_ID'))
|
||||
const API_HASH = Deno.env.get('API_HASH')
|
||||
{{#if botToken}}
|
||||
const BOT_TOKEN = Deno.env.get('BOT_TOKEN')
|
||||
{{/if}}
|
||||
{{else}}
|
||||
import process from 'node:process'
|
||||
|
||||
const API_ID = Number.parseInt(process.env.API_ID)
|
||||
const API_HASH = process.env.API_HASH
|
||||
{{#if botToken}}
|
||||
const BOT_TOKEN = process.env.BOT_TOKEN
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
if (Number.isNaN(API_ID) || !API_HASH) {
|
||||
throw new Error('API_ID or API_HASH not set!')
|
||||
}
|
||||
|
||||
export { API_HASH, API_ID{{#if botToken}}, BOT_TOKEN{{/if}} }
|
|
@ -1,11 +1,23 @@
|
|||
{{emit_if features.typescript}}
|
||||
const API_ID = parseInt(process.env.API_ID!)
|
||||
{{#if (eq runtime "deno")}}
|
||||
import '@std/dotenv/load'
|
||||
|
||||
const API_ID = Number.parseInt(Deno.env.get('API_ID')!)
|
||||
const API_HASH = Deno.env.get('API_HASH')!
|
||||
{{#if botToken}}
|
||||
const BOT_TOKEN = Deno.env.get('BOT_TOKEN')!
|
||||
{{/if}}
|
||||
{{else}}
|
||||
import process from 'node:process'
|
||||
|
||||
const API_ID = Number.parseInt(process.env.API_ID!)
|
||||
const API_HASH = process.env.API_HASH!
|
||||
{{#if botToken}}
|
||||
const BOT_TOKEN = process.env.BOT_TOKEN!
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
if (isNaN(API_ID) || !API_HASH) {
|
||||
if (Number.isNaN(API_ID) || !API_HASH) {
|
||||
throw new Error('API_ID or API_HASH not set!')
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
{{emit_if (and (not features.typescript) features.i18n)}}
|
||||
|
||||
import { createMtcuteI18n } from '@mtcute/i18n'
|
||||
|
||||
{{#if (eq runtime "node")}}
|
||||
import { en } from './en.js'
|
||||
import { ru } from './ru.js'
|
||||
{{else}}
|
||||
import { en } from './en.ts'
|
||||
import { ru } from './ru.ts'
|
||||
{{/if}}
|
||||
|
||||
export const tr = createMtcuteI18n({
|
||||
primaryLanguage: {
|
||||
|
|
|
@ -2,8 +2,10 @@
|
|||
{{#if features.dispatcher}}
|
||||
import { Dispatcher, filters } from '@mtcute/dispatcher'
|
||||
{{/if}}
|
||||
{{#if (eq packageManager "bun")}}
|
||||
{{#if (eq runtime "bun")}}
|
||||
import { TelegramClient } from '@mtcute/bun'
|
||||
{{else if (eq runtime "deno")}}
|
||||
import { TelegramClient } from '@mtcute/deno'
|
||||
{{else}}
|
||||
import { TelegramClient } from '@mtcute/node'
|
||||
{{/if}}
|
||||
|
@ -31,11 +33,9 @@ dp.onNewMessage(filters.start, async (msg) => {
|
|||
})
|
||||
{{/if}}
|
||||
|
||||
tg.run(
|
||||
{{#if botToken}}
|
||||
{ botToken: env.BOT_TOKEN },
|
||||
{{/if}}
|
||||
(user) => {
|
||||
console.log('Logged in as', user.username)
|
||||
},
|
||||
)
|
||||
{{#if botToken}}
|
||||
const user = await tg.start({ botToken: env.BOT_TOKEN })
|
||||
{{else}}
|
||||
const user = await tg.start()
|
||||
{{/if}}
|
||||
console.log('Logged in as', user.username)
|
|
@ -2,15 +2,25 @@
|
|||
{{#if features.dispatcher}}
|
||||
import { Dispatcher, filters } from '@mtcute/dispatcher'
|
||||
{{/if}}
|
||||
{{#if (eq packageManager "bun")}}
|
||||
{{#if (eq runtime "bun")}}
|
||||
import { TelegramClient } from '@mtcute/bun'
|
||||
{{else if (eq runtime "deno")}}
|
||||
import { TelegramClient } from '@mtcute/deno'
|
||||
{{else}}
|
||||
import { TelegramClient } from '@mtcute/node'
|
||||
{{/if}}
|
||||
|
||||
{{#if (eq runtime "node")}}
|
||||
import * as env from './env.js'
|
||||
{{else}}
|
||||
import * as env from './env.ts'
|
||||
{{/if}}
|
||||
{{#if features.i18n}}
|
||||
{{#if (eq runtime "node")}}
|
||||
import { tr } from './i18n/index.js'
|
||||
{{else}}
|
||||
import { tr } from './i18n/index.ts'
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
const tg = new TelegramClient({
|
||||
|
@ -31,11 +41,9 @@ dp.onNewMessage(filters.start, async (msg) => {
|
|||
})
|
||||
{{/if}}
|
||||
|
||||
tg.run(
|
||||
{{#if botToken}}
|
||||
{ botToken: env.BOT_TOKEN },
|
||||
{{/if}}
|
||||
(user) => {
|
||||
console.log('Logged in as', user.username)
|
||||
},
|
||||
)
|
||||
{{#if botToken}}
|
||||
const user = await tg.start({ botToken: env.BOT_TOKEN })
|
||||
{{else}}
|
||||
const user = await tg.start()
|
||||
{{/if}}
|
||||
console.log('Logged in as', user.username)
|
|
@ -1,28 +1,29 @@
|
|||
{{emit_if features.typescript}}
|
||||
{{emit_if (and features.typescript (ne runtime "deno"))}}
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
{{#if (eq runtime "bun")}}
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"noEmit": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
{{else}}
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"target": "es2020",
|
||||
{{/if}}
|
||||
"target": "es2022",
|
||||
"allowJs": true,
|
||||
"sourceMap": true,
|
||||
"inlineSources": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitThis": true,
|
||||
"incremental": true,
|
||||
"skipLibCheck": true,
|
||||
},
|
||||
"include": [
|
||||
"./src"
|
||||
],
|
||||
"ts-node": {
|
||||
"esm": true,
|
||||
"experimentalSpecifierResolution": "node"
|
||||
},
|
||||
"exclude": [
|
||||
"**/node_modules",
|
||||
]
|
||||
|
|
Loading…
Reference in a new issue