@whook/whook
Version:
Build strong and efficient REST web services.
162 lines (147 loc) • 5.5 kB
text/typescript
import { exit, stderr } from 'node:process';
import { readFile, writeFile } from 'node:fs/promises';
import { join, relative } from 'node:path';
import { pathToFileURL } from 'node:url';
import { mkdirp } from 'mkdirp';
import { Knifecycle, constant, initInitializerBuilder } from 'knifecycle';
import initCompiler, { DEFAULT_COMPILER_OPTIONS } from './services/compiler.js';
import initProxiedENV from './services/PROXIED_ENV.js';
import initBuildAutoloader from './services/_buildAutoload.js';
import { printStackTrace } from 'yerror';
import { type BuildInitializer } from 'knifecycle';
import {
type WhookCompilerOptions,
type WhookCompilerService,
} from './services/compiler.js';
import { type LogService } from 'common-services';
import { parseArgs } from './libs/args.js';
export const DEFAULT_BUILD_DIR = 'server';
export const DEFAULT_BUILD_INITIALIZER_PATH_MAP = {
$fatalError: 'knifecycle/dist/fatalError.js',
BASE_URL: '@whook/whook/dist/services/BASE_URL.js',
DEFINITIONS: '@whook/whook/dist/services/DEFINITIONS.js',
logger: '@whook/whook/dist/services/logger.js',
exit: '@whook/whook/dist/services/exit.js',
PORT: '@whook/whook/dist/services/PORT.js',
HOST: '@whook/whook/dist/services/HOST.js',
WHOOK_RESOLVED_PLUGINS:
'@whook/whook/dist/services/WHOOK_RESOLVED_PLUGINS.js',
httpRouter: '@whook/whook/dist/services/httpRouter.js',
httpTransaction: '@whook/whook/dist/services/httpTransaction.js',
httpServer: '@whook/whook/dist/services/httpServer.js',
apm: '@whook/whook/dist/services/apm.js',
obfuscator: '@whook/whook/dist/services/obfuscator.js',
errorHandler: '@whook/whook/dist/services/errorHandler.js',
APP_CONFIG: 'application-services/dist/services/APP_CONFIG.js',
PROJECT_DIR: 'application-services/dist/services/PROJECT_DIR.js',
PROCESS_ENV: 'application-services/dist/services/PROCESS_ENV.js',
process: 'application-services/dist/services/process.js',
ENV: 'application-services/dist/services/ENV.js',
log: 'common-services/dist/services/log.js',
time: 'common-services/dist/services/time.js',
delay: 'common-services/dist/services/delay.js',
random: 'common-services/dist/services/random.js',
importer: 'common-services/dist/services/importer.js',
resolve: 'common-services/dist/services/resolve.js',
};
export async function prepareBuildEnvironment<T extends Knifecycle>(
$: T = new Knifecycle() as T,
): Promise<T> {
$.register(initBuildAutoloader);
$.register(initInitializerBuilder);
$.register(initCompiler);
$.register(initProxiedENV);
$.register(
constant('INITIALIZER_PATH_MAP', DEFAULT_BUILD_INITIALIZER_PATH_MAP),
);
$.register(constant('args', parseArgs([])));
$.register(constant('BUILD_DIR', DEFAULT_BUILD_DIR));
$.register(constant('COMPILER_OPTIONS', DEFAULT_COMPILER_OPTIONS));
return $;
}
export async function runBuild(
aPrepareBuildEnvironment: typeof prepareBuildEnvironment,
): Promise<void> {
try {
const $ = await aPrepareBuildEnvironment();
const {
APP_ENV,
PROJECT_DIR,
BUILD_DIR,
COMPILER_OPTIONS,
compiler,
log,
buildInitializer,
}: {
APP_ENV: string;
PROJECT_DIR: string;
BUILD_DIR: string;
COMPILER_OPTIONS: WhookCompilerOptions;
compiler: WhookCompilerService;
log: LogService;
buildInitializer: BuildInitializer;
} = await $.run([
'APP_ENV',
'PROJECT_DIR',
'BUILD_DIR',
'COMPILER_OPTIONS',
'compiler',
'log',
'buildInitializer',
'process',
]);
log('info', 'Build environment initialized 🚀🌕');
const distPath = join(PROJECT_DIR, 'dist');
const srcPath = join(PROJECT_DIR, 'src');
const buildPath = join(PROJECT_DIR, 'builds', APP_ENV, BUILD_DIR);
const srcRelativePath = relative(buildPath, srcPath);
const initializerContent = (
await buildInitializer(['httpServer', 'process'])
).replaceAll(pathToFileURL(srcPath).toString(), srcRelativePath);
const indexContent = await buildIndex();
await mkdirp(distPath);
await mkdirp(buildPath);
await Promise.all([
ensureFile({ log }, join(buildPath, 'initialize.js'), initializerContent),
ensureFile({ log }, join(buildPath, 'main.js'), indexContent),
]);
const entryPoint = `${buildPath}/main.js`;
const { contents, mappings } = await compiler(entryPoint, COMPILER_OPTIONS);
await Promise.all([
ensureFile({ log }, `${buildPath}/start.js`, contents),
mappings
? ensureFile({ log }, `${buildPath}/start.js.map`, mappings)
: Promise.resolve(),
]);
} catch (err) {
stderr.write(
`💀 - Cannot launch the build: ${printStackTrace(err as Error)}`,
);
exit(1);
}
}
async function ensureFile(
{ log }: { log: LogService },
path: string,
content: string,
): Promise<void> {
try {
const oldContent = (await readFile(path)).toString();
if (oldContent === content) {
log('debug', `🗀 - Ignore unchanged file: "${path}".`);
return;
}
} catch (err) {
log('debug', `🗀 - Write new file: "${path}".`);
log('debug-stack', printStackTrace(err as Error));
return await writeFile(path, content);
}
log('debug', `🗀 - Write changed file: "${path}".`);
return await writeFile(path, content);
}
async function buildIndex(): Promise<string> {
return `// Built with \`@whook\`, do not edit in place!
import { initialize } from './initialize.js';
await initialize();
`;
}