32 Commits

Author SHA1 Message Date
github-actions[bot]
9f9a2aaca7 chore(main): release 1.2.1 (#132)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-02-07 22:09:32 -06:00
Jacob Nguyen
d1832e44ce fix: better error messages for publish 2024-02-07 22:04:05 -06:00
Jacob Nguyen
e55ceedadd refactor: error json inspecter 2024-02-07 21:49:09 -06:00
github-actions[bot]
6bb212a3be chore(main): release 1.2.0 (#131)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-01-29 00:38:02 -06:00
Jacob Nguyen
2d6e65a1e6 feat: make application id optional, thanks @trueharuu (#130)
make application id optional, thanks @trueharuu
2024-01-29 09:56:24 +05:30
github-actions[bot]
eb53ecb638 chore(main): release 1.1.0 (#129)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-01-27 23:50:17 -06:00
Jacob Nguyen
303ac0280c feat: command clear (#128) 2024-01-28 10:59:37 +05:30
github-actions[bot]
3f994d6948 chore(main): release 1.0.3 (#127)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-12-25 19:26:01 -06:00
Jacob Nguyen
a5cb66828e fix: intellisense for esm build ts (#126)
* fix: broken link and refactor
2023-12-25 11:47:46 -06:00
github-actions[bot]
667d0c1b89 chore(main): release 1.0.2 (#125)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-11-04 16:20:29 -05:00
Jacob Nguyen
5dbf2a87dc fix: better error handling 2023-11-04 16:14:00 -05:00
a309c085e9 chore: build docs rephrasing (#118)
Co-authored-by: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com>
2023-10-18 13:39:58 -05:00
f1d7d6c911 fix: build mkdir errors (#122)
Co-authored-by: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com>
2023-10-18 13:27:58 -05:00
Jacob Nguyen
4ec96dbe17 Update continuous-integration.yml 2023-10-18 13:18:29 -05:00
Peter-MJ-Parker
3970cc6911 chore: fix grammar in error message (#123) 2023-10-13 22:53:19 +05:30
github-actions[bot]
9045e1a03f chore(main): release 1.0.1 (#120)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-10-05 12:21:23 +05:30
Jacob Nguyen
d9ca5ff3ff fix: multiple bugs (#119) 2023-10-05 12:18:08 +05:30
github-actions[bot]
5a68916479 chore(main): release 1.0.0 (#116)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-09-06 10:56:59 +05:30
EvolutionX
e8c4764a9e style: pretty pretty 2023-09-05 09:02:18 +05:30
Jacob Nguyen
c785d49fa5 feat(plugins)!: new method to obtain plugins (#114)
BREAKING CHANGE: older versions of cli does not have plugins command functional
2023-09-05 08:58:08 +05:30
40829267c4 fix(extra): dockerfile errors and tsc fallback (#101) 2023-09-01 22:05:21 +05:30
renovate[bot]
1fc69b0a6e chore(deps): lock file maintenance (#104)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-01 10:05:31 +05:30
renovate[bot]
d01e4cb2f1 chore(deps): update all non-major dependencies (#107)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-01 09:59:07 +05:30
Evo
5fd2a7b4c4 feat: list subcommand (#113)
* chore: progress

* chore: blaming regex.

permalink: http://whatthecommit.com/7eaa73b94ca6e8f964d99b6f2db6e9e4

* chore:

permalink:

* chore: lol

* chore: accidental commit

permalink: http://whatthecommit.com/7c6c9323d8c243d10cd93c8bbbc55d09

* fix syntx

* fix list not showing up

* prety

* chore: refresh lockfile

* chore: progress

* fix list not showing up

* refactor: cleanup some mess

* Update preprocessor.ts

---------

Co-authored-by: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com>
2023-08-31 15:07:16 -05:00
EvolutionX
42bd528756 chore: for the statistics only
permalink: http://whatthecommit.com/f6cb5f952917639c9e28f94ae495bd59
2023-08-31 10:47:45 +05:30
EvolutionX
c218065a04 chore: refresh lockfile 2023-08-31 10:45:34 +05:30
Jacob Nguyen
2ece63cb8b fix preprocessor.ts dependencies intellisense 2023-08-28 10:57:19 -05:00
Jacob Nguyen
88e2bbf6c8 feat: build app (#112)
* chore: start publish work

* chore: more debug... who overwrote!

permalink: http://whatthecommit.com/2e6bbd4fd1a21e16039ce52216c3c0b4

* update node version requirement (#106)

* update node version requirement

* publish progress

* progress on publish

* style: pretty pretty

* chore: some progress i guess?

* more progress on publish command

* chore: remove magicast

* more progress on command data

* fix: adding extra fields to json output

* fix: stringify again

* rest field cli

* style: run prettier

* chore: friday 5pm

permalink: http://whatthecommit.com/502256b954264d50f29967e1fef8394b

* prettier and more progress on publish

* progress on publishing

* config can be function and publishing seems to be working correctly

* fix, made mistakes

* fix guild command publishing

* fix guild ids not publishing correctly

* edit correctly

* upgrade fire readiers

* refactor and separate some stuff

* ^

* add default_member_permissions

* refator

* refactors

* publish functionality done

* seems like attachment loading works correctly

* extrapolate

* code splitting and faster startup

* fix up bugs

* forgot to merge prompts/plugin

* remove unneeded esbuild plugin for simple define

* build auto generate typings

* fix: template dir after switching commands to dynamic imports

* update preprocessor

* fix: add experimental warning to publish and build

* fix vulnerability

* oops, false alarm

* better sern.build.js config support

* chore: remove pnpm lock

* cleaner build and also some cli options

* fix regression

* add more cli options

* optimize

* no any

---------

Co-authored-by: EvolutionX <evolutionx9777@gmail.com>
2023-08-28 09:46:29 -05:00
EvolutionX
913ff4dc15 docs(readme): update some outdated info 2023-08-17 13:20:06 +05:30
EvolutionX
30179d1ea9 docs(readme): update to latest help 2023-08-17 13:18:35 +05:30
Jacob Nguyen
3f74658b16 fix: credentials precedence (#111)
Co-authored-by: Evo <85353424+EvolutionX-10@users.noreply.github.com>
2023-08-10 18:23:18 +00:00
EvolutionX
0a3fedd1d8 chore: git stash changelog.md license.md readme.md dist node_modules package-lock.json package.json renovate.json src templates tsconfig.json tsup.config.ts
permalink: http://whatthecommit.com/7ad7c71289d30791e2f20c54465142aa
2023-08-10 11:14:55 +05:30
29 changed files with 1823 additions and 2646 deletions

View File

@@ -11,25 +11,25 @@ on:
- main
jobs:
Lint:
name: Linting
runs-on: ubuntu-latest
# Lint:
# name: Linting
# runs-on: ubuntu-latest
steps:
- name: Check out Git repository
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
# steps:
# - name: Check out Git repository
# uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
- name: Set up Node.js
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3
with:
node-version: 17
# - name: Set up Node.js
# uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3
# with:
# node-version: 17
# Prettier must be in `package.json`
- name: Install Node.js dependencies
run: npm i
# - name: Install Node.js dependencies
# run: npm i
- name: Run Prettier
run: npm run format
# - name: Run Prettier
# run: npm run format
Test:
name: Testing

View File

@@ -2,6 +2,68 @@
All notable changes to this project will be documented in this file.
## [1.2.1](https://github.com/sern-handler/cli/compare/v1.2.0...v1.2.1) (2024-02-08)
### Bug Fixes
* better error messages for publish ([d1832e4](https://github.com/sern-handler/cli/commit/d1832e44ce1b10aeb5b9dc902b7d35ab51c41ff3))
## [1.2.0](https://github.com/sern-handler/cli/compare/v1.1.0...v1.2.0) (2024-01-29)
### Features
* make application id optional, thanks [@trueharuu](https://github.com/trueharuu) ([#130](https://github.com/sern-handler/cli/issues/130)) ([2d6e65a](https://github.com/sern-handler/cli/commit/2d6e65a1e6073f605aab192b8cea33037a04af2c))
## [1.1.0](https://github.com/sern-handler/cli/compare/v1.0.3...v1.1.0) (2024-01-28)
### Features
* command clear ([#128](https://github.com/sern-handler/cli/issues/128)) ([303ac02](https://github.com/sern-handler/cli/commit/303ac0280c7c7c55f2670d49c9685b911670bc05))
## [1.0.3](https://github.com/sern-handler/cli/compare/v1.0.2...v1.0.3) (2023-12-25)
### Bug Fixes
* intellisense for esm build ts ([#126](https://github.com/sern-handler/cli/issues/126)) ([a5cb668](https://github.com/sern-handler/cli/commit/a5cb66828eae47d3eac5b86c7e67c01dbed500d5))
## [1.0.2](https://github.com/sern-handler/cli/compare/v1.0.1...v1.0.2) (2023-11-04)
### Bug Fixes
* better error handling ([5dbf2a8](https://github.com/sern-handler/cli/commit/5dbf2a87dcb0001993fe126d9002bc82c8108e24))
* build mkdir errors ([#122](https://github.com/sern-handler/cli/issues/122)) ([f1d7d6c](https://github.com/sern-handler/cli/commit/f1d7d6c911bc54ed3ca3e39eefbc7de6ee33b10d))
## [1.0.1](https://github.com/sern-handler/cli/compare/v1.0.0...v1.0.1) (2023-10-05)
### Bug Fixes
* multiple bugs ([#119](https://github.com/sern-handler/cli/issues/119)) ([d9ca5ff](https://github.com/sern-handler/cli/commit/d9ca5ff3ff2f304a6d75f2b7a296bd900431d41f))
## [1.0.0](https://github.com/sern-handler/cli/compare/v0.6.0...v1.0.0) (2023-09-05)
### ⚠ BREAKING CHANGES
* **plugins:** older versions of cli does not have plugins command functional
### Features
* build app ([#112](https://github.com/sern-handler/cli/issues/112)) ([88e2bbf](https://github.com/sern-handler/cli/commit/88e2bbf6c84e3841370a4288181e8ec721ef1925))
* list subcommand ([#113](https://github.com/sern-handler/cli/issues/113)) ([5fd2a7b](https://github.com/sern-handler/cli/commit/5fd2a7b4c4bc92467fbaa26005d261d4ed8b2a13))
* **plugins:** new method to obtain plugins ([#114](https://github.com/sern-handler/cli/issues/114)) ([c785d49](https://github.com/sern-handler/cli/commit/c785d49fa5c0d98261de7d7b0c39f85c21316156))
### Bug Fixes
* credentials precedence ([#111](https://github.com/sern-handler/cli/issues/111)) ([3f74658](https://github.com/sern-handler/cli/commit/3f74658b16b2697df11c7e33172c09c30b7543a8))
* **extra:** dockerfile errors and tsc fallback ([#101](https://github.com/sern-handler/cli/issues/101)) ([4082926](https://github.com/sern-handler/cli/commit/40829267c4e77b316a60604c63bad79124713b89))
## [0.6.0](https://github.com/sern-handler/cli/compare/v0.5.1...v0.6.0) (2023-08-09)

View File

@@ -30,29 +30,34 @@ When you install the CLI, you can use our commands with **sern** prefix.
```
Usage: sern [options] [command]
Welcome to sern!
If you're new to sern, run sern init for an interactive setup to your new bot project!
If you have any ideas, suggestions, bug reports, kindly join our support server: https://discord.gg/xzK5fUKT4r
___ ___ _ __ _ __
/ __|/ _ \ '__| '_ \
\__ \ __/ | | | | |
|___/\___|_| |_| |_|
Welcome!
If you're new to sern, run npm create @sern/bot for an interactive setup to your new bot project!
If you have any ideas, suggestions, bug reports, kindly join our support server: https://sern.dev/discord
Options:
-V, --version output the version number
-v, --version output the version number
-h, --help display help for command
Commands:
init [options] Quickest way to scaffold a new project
init [options] Quickest way to scaffold a new project [DEPRECATED]
plugins [options] Install plugins from https://github.com/sern-handler/awesome-plugins
extra Easy way to add extra things in your sern project
commands Defacto way to manage your slash commands
help [command] display help for command
```
## Setting Up Your Project
Run `sern init (-y)` for an interactive setup on a brand new project using our framework. <br>
Adding the `-y` flag sets up project as default. ( **Note** : the default initiates a typescript project)
Run `npm create @sern/bot` for an interactive setup on a brand new project using our framework.
## Installing Plugins
sern runs on your plugins. Contribute to the [repository](https://github.com/sern-handler/awesome-plugins) and then install the plugins via our cli! <br>
Run `sern plugins` to see all installable options
sern runs on your plugins. Contribute to our [repository](https://github.com/sern-handler/awesome-plugins) and then install the plugins via our cli! <br>
Run `sern plugins` to see all installable plugins.

3503
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@sern/cli",
"version": "0.6.0",
"version": "1.2.1",
"description": "Official CLI for @sern/handler",
"exports": "./dist/index.js",
"bin": {
@@ -37,20 +37,21 @@
"colorette": "2.0.20",
"commander": "11.0.0",
"dotenv": "^16.3.1",
"execa": "7.1.1",
"esbuild": "^0.19.1",
"execa": "7.2.0",
"find-up": "6.3.0",
"glob": "^10.3.3",
"ora": "6.3.1",
"prompts": "2.4.2",
"undici": "5.22.1"
"undici": "5.23.0"
},
"devDependencies": {
"@babel/parser": "^7.22.5",
"@favware/npm-deprecate": "1.0.7",
"@types/prompts": "2.4.4",
"esbuild-plugin-version-injector": "1.1.0",
"prettier": "2.8.8",
"tsup": "6.7.0",
"typescript": "5.1.3"
"tsup": "^6.7.0",
"typescript": "5.2.2"
},
"engines": {
"node": ">= 18.16.x"

168
src/commands/build.ts Normal file
View File

@@ -0,0 +1,168 @@
import esbuild from 'esbuild';
import { getConfig } from '../utilities/getConfig';
import { resolve } from 'node:path';
import { glob } from 'glob';
import { configDotenv } from 'dotenv';
import assert from 'node:assert';
import { imageLoader, validExtensions } from '../plugins/imageLoader';
import defaultEsbuild from '../utilities/defaultEsbuildConfig';
import { require } from '../utilities/require';
import { pathExists, pathExistsSync } from 'find-up';
import { mkdir, writeFile } from 'fs/promises';
import * as Preprocessor from '../utilities/preprocessor';
import { bold, magentaBright } from 'colorette';
type BuildOptions = {
/**
* Define __VERSION__
* This option is a quick switch to defining the __VERSION__ constant which will be a string of the version provided in
* cwd's package.json
*/
defineVersion?: boolean;
/**
* default = esm
*/
format?: 'cjs' | 'esm';
/**
* extra esbuild plugins to build with sern.
*/
esbuildPlugins?: esbuild.Plugin[];
/**
* https://esbuild.github.io/api/#drop-labels
**/
dropLabels?: string[];
/**
* https://esbuild.github.io/api/#define
**/
define?: Record<string, string>;
tsconfig?: string;
/**
* default = 'development'
*/
mode: 'production' | 'development';
/**
* will search for env file. If none exists,
* default to .env.
*/
env?: string;
};
export async function build(options: Record<string, any>) {
if (!options.supressWarnings) {
console.info(`${magentaBright('EXPERIMENTAL')}: This API has not been stabilized. add -W or --suppress-warnings flag to suppress`);
}
const sernConfig = await getConfig();
let buildConfig: Partial<BuildOptions> = {};
const entryPoints = await glob(`./src/**/*{${validExtensions.join(',')}}`, {
//for some reason, my ignore glob wasn't registering correctly'
ignore: {
ignored: (p) => p.name.endsWith('.d.ts'),
},
});
const buildConfigPath = resolve(options.project ?? 'sern.build.js');
const resolveBuildConfig = (path: string|undefined, language: string) => {
if(language === 'javascript') {
return path ?? resolve('jsconfig.json')
}
return path ?? resolve('tsconfig.json')
}
const defaultBuildConfig = {
defineVersion: true,
format: options.format ?? 'esm',
mode: options.mode ?? 'development',
dropLabels: [],
tsconfig: resolveBuildConfig(options.tsconfig, sernConfig.language),
env: options.env ?? resolve('.env'),
};
if (pathExistsSync(buildConfigPath)) {
//throwable, buildConfigPath may not exist
buildConfig = {
...defaultBuildConfig,
...(await import('file:///' + buildConfigPath)).default,
};
} else {
buildConfig = defaultBuildConfig;
console.log('No build config found, defaulting');
}
let env = {} as Record<string, string>;
configDotenv({ path: buildConfig.env, processEnv: env });
const modeButNotNodeEnvExists = env.MODE && !env.NODE_ENV;
if (modeButNotNodeEnvExists) {
console.warn('Use NODE_ENV instead of MODE');
console.warn('MODE has no effect.');
console.warn(`https://nodejs.org/en/learn/getting-started/nodejs-the-difference-between-development-and-production`);
}
if (env.NODE_ENV) {
buildConfig.mode = env.NODE_ENV as 'production' | 'development';
console.log(magentaBright('NODE_ENV:'), 'Found NODE_ENV variable, setting `mode` to this.');
}
assert(buildConfig.mode === 'development' || buildConfig.mode === 'production', 'Mode is not `production` or `development`');
try {
let config = require(buildConfig.tsconfig!);
config.extends && console.warn("Extend the generated tsconfig")
} catch(e) {
console.warn("no tsconfig / jsconfig found");
console.warn(`Please create a ${sernConfig.language === 'javascript' ? 'jsconfig.json' : 'tsconfig.json' }`);
console.warn("It should have at least extend the generated one sern makes.")
console.warn(`
{
"extends": "./.sern/tsconfig.json",
}`.trim())
throw e;
}
console.log(bold('Building with:'));
console.log(' ', magentaBright('defineVersion'), buildConfig.defineVersion);
console.log(' ', magentaBright('format'), buildConfig.format);
console.log(' ', magentaBright('mode'), buildConfig.mode);
console.log(' ', magentaBright('tsconfig'), buildConfig.tsconfig);
console.log(' ', magentaBright('env'), buildConfig.env);
const sernDir = resolve('.sern'),
genDir = resolve(sernDir, 'generated'),
ambientFilePath = resolve(sernDir, 'ambient.d.ts'),
packageJsonPath = resolve('package.json'),
sernTsConfigPath = resolve(sernDir, 'tsconfig.json'),
packageJson = () => require(packageJsonPath);
if (!(await pathExists(genDir))) {
console.log('Making .sern/generated dir, does not exist');
await mkdir(genDir, { recursive: true });
}
try {
const defVersion = () => JSON.stringify(packageJson().version);
const define = {
...(buildConfig.define ?? {}),
__DEV__: `${buildConfig.mode === 'development'}`,
__PROD__: `${buildConfig.mode === 'production'}`,
} satisfies Record<string, string>;
buildConfig.defineVersion && Object.assign(define, { __VERSION__: defVersion() });
await Preprocessor.writeTsConfig(buildConfig.format!, sernTsConfigPath, writeFile);
await Preprocessor.writeAmbientFile(ambientFilePath, define, writeFile);
//https://esbuild.github.io/content-types/#tsconfig-json
await esbuild.build({
entryPoints,
plugins: [imageLoader, ...(buildConfig.esbuildPlugins ?? [])],
...defaultEsbuild(buildConfig.format!, buildConfig.tsconfig),
define,
dropLabels: [buildConfig.mode === 'production' ? '__DEV__' : '__PROD__', ...buildConfig.dropLabels!],
});
} catch (e) {
console.error(e);
process.exit(1);
}
}

View File

@@ -0,0 +1,60 @@
import * as Rest from '../rest.js'
import assert from 'node:assert'
import dotenv from 'dotenv'
import ora from 'ora';
import type { CommandData, GuildId } from '../utilities/types.js';
import { readFileSync, writeFile } from 'node:fs'
import { resolve } from 'node:path'
import prompts from 'prompts';
const getConfirmation = (args: Record<string,any> ) => {
if(args.yes) {
return args.yes
} else {
return prompts({
type: 'confirm',
name: 'confirmation',
message: 'Are you sure you want to delete ALL your application commands?',
initial: true
}, { onCancel: () => (console.log("Cancelled operation ( ̄┰ ̄*)"), process.exit(1)) })
.then(response => response.confirmation);
}
}
export async function commandClear(args: Record<string,any>) {
dotenv.configDotenv({ path: args.env || resolve('.env') })
const token = process.env.token || process.env.DISCORD_TOKEN;
assert(token, 'Could not find a token for this bot in .env or commandline. Do you have DISCORD_TOKEN in env?');
const confirmation = await getConfirmation(args);
if (confirmation) {
const spin = ora({
text: `Deleting ALL application commands...`,
spinner: 'aesthetic',
}).start();
const rest = await Rest.create(token);
let guildCommands: Record<GuildId, CommandData[]>
try {
guildCommands = JSON.parse(readFileSync('.sern/command-data-remote.json', 'utf-8'))
await rest.updateGlobal([]);
delete guildCommands.global
for (const guildId in guildCommands) {
await rest.putGuildCommands(guildId, []);
}
writeFile('.sern/command-data-remote.json', "{}", (err) => {
if(err) {
spin.fail("Error happened while writing to json:");
console.error(err)
process.exit(1)
}
})
spin.succeed();
} catch(e) {
spin.fail("Something went wrong. ");
throw e;
}
} else {
console.log('Operation canceled. ( ̄┰ ̄*)');
}
}

View File

@@ -7,5 +7,6 @@ export async function extra() {
if (Object.keys(extra).length < 1) process.exit(1);
const lang = extra.extra.includes('typescript') ? 'TS' : 'JS';
await create(extra.extra.split('-')[0], lang, process.cwd(), true);
}

View File

@@ -7,6 +7,7 @@ export const help = `
|___/\\___|_| |_| |_|
Welcome!
If you're new to ${cyanBright('sern')}, run ${magentaBright('npm create @sern/bot')} for an interactive setup to your new bot project!
${green(`If you have any ideas, suggestions, bug reports, kindly join our support server: https://sern.dev/discord`)}`;

69
src/commands/list.ts Normal file
View File

@@ -0,0 +1,69 @@
import { blueBright, bold, cyanBright, greenBright, italic, magentaBright, underline } from 'colorette';
import { getSern } from '../utilities/getSern';
import { readFileSync } from 'node:fs';
import type { CommandData, GuildId } from '../utilities/types';
export function list() {
const files = getSern();
if (!files.includes('command-data-remote.json')) {
console.error(`No commands found\nPlease run ${cyanBright('sern commands publish')} to publish your commands`);
process.exit(1);
}
const commands: Record<GuildId, CommandData[]> = JSON.parse(readFileSync('.sern/command-data-remote.json', 'utf-8'));
const globalCommands = commands.global;
delete commands.global;
if(globalCommands) {
console.log(bold('Global Commands'));
for (const command of globalCommands) log(command);
}
console.log('\t');
for (const guildId in commands) {
const guildCommands = commands[guildId];
console.log(`${bold('Guild Commands')} [${underline(cyanBright(guildId))}]`);
for (const command of guildCommands) log(command);
}
}
const AppCommandsType: Record<number, string> = {
1: magentaBright('Slash'),
2: magentaBright('User'),
3: magentaBright('Message'),
};
const AppCommandOptionType: Record<number, string> = {
1: magentaBright('SubCommand'),
2: magentaBright('SubCommand Group'),
3: magentaBright('String'),
4: magentaBright('Integer'),
5: magentaBright('Boolean'),
6: magentaBright('User'),
7: magentaBright('Channel'),
8: magentaBright('Role'),
9: magentaBright('Mentionable'),
10: magentaBright('Number'),
11: magentaBright('Attachment'),
};
function log(command: CommandData) {
console.log(clean(`\t${cyanBright(command.name)} ${italic(command.description)} (${greenBright(command.id)})`));
console.log(`\t Type: ${AppCommandsType[command.type]}`);
if (command.options) {
console.log(`\t Options:`);
for (const option of command.options) {
console.log(`\t ${blueBright(option.name)}: ${AppCommandOptionType[option.type]}`);
if (option.options) {
console.log(`\t Options:`);
for (const subOption of option.options) {
console.log(`\t ${cyanBright(subOption.name)}: ${AppCommandOptionType[subOption.type]}`);
}
}
}
}
}
const clean = (str: string) => str.split(' ').filter(Boolean).join(' ');

View File

@@ -4,18 +4,59 @@ import prompt from 'prompts';
import { fetch } from 'undici';
import { pluginsQ } from '../prompts/plugin.js';
import { fromCwd } from '../utilities/fromCwd.js';
import esbuild from 'esbuild';
import { getLang } from '../utilities/getLang.js';
import { resolve } from 'path';
import { require } from '../utilities/require.js';
interface PluginData {
description: string;
hash: string;
name: string;
author: string[];
link: string;
example: string;
version: '1.0.0';
}
/**
* Installs plugins to project
*/
export async function plugins() {
const e: string[] = (await prompt([await pluginsQ()])).list;
const e: PluginData[] = (await prompt([await pluginsQ()])).list;
if (!e) process.exit(1);
for await (const url of e) {
await download(url);
const lang = await getLang();
for await (const plgData of e) {
const pluginText = await download(plgData.link);
const dir = fromCwd('/src/plugins');
const linkNoExtension = `${process.cwd()}/src/plugins/${plgData.name}`;
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
if (lang === 'typescript') {
fs.writeFileSync(linkNoExtension + '.ts', pluginText);
} else {
const { type = undefined } = require(resolve('package.json'));
const format = type === undefined || type === 'cjs' ? 'cjs' : 'esm';
const transformResult = await esbuild.transform(pluginText, {
target: 'node18',
format,
loader: 'ts',
banner: `/**\n Partial information: ${plgData.description}\n @author ${plgData.author}\n @example${plgData.example}*/`,
});
if (transformResult.warnings.length > 0) {
console.log(transformResult.warnings.map((msg) => msg.text).join('\n'));
}
console.warn('transforming plugins with js strips comments');
console.warn('We provided some minimal information at top of file, or view the documentation for this plugin here:');
console.warn(plgData.link);
fs.writeFileSync(linkNoExtension + '.js', transformResult.code);
}
}
const pluginNames = e.map((e) => e.split('/').pop());
const pluginNames = e.map((data) => {
return 'Installed ' + data.name + ' ' + 'from ' + data.author.join(',');
});
console.log(`Successfully downloaded plugin(s):\n${greenBright(pluginNames.join('\n'))}`);
}
@@ -26,11 +67,5 @@ async function download(url: string) {
if (!data) throw new Error('Download failed! Kindly contact developers');
const dir = `${fromCwd('/src/plugins')}`;
const filedir = `${process.cwd()}/src/plugins/${url.split('/').pop()}`;
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(filedir, data);
return data;
}

View File

@@ -1,21 +1,25 @@
import { magentaBright } from 'colorette';
import { getConfig } from '../utilities/getConfig';
import { fork } from 'node:child_process';
import { fileURLToPath } from 'url';
export async function publish(commandDir: string | undefined, args: Partial<PublishArgs>) {
if (!args.suppressWarnings) {
console.info(`${magentaBright('EXPERIMENTAL')}: This API has not been stabilized. add -W or --suppress-warnings flag to suppress`);
}
const config = await getConfig();
// pass in args into the command.
const rootPath = new URL('../', import.meta.url),
publishScript = new URL('./dist/create-publish.js', rootPath);
publishScript = new URL('../dist/create-publish.js', rootPath);
// assign args.import to empty array if non existent
args.import ??= [];
args.token && console.info('token passed through command line');
args.applicationId && console.info('applicationId passed through command line');
args.token && console.info('Token passed through command line');
args.applicationId && console.info(magentaBright('WARNING')+ ' This option is deprecated. Do not pass applicationId through command line');
commandDir && console.info('Publishing with override path: ', commandDir);
const dotenvLocation = new URL('./node_modules/dotenv/config.js', rootPath),
esmLoader = new URL('./node_modules/@esbuild-kit/esm-loader/dist/index.js', rootPath);
const dotenvLocation = new URL('../node_modules/dotenv/config.js', rootPath),
esmLoader = new URL('../node_modules/@esbuild-kit/esm-loader/dist/index.js', rootPath);
// We dynamically load the create-publish script in a child process so that we can pass the special
// loader flag to require typescript files
@@ -31,6 +35,7 @@ export async function publish(commandDir: string | undefined, args: Partial<Publ
}
interface PublishArgs {
suppressWarnings: boolean;
import: string[];
token: string;
applicationId: string;

View File

@@ -10,7 +10,8 @@ import { once } from 'node:events';
import * as Rest from './rest';
import type { sernConfig } from './utilities/getConfig';
import type { PublishableData, PublishableModule, Typeable } from './create-publish.d.ts';
import { cyanBright, greenBright, redBright } from 'colorette';
import { cyanBright, greenBright, magentaBright, redBright } from 'colorette';
import { inspect } from 'node:util'
import ora from 'ora';
async function deriveFileInfo(dir: string, file: string) {
@@ -57,7 +58,10 @@ async function* readPaths(dir: string, shouldDebug: boolean): AsyncGenerator<str
// recieved sern config
const [{ config, preloads, commandDir }] = await once(process, 'message'),
{ paths } = config as sernConfig;
for (const preload of preloads) {
console.log("preloading: ", preload);
await import('file:///' + resolve(preload));
}
@@ -125,6 +129,18 @@ const makeDescription = (type: number, desc: string) => {
}
return desc;
};
const serialize = (permissions: unknown) => {
if(typeof permissions === 'bigint' || typeof permissions === 'number') {
return permissions.toString();
}
if(Array.isArray(permissions)) {
return permissions
.reduce((acc, cur) => acc | cur, BigInt(0))
.toString()
}
return null;
}
const makePublishData = ({ commandModule, config }: Record<string, Record<string, unknown>>) => {
const applicationType = intoApplicationType(commandModule.type as number);
@@ -136,7 +152,7 @@ const makePublishData = ({ commandModule, config }: Record<string, Record<string
absPath: commandModule.absPath as string,
options: optionsTransformer((commandModule?.options ?? []) as Typeable[]),
dm_permission: config?.dmPermission,
default_member_permissions: config?.defaultMemberPermissions ?? null,
default_member_permissions: serialize(config?.defaultMemberPermissions),
},
config,
};
@@ -144,12 +160,11 @@ const makePublishData = ({ commandModule, config }: Record<string, Record<string
// We can use these objects to publish to DAPI
const publishableData = modules.map(makePublishData),
token = process.env.DISCORD_TOKEN ?? process.env.token,
appid = process.env.APPLICATION_ID ?? process.env.applicationId;
token = process.env.token || process.env.DISCORD_TOKEN,
appid = process.env.applicationId || process.env.APPLICATION_ID;
assert(token, 'Could not find a token for this bot in .env or commandline. Do you have DISCORD_TOKEN in env?');
assert(appid, 'Could not find an application id for this bot in .env or commandline. Do you have APPLICATION_ID in env?');
appid && console.warn(`${magentaBright('WARNING')}: APPLICATION_ID is not necessary anymore`);
// partition globally published and guilded commands
const [globalCommands, guildedCommands] = publishableData.reduce(
([globals, guilded], module) => {
@@ -166,28 +181,40 @@ const spin = ora(`Publishing ${cyanBright('Global')} commands`);
globalCommands.length && spin.start();
const rest = Rest.create(appid, token);
const rest = await Rest.create(token);
const res = await rest.updateGlobal(globalCommands);
let globalCommandsResponse: unknown;
if (res.ok) {
spin.succeed(`All ${cyanBright('Global')} commands published`);
globalCommands.length && spin.succeed(`All ${cyanBright('Global')} commands published`);
globalCommandsResponse = await res.json();
} else {
spin.fail(`Failed to publish global commands [Code: ${redBright(res.status)}]`);
if (res.status === 429) {
throw Error('Chill out homie, too many requests');
let err: Error
console.error("Status Text ", res.statusText);
switch(res.status) {
case 400 : {
const validation_errors = await res.json()
console.error('errors:', inspect(validation_errors, { depth: Infinity }));
console.error("Modules with validation errors:"
+ inspect(Object.keys(validation_errors.errors).map(idx => globalCommands[idx as any])))
throw Error("400: Ensure your commands have proper fields and data with nothing left out");
}
case 404 : {
console.error('errors:', inspect(await res.json(), { depth: Infinity }));
throw Error("Forbidden 404. Is you application id and/or token correct?")
}
case 429: {
console.error('errors:', inspect(await res.json(), { depth: Infinity }));
err = Error('Chill out homie, too many requests')
} break;
default: {
console.error('errors:', inspect(await res.json(), { depth: Infinity }));
throw Error(res.status.toString() + " error")
}
}
console.error(
'errors:',
await res.json().then((res) => {
const errors = Object.values(res.errors);
// @ts-ignore
return errors.map((err) => err?.name?._errors);
})
);
console.error(res.statusText);
}
function associateGuildIdsWithData(data: PublishableModule[]): Map<string, PublishableData[]> {
@@ -207,7 +234,6 @@ function associateGuildIdsWithData(data: PublishableModule[]): Map<string, Publi
});
}
});
return guildIdMap;
}
const guildCommandMap = associateGuildIdsWithData(guildedCommands);
@@ -226,7 +252,23 @@ for (const [guildId, array] of guildCommandMap.entries()) {
spin.succeed(`[${greenBright(guildId)}] Successfully updated commands for guild`);
} else {
spin.fail(`[${redBright(guildId)}] Failed to update commands for guild, Reason: ${result.message}`);
throw Error(result.message);
switch(response.status) {
case 400 : {
console.error(inspect(result, { depth: Infinity }))
console.error("Modules with validation errors:"
+ inspect(Object.keys(result.errors).map(idx => array[idx as any])))
throw Error("400: Ensure your commands have proper fields and data and nothing left out");
}
case 404 : {
console.error(inspect(result, { depth: Infinity }))
throw Error("Forbidden 404. Is you application id and/or token correct?")
}
case 429: {
console.error(inspect(result, { depth: Infinity }))
throw Error('Chill out homie, too many requests')
}
}
}
}
const remoteData = {

View File

@@ -1,52 +1,68 @@
#!/usr/bin/env node
import { extra } from './commands/extra.js';
import { help } from './commands/help.js';
import { init } from './commands/init.js';
import { publish } from './commands/publish.js';
import { Command } from 'commander';
import { plugins } from './commands/plugins.js';
import { yellowBright } from 'colorette';
export const program = new Command();
const version: string = '[VI]{{inject}}[/VI]';
const importDynamic = async <T extends string>(filename: T) => import(`./commands/${filename}` as const);
declare const __VERSION__: string;
program //
program
.name('sern')
.description(help)
.version(`sern CLI v${version}`, '-v, --version')
.description(await importDynamic('help.js').then((m) => m.help))
.version(`sern CLI v${__VERSION__}`, '-v, --version')
.exitOverride(() => process.exit(0));
program //
.command(init.name)
program
.command('init')
.description(`Quickest way to scaffold a new project ${yellowBright('[DEPRECATED]')}`)
.option('-y', 'Finishes setup as default')
.option('-s, --sync', 'Syncs the project and generates sern.config.json')
.action(init);
.action(async (...args) => importDynamic('init.js').then((m) => m.init(...args)));
program //
.command(plugins.name)
program
.command('plugins')
.description('Install plugins from https://github.com/sern-handler/awesome-plugins')
.option('-n --name', 'Name of plugin')
.action(plugins);
.action((...args) => importDynamic('plugins.js').then((m) => m.plugins(...args)));
program //
.command(extra.name)
program
.command('extra')
.description('Easy way to add extra things in your sern project')
.action(extra);
.action((...args) => importDynamic('extra.js').then((m) => m.extra(...args)));
program //
.command('commands')
.description('Defacto way to manage your slash commands')
.addCommand(
new Command(publish.name)
new Command('publish')
.description('New way to manage your slash commands')
.option('-W --suppress-warnings', 'suppress experimental warning')
.option('-i, --import [scriptPath...]', 'Prerequire a script to load into publisher')
.option('-t, --token [token]')
.option('--appId [applicationId]')
.argument('[path]', 'path with respect to current working directory that will locate all published files')
.action(publish)
);
.action(async (...args) => importDynamic('publish.js').then((m) => m.publish(...args)))
).addCommand(
new Command('list') //
.description('List all slash commands')
.action(async (...args) => importDynamic('list.js').then((m) => m.list(...args))))
.addCommand(
new Command('clear')
.description('Clear and reset commands-data-remote.json and the api')
.option('-y, --yes', "Say yes to all prompts")
.option('-e, --env [path]', "Supply a path to a .env")
.action(async (...args) => importDynamic('command-clear.js').then((m) => m.commandClear(...args))));
program
.command('build')
.description('Build your bot')
.option('-f --format [fmt]', 'The module system of your application. `cjs` or `esm`', 'esm')
.option('-m --mode [mode]', 'the mode for sern to build in. `production` or `development`', 'development')
.option('-W --suppress-warnings', 'suppress experimental warning')
.option('-p --project [filePath]', 'build with the provided sern.build file')
.option('-e --env', 'path to .env file')
.option('--tsconfig [filePath]', 'Use this tsconfig')
.action(async (...args) => importDynamic('build.js').then((m) => m.build(...args)));
program.parse();

View File

@@ -0,0 +1,39 @@
import fs from 'fs/promises';
import path from 'node:path';
import { require } from '../utilities/require.js';
import { type Plugin } from 'esbuild';
import { basename } from 'node:path';
export const validExtensions = ['.ts', '.js', '.json', '.png', '.jpg', '.jpeg', '.webp'];
//https://github.com/evanw/esbuild/issues/1051
export const imageLoader = {
name: 'attachment-loader',
setup: (b) => {
const filter = new RegExp(`\.${validExtensions.slice(3).join('|')}$`);
b.onResolve({ filter }, (args) => {
//if the module is being imported, resolve the path and transform to the js stub
if (args.importer) {
const newPath = path
.format({ ...path.parse(args.path), base: '', ext: '.js' })
.split(path.sep)
.join(path.posix.sep);
return { path: newPath, namespace: 'attachment-loader', external: true };
}
// if the file is actually the attachment, resolve the full dir
return { path: require.resolve(args.path, { paths: [args.resolveDir] }), namespace: 'attachment-loader' };
});
b.onLoad({ filter: /.*/, namespace: 'attachment-loader' }, async (args) => {
const base64 = await fs.readFile(args.path).then((s) => s.toString('base64'));
return {
contents: `
var __toBuffer = (base64) => Buffer.from(base64, "base64");
module.exports = {
name: '${basename(args.path)}',
attachment: __toBuffer("${base64}")
}`,
};
});
},
} satisfies Plugin;

View File

@@ -1,28 +1,17 @@
import type { Choice, PromptObject } from 'prompts';
import { fetch } from 'undici';
import { getLang } from '../utilities/getLang.js';
function upperCase(string: string | null) {
if (string === null) {
console.error('Lang property not found!');
process.exit(0);
}
return string === 'typescript' ? 'TypeScript' : 'JavaScript';
}
async function gimmechoices(): Promise<Choice[]> {
const lang = upperCase(await getLang().catch(() => null));
const link = `https://api.github.com/repos/sern-handler/awesome-plugins/contents/${lang}`;
const link = `https://raw.githubusercontent.com/sern-handler/awesome-plugins/main/pluginlist.json`;
const resp = await fetch(link).catch(() => null);
if (!resp) return [{ title: 'No plugins found!', value: '', disabled: true }];
const data = (await resp.json()) as Data[];
const choices = data.map((e) => ({
title: e.name,
value: e.download_url,
value: e,
}));
return choices;
}
@@ -38,5 +27,5 @@ export async function pluginsQ(): Promise<PromptObject> {
interface Data {
name: string;
download_url: string;
link: string;
}

View File

@@ -11,12 +11,22 @@ const publishablesIntoJson = (ps: PublishableModule[]) =>
4
);
export const create = (appid: string, token: string) => {
const globalURL = new URL(`${appid}/commands`, baseURL);
export const create = async (token: string) => {
const headers = {
Authorization: 'Bot ' + token,
'Content-Type': 'application/json',
};
let me;
let appid: string;
try {
me = await fetch(new URL('@me', baseURL), { headers }).then(res => res.json());
appid = me.id;
} catch(e) {
console.log("Something went wrong while trying to fetch your application:");
throw e;
}
const globalURL = new URL(`${appid}/commands`, baseURL);
return {
updateGlobal: (commands: PublishableModule[]) =>
fetch(globalURL, {
@@ -29,7 +39,7 @@ export const create = (appid: string, token: string) => {
return fetch(guildCommandURL, { headers });
},
putGuildCommands: (guildId: string, guildCommand: any) => {
putGuildCommands: (guildId: string, guildCommand: unknown) => {
const guildCommandURL = new URL(`${appid}/guilds/${guildId}/commands`, baseURL);
return fetch(guildCommandURL, {
method: 'PUT',

19
src/types/config.d.ts vendored Normal file
View File

@@ -0,0 +1,19 @@
export interface sernConfig {
language: 'typescript' | 'javascript';
paths: {
base: string;
commands: string;
};
scripts?: {
prepublish?: string;
};
buildPath: string;
rest?: Record<string, Record<string, unknown>>;
}
export interface TheoreticalEnv {
DISCORD_TOKEN: string;
APPLICATION_ID?: string;
MODE: 'PROD' | 'DEV';
[name: string]: string;
}

View File

@@ -2,9 +2,7 @@ import { mkdir, readFile, writeFile } from 'fs/promises';
import { dirname, resolve } from 'node:path';
import { fileURLToPath, URL } from 'url';
const root = new URL('../../', import.meta.url);
const sern = new URL('./@sern/', root);
const cli = new URL('./cli/', sern);
const templates = new URL('./templates/', cli);
const templates = new URL('./templates/', root);
const extraURL = new URL('./extra/', templates);
const extraFolder = fileURLToPath(extraURL);
@@ -15,6 +13,7 @@ const extraFolder = fileURLToPath(extraURL);
* @param location - The location of the file to be created.
* @param no_ext - If true, the file will be created without an extension.
*/
export async function create(name: string, lang: string, location: string, no_ext: boolean) {
const file = `${name}.${lang}.sern`;

View File

@@ -0,0 +1,12 @@
import type esbuild from 'esbuild';
import { resolve } from 'path';
export default (format: 'cjs' | 'esm', tsconfig: string|undefined) =>
({
platform: 'node',
format,
tsconfig: tsconfig,
logLevel: 'info',
minify: false,
outdir: resolve('dist'),
} satisfies esbuild.BuildOptions);

View File

@@ -20,4 +20,5 @@ export interface sernConfig {
commands: string;
events?: string;
};
buildPath: string;
}

17
src/utilities/getSern.ts Normal file
View File

@@ -0,0 +1,17 @@
import { readdirSync } from 'node:fs';
import { fromCwd } from './fromCwd';
import { redBright, cyanBright } from 'colorette';
export function getSern() {
let files: string[] = [];
try {
const sern = fromCwd('.sern');
files = readdirSync(sern);
} catch (error) {
console.error(`${redBright('Error:')} Could not locate ${cyanBright('.sern')} directory`);
process.exit(1);
} finally {
return files;
}
}

View File

@@ -0,0 +1,93 @@
const declareConstType = (name: string, type: string) => String.raw`declare var ${name}: ${type}`;
const processEnvType = (env: NodeJS.ProcessEnv) => {
const entries = Object.keys(env);
const envBuilder = new StringWriter();
for (const key of entries) {
envBuilder.tab()
.tab()
.envField(key);
}
return envBuilder.build();
};
const determineJSONType = (s: string) => {
return typeof JSON.parse(s);
};
type FileWriter = (path: string, content: string, format: BufferEncoding) => Promise<void>;
const writeAmbientFile = async (path: string, define: Record<string, string>, writeFile: FileWriter) => {
const fileContent = new StringWriter();
for (const [k, v] of Object.entries(define)) {
fileContent.varDecl(k, v);
}
fileContent
.println('declare namespace NodeJS {')
.tab()
.println('interface ProcessEnv {')
.envFields(process.env)
.tab()
.println('}')
.println('}');
await writeFile(path, fileContent.build(), 'utf8');
};
const writeTsConfig = async (format: 'cjs' | 'esm', configPath: string, fw: FileWriter) => {
//maybe better way to do this
const target = format === 'esm' ? { target: 'esnext' } : {};
const sernTsConfig = {
compilerOptions: {
//module determines top level await. CJS doesn't have that abliity afaik
module: format === 'cjs' ? 'node' : 'esnext',
moduleResolution: 'node',
strict: true,
skipLibCheck: true,
...target,
rootDirs: ['./generated', '../src'],
},
include: ['./ambient.d.ts', '../src'],
};
await fw(configPath, JSON.stringify(sernTsConfig, null, 3), 'utf8');
};
class StringWriter {
private fileString = '';
tab() {
this.fileString += ' ';
return this;
}
varDecl(name: string, type: string) {
this.fileString += declareConstType(name, determineJSONType(type)) + '\n';
return this;
}
println(data: string) {
this.fileString += data + '\n';
return this;
}
envField(key: string) {
//if env field has space or parens, wrap key in ""
if (/\s|\(|\)/g.test(key)) {
this.fileString += `"${key}": string`;
} else {
this.fileString += key + ':' + 'string';
}
this.fileString += '\n';
return this;
}
envFields(env: NodeJS.ProcessEnv) {
this.fileString += processEnvType(env);
return this;
}
build() {
return this.fileString;
}
}
export { writeAmbientFile, writeTsConfig };

View File

@@ -0,0 +1,41 @@
import { readdir, stat } from 'fs/promises';
import { basename, join, extname } from 'path';
function isSkippable(filename: string) {
//empty string is for non extension files (directories)
const validExtensions = ['.js', '.cjs', '.mts', '.mjs', '.cts', '.ts', ''];
return filename[0] === '!' || !validExtensions.includes(extname(filename));
}
async function deriveFileInfo(dir: string, file: string) {
const fullPath = join(dir, file);
return {
fullPath,
fileStats: await stat(fullPath),
base: basename(file),
};
}
export async function* readPaths(dir: string, shouldDebug: boolean): AsyncGenerator<string> {
try {
const files = await readdir(dir);
for (const file of files) {
const { fullPath, fileStats, base } = await deriveFileInfo(dir, file);
if (fileStats.isDirectory()) {
//Todo: refactor so that i dont repeat myself for files (line 71)
if (isSkippable(base)) {
if (shouldDebug) console.info(`ignored directory: ${fullPath}`);
} else {
yield* readPaths(fullPath, shouldDebug);
}
} else {
if (isSkippable(base)) {
if (shouldDebug) console.info(`ignored: ${fullPath}`);
} else {
yield 'file:///' + fullPath;
}
}
}
} catch (err) {
throw err;
}
}

3
src/utilities/require.ts Normal file
View File

@@ -0,0 +1,3 @@
import { createRequire } from 'node:module';
export const require = createRequire(import.meta.url);

View File

@@ -1 +1,35 @@
export type PackageManagerChoice = 'skip' | 'npm' | 'yarn';
export type GuildId = string;
export interface CommandData {
id: string;
application_id: string;
version: string;
default_member_permissions?: string;
type: number;
name: string;
name_localizations?: Record<string, string>;
description: string;
description_localizations?: Record<string, string>;
dm_permission: boolean;
guild_id: string;
nsfw: boolean;
options?: OptionData[];
}
interface OptionData {
type: number;
name: string;
name_localizations?: Record<string, string>;
description: string;
description_localizations?: Record<string, string>;
required?: boolean;
choices?: ChoiceData[];
options?: OptionData[];
}
interface ChoiceData {
name: string;
value: string | number;
}

View File

@@ -8,4 +8,4 @@ RUN npm install
COPY . .
RUN node src/index.js
CMD node src/index.js

View File

@@ -6,8 +6,10 @@ COPY package.json ./
RUN npm install
RUN npm install -D typescript
COPY . .
RUN tsc --build
RUN tsc --build || npx -p typescript tsc --build
RUN node dist/index.js
CMD node dist/index.js

View File

@@ -1,7 +1,11 @@
import { defineConfig } from 'tsup';
import { esbuildPluginVersionInjector } from 'esbuild-plugin-version-injector';
import { createRequire } from 'node:module';
const shared = {
entry: ['src/index.ts', 'src/create-publish.mts'],
entry: [
'src/index.ts',
'src/create-publish.mts',
'src/commands/**',
],
clean: true,
sourcemap: true,
};
@@ -11,8 +15,12 @@ export default defineConfig({
tsconfig: './tsconfig.json',
outDir: './dist',
treeshake: true,
esbuildPlugins: [esbuildPluginVersionInjector()],
bundle: true,
esbuildPlugins: [],
platform: 'node',
splitting: false,
splitting: true,
define: {
__VERSION__: `"${createRequire(import.meta.url)('./package.json').version}"`,
},
...shared,
});