10 Commits

Author SHA1 Message Date
github-actions[bot]
f12d541dfe chore(main): release 0.6.0 (#110)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-08-09 23:00:36 +05:30
EvolutionX
642bf11608 chore: release 0.6.0
Release-As: 0.6.0
2023-08-09 22:14:55 +05:30
Evo
827ffb7ad9 feat: publish command (#105)
Co-authored-by: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com>
Co-authored-by: jacob <jacoobes@sern.dev>
2023-08-09 22:06:50 +05:30
Gary
2348e3e9ab fix: change file path for sern/extras (#109)
Co-authored-by: gary <gary@mini-hoster.thetechgurus.xyz>
2023-08-06 20:37:35 +05:30
Jacob Nguyen
14df4a9b65 chore: Update package.json 2023-06-29 22:57:54 -05:00
renovate[bot]
4bc7d1b081 chore(deps): lock file maintenance (#94)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-26 08:09:42 +05:30
EvolutionX
acbec3e733 chore: bless seren 2023-06-19 16:57:46 +05:30
github-actions[bot]
dbc3154101 chore(main): release 0.5.1 (#103)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-06-16 10:38:09 +05:30
EvolutionX
7252c533bc chore: remove yarn 2023-06-16 10:35:39 +05:30
Evo
dce78c0945 feat(init): deprecate init and bump deps (#102)
Co-authored-by: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com>
2023-06-16 10:32:56 +05:30
26 changed files with 6514 additions and 5363 deletions

View File

@@ -1,5 +1,6 @@
{ {
"tabWidth": 4, "tabWidth": 4,
"useTabs": true, "useTabs": false,
"singleQuote": true "singleQuote": true,
"printWidth": 140
} }

View File

@@ -2,6 +2,36 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## [0.6.0](https://github.com/sern-handler/cli/compare/v0.5.1...v0.6.0) (2023-08-09)
### Features
* publish command ([#105](https://github.com/sern-handler/cli/issues/105)) ([827ffb7](https://github.com/sern-handler/cli/commit/827ffb7ad9252e3cda257bed1febdca2da03e253))
### Bug Fixes
* change file path for sern/extras ([#109](https://github.com/sern-handler/cli/issues/109)) ([2348e3e](https://github.com/sern-handler/cli/commit/2348e3e9ab055fddab3f44d574af79fd7ccd4485))
### Miscellaneous Chores
* release 0.6.0 ([642bf11](https://github.com/sern-handler/cli/commit/642bf11608cf5d9b4256999e3bdb48e762ca88ee))
## [0.5.1](https://github.com/sern-handler/cli/compare/v0.5.0...v0.5.1) (2023-06-16)
### Features
* **init:** deprecate init and bump deps ([#102](https://github.com/sern-handler/cli/issues/102)) ([dce78c0](https://github.com/sern-handler/cli/commit/dce78c0945de6da79bf1e268f29651da0c44c1eb))
* version injector ([#90](https://github.com/sern-handler/cli/issues/90)) ([58fa325](https://github.com/sern-handler/cli/commit/58fa3253f62da9fb66d1b2ae901b568367f065d0))
### Bug Fixes
* git not installed errors during init ([#79](https://github.com/sern-handler/cli/issues/79)) ([69287ab](https://github.com/sern-handler/cli/commit/69287ab1bd0c4960384144f90fea8ebded3b0cc5))
## [0.5.0](https://github.com/sern-handler/cli/compare/v0.4.2...v0.5.0) (2022-09-16) ## [0.5.0](https://github.com/sern-handler/cli/compare/v0.4.2...v0.5.0) (2022-09-16)

1931
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "@sern/cli", "name": "@sern/cli",
"version": "0.5.0", "version": "0.6.0",
"description": "Official CLI for @sern/handler", "description": "Official CLI for @sern/handler",
"exports": "./dist/index.js", "exports": "./dist/index.js",
"bin": { "bin": {
@@ -12,7 +12,7 @@
"format": "prettier --check .", "format": "prettier --check .",
"fix": "prettier --write .", "fix": "prettier --write .",
"build": "tsup", "build": "tsup",
"watch": "tsc --watch" "watch": "tsup --watch"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@@ -30,26 +30,30 @@
"bugs": { "bugs": {
"url": "https://github.com/sern-handler/cli/issues" "url": "https://github.com/sern-handler/cli/issues"
}, },
"homepage": "https://sern-handler.js.org", "homepage": "https://sern.dev",
"dependencies": { "dependencies": {
"colorette": "^2.0.16", "@esbuild-kit/cjs-loader": "^2.4.2",
"commander": "^9.3.0", "@esbuild-kit/esm-loader": "^2.5.5",
"execa": "^6.1.0", "colorette": "2.0.20",
"commander": "11.0.0",
"dotenv": "^16.3.1",
"execa": "7.1.1",
"find-up": "6.3.0", "find-up": "6.3.0",
"ora": "^6.1.0", "ora": "6.3.1",
"prompts": "2.4.2", "prompts": "2.4.2",
"undici": "^5.6.1" "undici": "5.22.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/parser": "^7.22.5",
"@favware/npm-deprecate": "1.0.7", "@favware/npm-deprecate": "1.0.7",
"@types/prompts": "2.4.3", "@types/prompts": "2.4.4",
"esbuild-plugin-version-injector": "^1.0.3", "esbuild-plugin-version-injector": "1.1.0",
"prettier": "2.8.4", "prettier": "2.8.8",
"tsup": "^6.6.3", "tsup": "6.7.0",
"typescript": "4.9.5" "typescript": "5.1.3"
}, },
"engines": { "engines": {
"node": ">= 16.10.x" "node": ">= 18.16.x"
}, },
"publishConfig": { "publishConfig": {
"registry": "https://registry.npmjs.org/", "registry": "https://registry.npmjs.org/",

View File

@@ -1,6 +1,6 @@
{ {
"$schema": "https://docs.renovatebot.com/renovate-schema.json", "$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:base"], "extends": ["config:base", "group:allNonMajor"],
"major": { "major": {
"dependencyDashboardApproval": true "dependencyDashboardApproval": true
}, },

View File

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

View File

@@ -1,17 +1,9 @@
import { greenBright, redBright, underline } from 'colorette'; import { greenBright, redBright, underline, yellowBright } from 'colorette';
import { execa } from 'execa'; import { execa } from 'execa';
import { findUp } from 'find-up'; import { findUp } from 'find-up';
import ora from 'ora'; import ora from 'ora';
import prompt from 'prompts'; import prompt from 'prompts';
import { import { cmds_dir, gitInit, lang, main_dir, name, skip_install_dep, which_manager } from '../prompts/init.js';
cmds_dir,
gitInit,
lang,
main_dir,
name,
skip_install_dep,
which_manager,
} from '../prompts/init.js';
import { writeFile } from 'fs/promises'; import { writeFile } from 'fs/promises';
import { editDirs, editMain } from '../utilities/edits.js'; import { editDirs, editMain } from '../utilities/edits.js';
@@ -19,7 +11,10 @@ import { cloneRepo, installDeps } from '../utilities/install.js';
import { npm } from '../utilities/npm.js'; import { npm } from '../utilities/npm.js';
import type { PackageManagerChoice } from '../utilities/types.js'; import type { PackageManagerChoice } from '../utilities/types.js';
/** @deprecated Use npm create instead */
export async function init(flags: Flags) { export async function init(flags: Flags) {
console.log(`${yellowBright('[WARN]:')} This command is deprecated, use ${greenBright('npm create @sern/bot')} instead`);
let data: PromptData; let data: PromptData;
let git_init = true; // the default; let git_init = true; // the default;
let pm = flags.sync ? undefined : flags.y ? 'npm' : await npm(); let pm = flags.sync ? undefined : flags.y ? 'npm' : await npm();
@@ -78,9 +73,7 @@ export async function init(flags: Flags) {
if (pm === 'both') { if (pm === 'both') {
choice = (await prompt([which_manager])).manager; choice = (await prompt([which_manager])).manager;
} else { } else {
choice = ( choice = ((await prompt([skip_install_dep])).skip_install_dep ? pm : 'skip') as PackageManagerChoice;
(await prompt([skip_install_dep])).skip_install_dep ? pm : 'skip'
) as PackageManagerChoice;
} }
await installDeps(choice, data.name); await installDeps(choice, data.name);
@@ -103,13 +96,7 @@ async function git(data: Data) {
await wait(300); await wait(300);
spin.succeed('Git initialized!'); spin.succeed('Git initialized!');
} catch (error) { } catch (error) {
spin.fail( spin.fail(`${redBright('Failed')} to initialize git!\nTry to install it at ${underline('https://git-scm.com')}\nSkipping for now.`);
`${redBright(
'Failed'
)} to initialize git!\nTry to install it at ${underline(
'https://git-scm.com'
)}\nSkipping for now.`
);
} }
} }

View File

@@ -16,11 +16,7 @@ export async function plugins() {
await download(url); await download(url);
} }
const pluginNames = e.map((e) => e.split('/').pop()); const pluginNames = e.map((e) => e.split('/').pop());
console.log( console.log(`Successfully downloaded plugin(s):\n${greenBright(pluginNames.join('\n'))}`);
`Successfully downloaded plugin(s):\n${greenBright(
pluginNames.join('\n')
)}`
);
} }
async function download(url: string) { async function download(url: string) {

37
src/commands/publish.ts Normal file
View File

@@ -0,0 +1,37 @@
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>) {
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);
// 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');
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);
// We dynamically load the create-publish script in a child process so that we can pass the special
// loader flag to require typescript files
const command = fork(fileURLToPath(publishScript), [], {
execArgv: ['--loader', esmLoader.toString(), '-r', fileURLToPath(dotenvLocation), '--no-warnings'],
env: {
token: args.token ?? '',
applicationId: args.applicationId ?? '',
},
});
// send paths object so we dont have to recalculate it in script
command.send({ config, preloads: args.import, commandDir });
}
interface PublishArgs {
import: string[];
token: string;
applicationId: string;
}

17
src/create-publish.d.ts vendored Normal file
View File

@@ -0,0 +1,17 @@
export interface PublishableData {
name: string;
type: number;
description: string;
absPath: string;
options: Typeable[];
}
export interface Typeable {
type: number;
}
export interface Config {
guildIds?: string[];
}
export interface PublishableModule {
data: PublishableData;
config: Config;
}

241
src/create-publish.mts Normal file
View File

@@ -0,0 +1,241 @@
/**
* This file is meant to be run with the esm / cjs esbuild-kit loader to properly import typescript modules
*/
import { readdir, stat, mkdir, writeFile } from 'fs/promises';
import { join, basename, extname, resolve } from 'node:path';
import { pathExistsSync } from 'find-up';
import assert from 'assert';
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 ora from 'ora';
async function deriveFileInfo(dir: string, file: string) {
const fullPath = join(dir, file);
return {
fullPath,
fileStats: await stat(fullPath),
base: basename(file),
};
}
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* 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;
}
}
// recieved sern config
const [{ config, preloads, commandDir }] = await once(process, 'message'),
{ paths } = config as sernConfig;
for (const preload of preloads) {
await import('file:///' + resolve(preload));
}
const commandsPath = commandDir ? resolve(commandDir) : resolve(paths.base, paths.commands);
const filePaths = readPaths(commandsPath, true);
const modules = [];
const PUBLISHABLE = 0b1110;
for await (const absPath of filePaths) {
let mod = await import(absPath);
let commandModule = mod.default;
let config = mod.config;
if ('default' in commandModule) {
commandModule = commandModule.default;
}
if (typeof config === 'function') {
config = config(absPath, commandModule);
}
try {
commandModule = commandModule.getInstance();
} catch {}
if ((PUBLISHABLE & commandModule.type) != 0) {
// assign defaults
const filename = basename(absPath);
const filenameNoExtension = filename.substring(0, filename.lastIndexOf('.'));
commandModule.name ??= filenameNoExtension;
commandModule.description ??= '';
commandModule.absPath = absPath;
modules.push({ commandModule, config });
}
}
const cacheDir = resolve('./.sern');
if (!pathExistsSync(cacheDir)) {
// TODO: add this in verbose flag
// console.log('Making .sern directory: ', cacheDir);
await mkdir(cacheDir);
}
const optionsTransformer = (ops: Array<Typeable>) => {
return ops.map((el) => {
if ('command' in el) {
const { command, ...rest } = el;
return rest;
}
return el;
});
};
const intoApplicationType = (type: number) => {
if (type === 3) {
return 1;
}
return Math.log2(type);
};
const makeDescription = (type: number, desc: string) => {
if (type !== 1 && desc !== '') {
console.warn('Found context menu that has non empty description field. Implictly publishing with empty description');
return '';
}
return desc;
};
const makePublishData = ({ commandModule, config }: Record<string, Record<string, unknown>>) => {
const applicationType = intoApplicationType(commandModule.type as number);
return {
data: {
name: commandModule.name as string,
type: applicationType,
description: makeDescription(applicationType, commandModule.description as string),
absPath: commandModule.absPath as string,
options: optionsTransformer((commandModule?.options ?? []) as Typeable[]),
dm_permission: config?.dmPermission,
default_member_permissions: config?.defaultMemberPermissions ?? null,
},
config,
};
};
// 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;
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?');
// partition globally published and guilded commands
const [globalCommands, guildedCommands] = publishableData.reduce(
([globals, guilded], module) => {
const isPublishableGlobally = !module.config || !Array.isArray(module.config.guildIds);
if (isPublishableGlobally) {
return [[module, ...globals], guilded];
}
return [globals, [module, ...guilded]];
},
[[], []] as [PublishableModule[], PublishableModule[]]
);
const spin = ora(`Publishing ${cyanBright('Global')} commands`);
globalCommands.length && spin.start();
const rest = Rest.create(appid, token);
const res = await rest.updateGlobal(globalCommands);
let globalCommandsResponse: unknown;
if (res.ok) {
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');
}
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[]> {
const guildIdMap: Map<string, PublishableData[]> = new Map();
data.forEach((entry) => {
const { data, config } = entry;
const { guildIds } = config || {};
if (guildIds) {
guildIds.forEach((guildId) => {
if (guildIdMap.has(guildId)) {
guildIdMap.get(guildId)?.push(data);
} else {
guildIdMap.set(guildId, [data]);
}
});
}
});
return guildIdMap;
}
const guildCommandMap = associateGuildIdsWithData(guildedCommands);
let guildCommandMapResponse = new Map<string, Record<string, unknown>>();
for (const [guildId, array] of guildCommandMap.entries()) {
const spin = ora(`[${cyanBright(guildId)}] Updating commands for guild`);
spin.start();
const response = await rest.putGuildCommands(guildId, array);
const result = await response.json();
if (response.ok) {
guildCommandMapResponse.set(guildId, result);
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);
}
}
const remoteData = {
global: globalCommandsResponse,
...Object.fromEntries(guildCommandMapResponse),
};
await writeFile(resolve(cacheDir, 'command-data-remote.json'), JSON.stringify(remoteData, null, 4), 'utf8');
// TODO: add this in a verbose flag
// console.info('View json output in ' + resolve(cacheDir, 'command-data-remote.json'));
process.exit(0);

View File

@@ -3,36 +3,50 @@
import { extra } from './commands/extra.js'; import { extra } from './commands/extra.js';
import { help } from './commands/help.js'; import { help } from './commands/help.js';
import { init } from './commands/init.js'; import { init } from './commands/init.js';
import { publish } from './commands/publish.js';
import { Command } from 'commander'; import { Command } from 'commander';
import { plugins } from './commands/plugins.js'; import { plugins } from './commands/plugins.js';
import { yellowBright } from 'colorette';
export const program = new Command(); export const program = new Command();
const version: string = '[VI]{{inject}}[/VI]'; const version: string = '[VI]{{inject}}[/VI]';
program
program //
.name('sern') .name('sern')
.description(help()) .description(help)
.version(`sern CLI v${version}`) .version(`sern CLI v${version}`, '-v, --version')
.exitOverride(() => process.exit(0)); .exitOverride(() => process.exit(0));
program program //
.command(init.name) .command(init.name)
.description('Quickest way to scaffold a new project') .description(`Quickest way to scaffold a new project ${yellowBright('[DEPRECATED]')}`)
.option('-y', 'Finishes setup as default') .option('-y', 'Finishes setup as default')
.option('-s, --sync', 'Syncs the project and generates sern.config.json') .option('-s, --sync', 'Syncs the project and generates sern.config.json')
.action(init); .action(init);
program program //
.command(plugins.name) .command(plugins.name)
.description( .description('Install plugins from https://github.com/sern-handler/awesome-plugins')
'Install plugins from https://github.com/sern-handler/awesome-plugins'
)
.option('-n --name', 'Name of plugin') .option('-n --name', 'Name of plugin')
.action(plugins); .action(plugins);
program program //
.command(extra.name) .command(extra.name)
.description('Easy way to add extra things in your sern project') .description('Easy way to add extra things in your sern project')
.action(extra); .action(extra);
program //
.command('commands')
.description('Defacto way to manage your slash commands')
.addCommand(
new Command(publish.name)
.description('New way to manage your slash commands')
.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)
);
program.parse(); program.parse();

View File

@@ -36,8 +36,7 @@ export const cmds_dir: PromptObject = {
name: 'cmds_dir', name: 'cmds_dir',
type: 'text', type: 'text',
initial: 'commands', initial: 'commands',
validate: (dir: string) => validate: (dir: string) => (dir === 'src' ? 'You can not use src as a directory' : true),
dir === 'src' ? 'You can not use src as a directory' : true,
}; };
export const npmInit: PromptObject = { export const npmInit: PromptObject = {
@@ -89,8 +88,5 @@ export const name: PromptObject = {
message: 'What is your project name?', message: 'What is your project name?',
name: 'name', name: 'name',
type: 'text', type: 'text',
validate: (name: string) => validate: (name: string) => (name.match('^(?:@[a-z0-9-*~][a-z0-9-*._~]*/)?[a-z0-9-~][a-z0-9-._~]*$') ? true : 'Invalid name'),
name.match('^(?:@[a-z0-9-*~][a-z0-9-*._~]*/)?[a-z0-9-~][a-z0-9-._~]*$')
? true
: 'Invalid name',
}; };

View File

@@ -16,8 +16,7 @@ async function gimmechoices(): Promise<Choice[]> {
const link = `https://api.github.com/repos/sern-handler/awesome-plugins/contents/${lang}`; const link = `https://api.github.com/repos/sern-handler/awesome-plugins/contents/${lang}`;
const resp = await fetch(link).catch(() => null); const resp = await fetch(link).catch(() => null);
if (!resp) if (!resp) return [{ title: 'No plugins found!', value: '', disabled: true }];
return [{ title: 'No plugins found!', value: '', disabled: true }];
const data = (await resp.json()) as Data[]; const data = (await resp.json()) as Data[];
const choices = data.map((e) => ({ const choices = data.map((e) => ({

41
src/rest.ts Normal file
View File

@@ -0,0 +1,41 @@
import type { PublishableModule } from './create-publish.d.ts';
const baseURL = new URL('https://discord.com/api/v10/applications/');
const excludedKeys = new Set(['command', 'absPath']);
const publishablesIntoJson = (ps: PublishableModule[]) =>
JSON.stringify(
ps.map((module) => module.data),
(key, value) => (excludedKeys.has(key) ? undefined : value),
4
);
export const create = (appid: string, token: string) => {
const globalURL = new URL(`${appid}/commands`, baseURL);
const headers = {
Authorization: 'Bot ' + token,
'Content-Type': 'application/json',
};
return {
updateGlobal: (commands: PublishableModule[]) =>
fetch(globalURL, {
method: 'PUT',
body: publishablesIntoJson(commands),
headers,
}),
getGuildCommands: (id: string) => {
const guildCommandURL = new URL(`${appid}/guilds/${id}/commands`, baseURL);
return fetch(guildCommandURL, { headers });
},
putGuildCommands: (guildId: string, guildCommand: any) => {
const guildCommandURL = new URL(`${appid}/guilds/${guildId}/commands`, baseURL);
return fetch(guildCommandURL, {
method: 'PUT',
body: JSON.stringify(guildCommand),
headers,
});
},
};
};

View File

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

View File

@@ -74,10 +74,7 @@ export async function editDirs(
const newfold = ext === 'ts' ? 'dist' : srcName; const newfold = ext === 'ts' ? 'dist' : srcName;
const regex = new RegExp(`commands: '${oldfold}/commands'`); const regex = new RegExp(`commands: '${oldfold}/commands'`);
const edit = output.replace( const edit = output.replace(regex, `commands: '${newfold}/${cmds_dirName}'`);
regex,
`commands: '${newfold}/${cmds_dirName}'`
);
return writeFile(index, edit); return writeFile(index, edit);
} }

View File

@@ -0,0 +1,23 @@
import { readFile } from 'node:fs/promises';
import { findUp } from 'find-up';
import assert from 'node:assert';
export async function getConfig(): Promise<sernConfig> {
const sernLocation = await findUp('sern.config.json');
assert(sernLocation, "Can't find sern.config.json");
const output = JSON.parse(await readFile(sernLocation, 'utf8')) as sernConfig;
assert(output, "Can't read your sern.config.json.");
return output;
}
export interface sernConfig {
language: 'typescript' | 'javascript';
defaultPrefix?: string;
paths: {
base: string;
commands: string;
events?: string;
};
}

View File

@@ -50,18 +50,11 @@ export async function installDeps(choice: PackageManagerChoice, name: string) {
*/ */
export async function cloneRepo(lang: string, name: string) { export async function cloneRepo(lang: string, name: string) {
try { try {
await execa('git', [ await execa('git', ['clone', `https://github.com/sern-handler/templates.git`]);
'clone',
`https://github.com/sern-handler/templates.git`,
]);
copyRecursiveSync(`templates/templates/${lang}`, name); copyRecursiveSync(`templates/templates/${lang}`, name);
fs.rmSync(`templates/`, { recursive: true, force: true }); fs.rmSync(`templates/`, { recursive: true, force: true });
} catch (error) { } catch (error) {
console.log( console.log(`${redBright('✖ Failed')} to clone github templates repo. Install git and try again!`);
`${redBright(
'✖ Failed'
)} to clone github templates repo. Install git and try again!`
);
process.exit(1); process.exit(1);
} }
} }
@@ -83,10 +76,7 @@ export function copyRecursiveSync(src: string, dest: string) {
fs.mkdirSync(dest); fs.mkdirSync(dest);
fs.readdirSync(src).forEach(function (childItemName) { fs.readdirSync(src).forEach(function (childItemName) {
copyRecursiveSync( copyRecursiveSync(path.join(src, childItemName), path.join(dest, childItemName));
path.join(src, childItemName),
path.join(dest, childItemName)
);
}); });
} else { } else {
fs.copyFileSync(src, dest); fs.copyFileSync(src, dest);

View File

@@ -4,14 +4,14 @@
"module": "ESNext", "module": "ESNext",
"moduleResolution": "Node", "moduleResolution": "Node",
"outDir": "dist", "outDir": "dist",
"rootDir": "src", "rootDir": ".",
"declaration": true, "declaration": true,
"declarationMap": true, "declarationMap": true,
"strict": true, "strict": true,
"esModuleInterop": true, "esModuleInterop": true,
"noImplicitAny": true, "noImplicitAny": true,
"strictNullChecks": true, "strictNullChecks": true,
"importsNotUsedAsValues": "error", "verbatimModuleSyntax": true,
"skipLibCheck": true, "skipLibCheck": true,
"forceConsistentCasingInFileNames": true "forceConsistentCasingInFileNames": true
} }

View File

@@ -1,19 +1,18 @@
import { defineConfig } from 'tsup' import { defineConfig } from 'tsup';
import { esbuildPluginVersionInjector } from 'esbuild-plugin-version-injector'; import { esbuildPluginVersionInjector } from 'esbuild-plugin-version-injector';
const shared = { const shared = {
entry: ['src/index.ts'], entry: ['src/index.ts', 'src/create-publish.mts'],
platform: 'node',
clean: true, clean: true,
sourcemap: true, sourcemap: true,
}; };
export default defineConfig( export default defineConfig({
{
format: 'esm', format: 'esm',
target: 'node16', target: 'node18',
tsconfig: './tsconfig.json', tsconfig: './tsconfig.json',
outDir: './dist', outDir: './dist',
treeshake: true, treeshake: true,
esbuildPlugins: [esbuildPluginVersionInjector()], esbuildPlugins: [esbuildPluginVersionInjector()],
platform: 'node',
splitting: false,
...shared, ...shared,
} });
)