From 64da48926fe18de3d7f02a28d42cc8801fe3bc0c Mon Sep 17 00:00:00 2001 From: alina sireneva Date: Mon, 24 Jun 2024 00:20:10 +0300 Subject: [PATCH] feat(create-bot): various improvements - improved bun support - deno support - use antfu/eslint-config - fixed some issues - removed pre-commit hooks --- packages/create-bot/README.md | 14 +- packages/create-bot/package.json | 3 +- packages/create-bot/scripts/.gitignore | 1 + .../create-bot/scripts/generate-import-map.js | 11 + packages/create-bot/src/config.ts | 7 +- packages/create-bot/src/dependencies.ts | 72 ++++-- packages/create-bot/src/features/cli.ts | 17 +- packages/create-bot/src/features/types.ts | 1 + packages/create-bot/src/jsr.ts | 25 +++ packages/create-bot/src/main.ts | 41 ++-- packages/create-bot/src/package-manager.ts | 55 ++++- packages/create-bot/src/templater.ts | 3 + packages/create-bot/src/utils.ts | 2 +- .../create-bot/template/.eslintignore.hbs | 8 - .../create-bot/template/.eslintrc.cjs.hbs | 210 ------------------ packages/create-bot/template/.gitignore.hbs | 3 +- .../create-bot/template/.husky/pre-commit.hbs | 5 - .../create-bot/template/.lintstagedrc.cjs.hbs | 7 - packages/create-bot/template/.prettierrc.hbs | 20 -- packages/create-bot/template/Dockerfile.hbs | 2 +- packages/create-bot/template/README.md.hbs | 16 +- .../create-bot/template/eslint.config.js.hbs | 21 ++ packages/create-bot/template/package.json.hbs | 18 +- packages/create-bot/template/src/env.js.hbs | 24 ++ packages/create-bot/template/src/env.ts.hbs | 16 +- .../create-bot/template/src/i18n/index.js.hbs | 6 + .../src/{index.js.hbs => main.js.hbs} | 18 +- .../src/{index.ts.hbs => main.ts.hbs} | 26 ++- .../create-bot/template/tsconfig.json.hbs | 17 +- 29 files changed, 324 insertions(+), 345 deletions(-) create mode 100644 packages/create-bot/scripts/.gitignore create mode 100644 packages/create-bot/scripts/generate-import-map.js create mode 100644 packages/create-bot/src/jsr.ts delete mode 100644 packages/create-bot/template/.eslintignore.hbs delete mode 100644 packages/create-bot/template/.eslintrc.cjs.hbs delete mode 100644 packages/create-bot/template/.husky/pre-commit.hbs delete mode 100644 packages/create-bot/template/.lintstagedrc.cjs.hbs delete mode 100644 packages/create-bot/template/.prettierrc.hbs create mode 100644 packages/create-bot/template/eslint.config.js.hbs create mode 100644 packages/create-bot/template/src/env.js.hbs rename packages/create-bot/template/src/{index.js.hbs => main.js.hbs} (72%) rename packages/create-bot/template/src/{index.ts.hbs => main.ts.hbs} (62%) diff --git a/packages/create-bot/README.md b/packages/create-bot/README.md index 4c7e3440..6747df24 100644 --- a/packages/create-bot/README.md +++ b/packages/create-bot/README.md @@ -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 diff --git a/packages/create-bot/package.json b/packages/create-bot/package.json index d73e845b..e2c105ef 100644 --- a/packages/create-bot/package.json +++ b/packages/create-bot/package.json @@ -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" diff --git a/packages/create-bot/scripts/.gitignore b/packages/create-bot/scripts/.gitignore new file mode 100644 index 00000000..482d98f7 --- /dev/null +++ b/packages/create-bot/scripts/.gitignore @@ -0,0 +1 @@ +import-map.json \ No newline at end of file diff --git a/packages/create-bot/scripts/generate-import-map.js b/packages/create-bot/scripts/generate-import-map.js new file mode 100644 index 00000000..888ffe95 --- /dev/null +++ b/packages/create-bot/scripts/generate-import-map.js @@ -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)) diff --git a/packages/create-bot/src/config.ts b/packages/create-bot/src/config.ts index 8fc289e8..552b413d 100644 --- a/packages/create-bot/src/config.ts +++ b/packages/create-bot/src/config.ts @@ -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 diff --git a/packages/create-bot/src/dependencies.ts b/packages/create-bot/src/dependencies.ts index e44e77e9..bff36172 100644 --- a/packages/create-bot/src/dependencies.ts +++ b/packages/create-bot/src/dependencies.ts @@ -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, + 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 })) } diff --git a/packages/create-bot/src/features/cli.ts b/packages/create-bot/src/features/cli.ts index f079458c..9f4058f3 100644 --- a/packages/create-bot/src/features/cli.ts +++ b/packages/create-bot/src/features/cli.ts @@ -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', diff --git a/packages/create-bot/src/features/types.ts b/packages/create-bot/src/features/types.ts index 71e8f419..294cb6fb 100644 --- a/packages/create-bot/src/features/types.ts +++ b/packages/create-bot/src/features/types.ts @@ -5,4 +5,5 @@ export enum MtcuteFeature { Docker = 'docker', TypeScript = 'typescript', Linters = 'linters', + Git = 'git', } diff --git a/packages/create-bot/src/jsr.ts b/packages/create-bot/src/jsr.ts new file mode 100644 index 00000000..b535de7b --- /dev/null +++ b/packages/create-bot/src/jsr.ts @@ -0,0 +1,25 @@ +export async function fetchLatestVersionJsr(pkg: string): Promise { + 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> { + const res = new Map() + + await Promise.all( + pkgs.map(async (pkg) => { + res.set(pkg, await fetchLatestVersionJsr(pkg)) + }), + ) + + return res +} diff --git a/packages/create-bot/src/main.ts b/packages/create-bot/src/main.ts index 4cbaf945..9956d274 100644 --- a/packages/create-bot/src/main.ts +++ b/packages/create-bot/src/main.ts @@ -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`) +} diff --git a/packages/create-bot/src/package-manager.ts b/packages/create-bot/src/package-manager.ts index 5dbd0245..f5419c9b 100644 --- a/packages/create-bot/src/package-manager.ts +++ b/packages/create-bot/src/package-manager.ts @@ -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' } } diff --git a/packages/create-bot/src/templater.ts b/packages/create-bot/src/templater.ts index 75d26a28..16bb508c 100644 --- a/packages/create-bot/src/templater.ts +++ b/packages/create-bot/src/templater.ts @@ -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>( (acc, f) => { acc[f] = true diff --git a/packages/create-bot/src/utils.ts b/packages/create-bot/src/utils.ts index 90a31419..d4bb7d9b 100644 --- a/packages/create-bot/src/utils.ts +++ b/packages/create-bot/src/utils.ts @@ -3,7 +3,7 @@ import { spawn } from 'cross-spawn' export function exec(cwd: string, ...cmd: string[]) { return new Promise((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', diff --git a/packages/create-bot/template/.eslintignore.hbs b/packages/create-bot/template/.eslintignore.hbs deleted file mode 100644 index 0e95a5b3..00000000 --- a/packages/create-bot/template/.eslintignore.hbs +++ /dev/null @@ -1,8 +0,0 @@ -{{emit_if features.linters}} -node_modules/ -.husky/ -.idea/ - -dist/ - -*.json diff --git a/packages/create-bot/template/.eslintrc.cjs.hbs b/packages/create-bot/template/.eslintrc.cjs.hbs deleted file mode 100644 index 95e5294f..00000000 --- a/packages/create-bot/template/.eslintrc.cjs.hbs +++ /dev/null @@ -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, - }, - }, -} diff --git a/packages/create-bot/template/.gitignore.hbs b/packages/create-bot/template/.gitignore.hbs index 89a05989..4ea5015f 100644 --- a/packages/create-bot/template/.gitignore.hbs +++ b/packages/create-bot/template/.gitignore.hbs @@ -1,6 +1,7 @@ +{{#if (ne runtime "deno")}} node_modules/ +{{/if}} dist/ -private/ .nyc_output/ **/.DS_Store .idea diff --git a/packages/create-bot/template/.husky/pre-commit.hbs b/packages/create-bot/template/.husky/pre-commit.hbs deleted file mode 100644 index 9fd01df4..00000000 --- a/packages/create-bot/template/.husky/pre-commit.hbs +++ /dev/null @@ -1,5 +0,0 @@ -{{emit_if features.linters}} -#!/usr/bin/env sh -. "$(dirname -- "$0")/_/husky.sh" - -pnpm exec lint-staged \ No newline at end of file diff --git a/packages/create-bot/template/.lintstagedrc.cjs.hbs b/packages/create-bot/template/.lintstagedrc.cjs.hbs deleted file mode 100644 index e21e3dd7..00000000 --- a/packages/create-bot/template/.lintstagedrc.cjs.hbs +++ /dev/null @@ -1,7 +0,0 @@ -{{emit_if features.linters}} -module.exports = { - '*.{js,jsx,ts,tsx}': [ - `prettier --write`, - `eslint --fix`, - ] -} diff --git a/packages/create-bot/template/.prettierrc.hbs b/packages/create-bot/template/.prettierrc.hbs deleted file mode 100644 index 1395d786..00000000 --- a/packages/create-bot/template/.prettierrc.hbs +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/packages/create-bot/template/Dockerfile.hbs b/packages/create-bot/template/Dockerfile.hbs index 7ca6a174..1c572a16 100644 --- a/packages/create-bot/template/Dockerfile.hbs +++ b/packages/create-bot/template/Dockerfile.hbs @@ -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" ] diff --git a/packages/create-bot/template/README.md.hbs b/packages/create-bot/template/README.md.hbs index d0883325..d0acdc5e 100644 --- a/packages/create-bot/template/README.md.hbs +++ b/packages/create-bot/template/README.md.hbs @@ -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* \ No newline at end of file diff --git a/packages/create-bot/template/eslint.config.js.hbs b/packages/create-bot/template/eslint.config.js.hbs new file mode 100644 index 00000000..39d64d8a --- /dev/null +++ b/packages/create-bot/template/eslint.config.js.hbs @@ -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', + }, +}) diff --git a/packages/create-bot/template/package.json.hbs b/packages/create-bot/template/package.json.hbs index f3684ab3..5576e2c5 100644 --- a/packages/create-bot/template/package.json.hbs +++ b/packages/create-bot/template/package.json.hbs @@ -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}} } diff --git a/packages/create-bot/template/src/env.js.hbs b/packages/create-bot/template/src/env.js.hbs new file mode 100644 index 00000000..510d9d29 --- /dev/null +++ b/packages/create-bot/template/src/env.js.hbs @@ -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}} } diff --git a/packages/create-bot/template/src/env.ts.hbs b/packages/create-bot/template/src/env.ts.hbs index addfad52..f9264dab 100644 --- a/packages/create-bot/template/src/env.ts.hbs +++ b/packages/create-bot/template/src/env.ts.hbs @@ -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!') } diff --git a/packages/create-bot/template/src/i18n/index.js.hbs b/packages/create-bot/template/src/i18n/index.js.hbs index 1bb46608..0391db9b 100644 --- a/packages/create-bot/template/src/i18n/index.js.hbs +++ b/packages/create-bot/template/src/i18n/index.js.hbs @@ -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: { diff --git a/packages/create-bot/template/src/index.js.hbs b/packages/create-bot/template/src/main.js.hbs similarity index 72% rename from packages/create-bot/template/src/index.js.hbs rename to packages/create-bot/template/src/main.js.hbs index 629c923c..37dc7d31 100644 --- a/packages/create-bot/template/src/index.js.hbs +++ b/packages/create-bot/template/src/main.js.hbs @@ -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) - }, -) \ No newline at end of file +{{#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) diff --git a/packages/create-bot/template/src/index.ts.hbs b/packages/create-bot/template/src/main.ts.hbs similarity index 62% rename from packages/create-bot/template/src/index.ts.hbs rename to packages/create-bot/template/src/main.ts.hbs index db1cf374..ca59ff81 100644 --- a/packages/create-bot/template/src/index.ts.hbs +++ b/packages/create-bot/template/src/main.ts.hbs @@ -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) diff --git a/packages/create-bot/template/tsconfig.json.hbs b/packages/create-bot/template/tsconfig.json.hbs index ff21c4a9..5660d503 100644 --- a/packages/create-bot/template/tsconfig.json.hbs +++ b/packages/create-bot/template/tsconfig.json.hbs @@ -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", ]