-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
39 changed files
with
4,080 additions
and
3,341 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
// @ts-check | ||
|
||
import { readFile, writeFile } from 'node:fs/promises'; | ||
import { join } from 'node:path'; | ||
import { build } from 'tsup'; | ||
import { Colors, erase, findCommandKitConfig, panic, write } from './common.mjs'; | ||
import ora from 'ora'; | ||
|
||
export async function bootstrapProductionBuild(config) { | ||
const { | ||
sourcemap = false, | ||
minify = false, | ||
outDir = 'dist', | ||
antiCrash = true, | ||
src, | ||
main, | ||
requirePolyfill: polyfillRequire, | ||
} = await findCommandKitConfig(config); | ||
|
||
const status = ora('Creating optimized production build...\n').start(); | ||
const start = performance.now(); | ||
|
||
erase(outDir); | ||
|
||
try { | ||
await build({ | ||
clean: true, | ||
format: ['esm'], | ||
dts: false, | ||
skipNodeModulesBundle: true, | ||
minify, | ||
shims: true, | ||
banner: { | ||
js: '/* Optimized production build generated by CommandKit */', | ||
}, | ||
sourcemap, | ||
keepNames: true, | ||
outDir, | ||
silent: true, | ||
watch: false, | ||
entry: [src, '!dist', '!.commandkit', `!${outDir}`], | ||
}); | ||
|
||
await injectShims(outDir, main, antiCrash, polyfillRequire); | ||
|
||
status.succeed( | ||
Colors.green(`Build completed in ${(performance.now() - start).toFixed(2)}ms!`), | ||
); | ||
write( | ||
Colors.green( | ||
`\nRun ${Colors.magenta(`commandkit start`)} ${Colors.green('to start your bot.')}`, | ||
), | ||
); | ||
} catch (e) { | ||
status.fail(`Build failed after ${(performance.now() - start).toFixed(2)}ms!`); | ||
panic(e); | ||
} | ||
} | ||
|
||
export async function injectShims(outDir, main, antiCrash, polyfillRequire) { | ||
const path = join(process.cwd(), outDir, main); | ||
|
||
const head = ['\n\n;await (async()=>{', " 'use strict';"].join('\n'); | ||
const tail = '\n})();'; | ||
const requireScript = polyfillRequire | ||
? [ | ||
'// --- CommandKit require() polyfill ---', | ||
' if (typeof require === "undefined") {', | ||
' const { createRequire } = await import("node:module");', | ||
' const __require = createRequire(import.meta.url);', | ||
' Object.defineProperty(globalThis, "require", {', | ||
' value: (id) => {', | ||
' return __require(id);', | ||
' },', | ||
' configurable: true,', | ||
' enumerable: false,', | ||
' writable: true,', | ||
' });', | ||
' }', | ||
'// --- CommandKit require() polyfill ---', | ||
].join('\n') | ||
: ''; | ||
|
||
const antiCrashScript = antiCrash | ||
? [ | ||
'// --- CommandKit Anti-Crash Monitor ---', | ||
" // 'uncaughtException' event is supposed to be used to perform synchronous cleanup before shutting down the process", | ||
' // instead of using it as a means to resume operation.', | ||
' // But it exists here due to compatibility reasons with discord bot ecosystem.', | ||
" const p = (t) => `\\x1b[33m${t}\\x1b[0m`, b = '[CommandKit Anti-Crash Monitor]', l = console.log, e1 = 'uncaughtException', e2 = 'unhandledRejection';", | ||
' if (!process.eventNames().includes(e1)) // skip if it is already handled', | ||
' process.on(e1, (e) => {', | ||
' l(p(`${b} Uncaught Exception`)); l(p(b), p(e.stack || e));', | ||
' })', | ||
' if (!process.eventNames().includes(e2)) // skip if it is already handled', | ||
' process.on(e2, (r) => {', | ||
' l(p(`${b} Unhandled promise rejection`)); l(p(`${b} ${r.stack || r}`));', | ||
' });', | ||
'// --- CommandKit Anti-Crash Monitor ---', | ||
].join('\n') | ||
: ''; | ||
|
||
const contents = await readFile(path, 'utf-8'); | ||
const finalScript = [head, requireScript, antiCrashScript, tail, '\n\n', contents].join('\n'); | ||
|
||
return writeFile(path, finalScript); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
// @ts-check | ||
|
||
import { rimrafSync } from 'rimraf'; | ||
import { join } from 'node:path'; | ||
import fs from 'node:fs'; | ||
|
||
const resetColor = '\x1b[0m'; | ||
|
||
export const Colors = { | ||
reset: (text) => `${text}${resetColor}`, | ||
bright: (text) => `\x1b[1m${text}${resetColor}`, | ||
dim: (text) => `\x1b[2m${text}${resetColor}`, | ||
underscore: (text) => `\x1b[4m${text}${resetColor}`, | ||
blink: (text) => `\x1b[5m${text}${resetColor}`, | ||
reverse: (text) => `\x1b[7m${text}${resetColor}`, | ||
hidden: (text) => `\x1b[8m${text}${resetColor}`, | ||
|
||
black: (text) => `\x1b[30m${text}${resetColor}`, | ||
red: (text) => `\x1b[31m${text}${resetColor}`, | ||
green: (text) => `\x1b[32m${text}${resetColor}`, | ||
yellow: (text) => `\x1b[33m${text}${resetColor}`, | ||
blue: (text) => `\x1b[34m${text}${resetColor}`, | ||
magenta: (text) => `\x1b[35m${text}${resetColor}`, | ||
cyan: (text) => `\x1b[36m${text}${resetColor}`, | ||
white: (text) => `\x1b[37m${text}${resetColor}`, | ||
|
||
bgBlack: (text) => `\x1b[40m${text}${resetColor}`, | ||
bgRed: (text) => `\x1b[41m${text}${resetColor}`, | ||
bgGreen: (text) => `\x1b[42m${text}${resetColor}`, | ||
bgYellow: (text) => `\x1b[43m${text}${resetColor}`, | ||
bgBlue: (text) => `\x1b[44m${text}${resetColor}`, | ||
bgMagenta: (text) => `\x1b[45m${text}${resetColor}`, | ||
bgCyan: (text) => `\x1b[46m${text}${resetColor}`, | ||
bgWhite: (text) => `\x1b[47m${text}${resetColor}`, | ||
}; | ||
|
||
export function write(message) { | ||
process.stdout.write(message); | ||
process.stdout.write('\n'); | ||
} | ||
|
||
/** | ||
* @returns {never} | ||
*/ | ||
export function panic(message) { | ||
write(Colors.red(`Error: ${message}`)); | ||
process.exit(1); | ||
} | ||
|
||
export function findPackageJSON() { | ||
const cwd = process.cwd(); | ||
const target = join(cwd, 'package.json'); | ||
|
||
if (!fs.existsSync(target)) { | ||
panic('Could not find package.json in current directory.'); | ||
} | ||
|
||
return JSON.parse(fs.readFileSync(target, 'utf8')); | ||
} | ||
|
||
const possibleFileNames = [ | ||
'commandkit.json', | ||
'commandkit.config.json', | ||
'commandkit.js', | ||
'commandkit.config.js', | ||
'commandkit.mjs', | ||
'commandkit.config.mjs', | ||
'commandkit.cjs', | ||
'commandkit.config.cjs', | ||
'commandkit.ts', | ||
'commandkit.mts', | ||
'commandkit.cts', | ||
]; | ||
|
||
export async function findCommandKitConfig(src) { | ||
const cwd = process.cwd(); | ||
const locations = src ? [join(cwd, src)] : possibleFileNames.map((name) => join(cwd, name)); | ||
|
||
for (const location of locations) { | ||
try { | ||
return await loadConfigInner(location); | ||
} catch (e) { | ||
continue; | ||
} | ||
} | ||
|
||
panic(`Could not locate commandkit config from ${cwd}`); | ||
} | ||
|
||
|
||
function ensureTypeScript(target) { | ||
const isTypeScript = /\.(c|m)tsx?$/.test(target); | ||
|
||
if (isTypeScript && !process.features.typescript) { | ||
panic('You are trying to load commandkit config file that is written in typescript. The current Node.js version does not have TypeScript feature enabled.'); | ||
} | ||
} | ||
|
||
async function loadConfigInner(target) { | ||
const isJSON = target.endsWith('.json'); | ||
|
||
await ensureExists(target); | ||
|
||
ensureTypeScript(target); | ||
|
||
/** | ||
* @type {import('..').CommandKitConfig} | ||
*/ | ||
const config = await import(`file://${target}`, { | ||
assert: isJSON ? { type: 'json' } : undefined, | ||
}).then((conf) => conf.default || conf); | ||
|
||
return config; | ||
} | ||
|
||
async function ensureExists(loc) { | ||
await fs.promises.access(loc, fs.constants.F_OK); | ||
} | ||
|
||
export function erase(dir) { | ||
rimrafSync(dir); | ||
} |
Oops, something went wrong.