chore: for the statistics only

permalink: http://whatthecommit.com/f6cb5f952917639c9e28f94ae495bd59
This commit is contained in:
EvolutionX
2023-08-31 10:47:45 +05:30
parent c218065a04
commit 42bd528756
12 changed files with 212 additions and 241 deletions

View File

@@ -1,68 +1,66 @@
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'
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
* 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[]
* default = esm
*/
format?: 'cjs' | 'esm';
/**
* extra esbuild plugins to build with sern.
*/
esbuildPlugins?: esbuild.Plugin[];
/**
* https://esbuild.github.io/api/#drop-labels
**/
dropLabels?: string[]
dropLabels?: string[];
/**
* https://esbuild.github.io/api/#define
**/
define?: Record<string, string>
define?: Record<string, string>;
tsconfig?: string;
/**
* default = 'development'
*/
mode: 'production' | 'development',
* default = 'development'
*/
mode: 'production' | 'development';
/**
* will search for env file. If none exists,
* default to .env.
*/
env?: string
}
* 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`)
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 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')
//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 defaultBuildConfig = {
defineVersion: true,
@@ -70,96 +68,94 @@ export async function build(options: Record<string,any>) {
mode: options.mode ?? 'development',
dropLabels: [],
tsconfig: options.tsconfig ?? resolve('tsconfig.json'),
env: options.env ?? resolve('.env')
}
if(pathExistsSync(buildConfigPath)) {
env: options.env ?? resolve('.env'),
};
if (pathExistsSync(buildConfigPath)) {
try {
buildConfig = {
...defaultBuildConfig,
...(await import('file:///'+buildConfigPath)).default,
}
} catch(e) {
console.log(e)
process.exit(1)
...(await import('file:///' + buildConfigPath)).default,
};
} catch (e) {
console.log(e);
process.exit(1);
}
} else {
buildConfig = {
...defaultBuildConfig
}
console.log('No build config found, defaulting')
...defaultBuildConfig,
};
console.log('No build config found, defaulting');
}
let env = {} as Record<string,string>
configDotenv({ path: buildConfig.env, processEnv: env })
let env = {} as Record<string, string>;
configDotenv({ path: buildConfig.env, processEnv: env });
if(env.MODE && !env.NODE_ENV) {
if (env.MODE && !env.NODE_ENV) {
console.warn('Use NODE_ENV instead of MODE');
console.warn('MODE has no effect.')
console.warn('MODE has no effect.');
console.warn(`https://nodejs.dev/en/learn/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.')
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`");
assert(buildConfig.mode === 'development' || buildConfig.mode === 'production', 'Mode is not `production` or `development`');
const defaultTsConfig = {
extends: "./.sern/tsconfig.json",
}
extends: './.sern/tsconfig.json',
};
!buildConfig.tsconfig && console.log('Using default options for tsconfig', defaultTsConfig);
const tsconfigRaw = require(buildConfig.tsconfig!);
sernConfig.language === 'typescript' && tsconfigRaw && !tsconfigRaw.extends && (
console.warn('tsconfig does not contain an "extends". Will not use sern automatic path aliasing'),
console.warn('For projects that predate sern build and want to fully integrate, extend the tsconfig generated in .sern'),
console.warn('Extend preexisting tsconfig with top level: "extends": "./.sern/tsconfig.json"')
);
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)
sernConfig.language === 'typescript' &&
tsconfigRaw &&
!tsconfigRaw.extends &&
(console.warn('tsconfig does not contain an "extends". Will not use sern automatic path aliasing'),
console.warn('For projects that predate sern build and want to fully integrate, extend the tsconfig generated in .sern'),
console.warn('Extend preexisting tsconfig with top level: "extends": "./.sern/tsconfig.json"'));
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)
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)
if (!(await pathExists(genDir))) {
console.log('Making .sern/generated dir, does not exist');
await mkdir(genDir);
}
try {
const defVersion = () => JSON.stringify(packageJson().version)
const defVersion = () => JSON.stringify(packageJson().version);
const define = {
...buildConfig.define ?? {},
__DEV__: `${buildConfig.mode === 'development'}`,
__PROD__: `${buildConfig.mode === 'production'}`,
...(buildConfig.define ?? {}),
__DEV__: `${buildConfig.mode === 'development'}`,
__PROD__: `${buildConfig.mode === 'production'}`,
} satisfies Record<string, string>;
buildConfig.defineVersion && Object.assign(define, { '__VERSION__': defVersion() });
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({
await esbuild.build({
entryPoints,
plugins: [imageLoader, ...buildConfig.esbuildPlugins??[] ],
plugins: [imageLoader, ...(buildConfig.esbuildPlugins ?? [])],
...defaultEsbuild(buildConfig.format!, tsconfigRaw),
define,
dropLabels: [ buildConfig.mode === 'production' ? '__DEV__' : '__PROD__', ...buildConfig.dropLabels!],
})
} catch(e) {
console.error(e)
process.exit(1)
dropLabels: [buildConfig.mode === 'production' ? '__DEV__' : '__PROD__', ...buildConfig.dropLabels!],
});
} catch (e) {
console.error(e);
process.exit(1);
}
}

View File

@@ -4,9 +4,8 @@ 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`)
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.
@@ -36,7 +35,7 @@ export async function publish(commandDir: string | undefined, args: Partial<Publ
}
interface PublishArgs {
suppressWarnings: boolean
suppressWarnings: boolean;
import: string[];
token: string;
applicationId: string;

View File

@@ -215,7 +215,6 @@ 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();

View File

@@ -4,36 +4,32 @@ import { Command } from 'commander';
import { yellowBright } from 'colorette';
export const program = new Command();
const importDynamic = async <T extends string>(filename: T) => import(`./commands/${filename}` as const)
declare const __VERSION__: string
const importDynamic = async <T extends string>(filename: T) => import(`./commands/${filename}` as const);
declare const __VERSION__: string;
program
.name('sern')
.description(await importDynamic('help.js').then(m => m.help))
.description(await importDynamic('help.js').then((m) => m.help))
.version(`sern CLI v${__VERSION__}`, '-v, --version')
.exitOverride(() => process.exit(0));
program
.command('init')
.description(
`Quickest way to scaffold a new project ${yellowBright('[DEPRECATED]')}`
)
.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(async (...args) => importDynamic('init.js').then(m => m.init(...args)));
.action(async (...args) => importDynamic('init.js').then((m) => m.init(...args)));
program
.command('plugins')
.description(
'Install plugins from https://github.com/sern-handler/awesome-plugins'
)
.description('Install plugins from https://github.com/sern-handler/awesome-plugins')
.option('-n --name', 'Name of plugin')
.action((...args) => importDynamic('plugins.js').then(m => m.plugins(...args)));
.action((...args) => importDynamic('plugins.js').then((m) => m.plugins(...args)));
program
.command('extra')
.description('Easy way to add extra things in your sern project')
.action((...args) => importDynamic('extra.js').then(m => m.extra(...args)));
.action((...args) => importDynamic('extra.js').then((m) => m.extra(...args)));
program //
.command('commands')
@@ -46,10 +42,10 @@ program //
.option('-t, --token [token]')
.option('--appId [applicationId]')
.argument('[path]', 'path with respect to current working directory that will locate all published files')
.action(async (...args) => importDynamic('publish.js').then(m => m.publish(...args)))
.action(async (...args) => importDynamic('publish.js').then((m) => m.publish(...args)))
);
program
program
.command('build')
.description('Build your bot')
.option('-f --format [fmt]', 'The module system of your application. `cjs` or `esm`', 'esm')
@@ -57,7 +53,7 @@ program
.option('-W --suppress-warnings', 'suppress experimental warning')
.option('-p --project [filePath]', 'build with this 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)))
.option('--tsconfig [filePath]', 'Use this tsconfig')
.action(async (...args) => importDynamic('build.js').then((m) => m.build(...args)));
program.parse();

View File

@@ -1,40 +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'
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']
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 => {
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
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 }
.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' }
})
// 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: `
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
};
});
},
} satisfies Plugin;

14
src/types/config.d.ts vendored
View File

@@ -6,16 +6,14 @@ export interface sernConfig {
};
scripts?: {
prepublish?: string;
}
};
buildPath: string;
rest?: Record<string, Record<string,unknown>>;
rest?: Record<string, Record<string, unknown>>;
}
export interface TheoreticalEnv {
DISCORD_TOKEN: string
APPLICATION_ID: string,
MODE: 'PROD' | 'DEV'
[name: string]: string
DISCORD_TOKEN: string;
APPLICATION_ID: string;
MODE: 'PROD' | 'DEV';
[name: string]: string;
}

View File

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

View File

@@ -1,4 +1,3 @@
import { readFile } from 'node:fs/promises';
import { findUp } from 'find-up';
import assert from 'node:assert';
@@ -21,5 +20,5 @@ export interface sernConfig {
commands: string;
events?: string;
};
buildPath: string
buildPath: string;
}

View File

@@ -1,96 +1,90 @@
const declareConstType = (name: string, type: string) => String.raw`declare var ${name}: ${type}`
const declareConstType = (name: string, type: string) => String.raw`declare var ${name}: ${type}`;
const processEnvType = (env: NodeJS.ProcessEnv) => {
const entries = Object.keys(env)
const entries = Object.keys(env);
const envBuilder = new StringWriter()
const envBuilder = new StringWriter();
for(const key of entries) {
envBuilder.tab()
envBuilder.tab()
envBuilder.envField(key)
for (const key of entries) {
envBuilder.tab();
envBuilder.tab();
envBuilder.envField(key);
}
return envBuilder.build()
}
return envBuilder.build();
};
const determineJSONType = (s : string) => {
return typeof JSON.parse(s)
}
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)
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 {')
.println('declare namespace NodeJS {')
.tab()
.println('interface ProcessEnv {')
.envFields(process.env)
.tab()
.println('}')
.println('}')
await writeFile(path, fileContent.build(), 'utf8')
}
.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 target = format === 'esm' ? { target: 'esnext' } : {};
const sernTsConfig = {
"compilerOptions": {
"moduleResolution": "node",
"strict": true,
"skipLibCheck": true,
compilerOptions: {
moduleResolution: 'node',
strict: true,
skipLibCheck: true,
...target,
"rootDirs": ["./generated", "../src"]
rootDirs: ['./generated', '../src'],
},
"include": ["./ambient.d.ts", "../src"]
}
include: ['./ambient.d.ts', '../src'],
};
await fw(configPath, JSON.stringify(sernTsConfig, null, 3), 'utf8')
}
await fw(configPath, JSON.stringify(sernTsConfig, null, 3), 'utf8');
};
class StringWriter {
private fileString = ""
private fileString = '';
tab() {
this.fileString+=" "
this.fileString += ' ';
return this;
}
varDecl(name: string, type: string) {
this.fileString+=declareConstType(name, determineJSONType(type))+'\n'
this.fileString += declareConstType(name, determineJSONType(type)) + '\n';
return this;
}
println(data: string) {
this.fileString+=data+"\n"
this.fileString += data + '\n';
return this;
}
envField(key: string) {
if(/\s|\(|\)/g.test(key)) {
this.fileString+=`"${key}": string`
if (/\s|\(|\)/g.test(key)) {
this.fileString += `"${key}": string`;
} else {
this.fileString+=key+ ':'+ 'string'
this.fileString += key + ':' + 'string';
}
this.fileString+="\n"
this.fileString += '\n';
return this;
}
envFields(env: NodeJS.ProcessEnv) {
this.fileString+=processEnvType(env);
this.fileString += processEnvType(env);
return this;
}
build() {
return this.fileString;
}
}
export { writeAmbientFile, writeTsConfig }
export { writeAmbientFile, writeTsConfig };

View File

@@ -1,12 +1,11 @@
import { readdir, stat } from 'fs/promises'
import { basename, join, extname } from 'path'
function isSkippable (filename: string) {
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 {
@@ -16,22 +15,15 @@ async function deriveFileInfo(dir: string, file: string) {
};
}
export async function* readPaths(
dir: string,
shouldDebug: boolean
): AsyncGenerator<string> {
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
);
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}`);
if (shouldDebug) console.info(`ignored directory: ${fullPath}`);
} else {
yield* readPaths(fullPath, shouldDebug);
}
@@ -47,4 +39,3 @@ export async function* readPaths(
throw err;
}
}

View File

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

View File

@@ -1,5 +1,5 @@
import { defineConfig } from 'tsup';
import { createRequire } from 'node:module'
import { createRequire } from 'node:module';
const shared = {
entry: ['src/index.ts', 'src/create-publish.mts', 'src/commands/**', 'sern-tsconfig.json'],
clean: true,
@@ -16,10 +16,10 @@ export default defineConfig({
platform: 'node',
splitting: true,
define: {
__VERSION__: `"${createRequire(import.meta.url)('./package.json').version}"`
__VERSION__: `"${createRequire(import.meta.url)('./package.json').version}"`,
},
loader: {
'.json': 'file'
'.json': 'file',
},
...shared,
});