UNPKG

@sern/cli

Version:

Official CLI for @sern/handler

259 lines (256 loc) 9.1 kB
import { require } from '../chunk-S2QRDR2W.js'; import { getConfig } from '../chunk-PZE2TMOK.js'; import { parseTsConfig } from '../chunk-LLD33MCO.js'; import esbuild from 'esbuild'; import p from 'node:path'; import { glob } from 'glob'; import { configDotenv } from 'dotenv'; import assert from 'node:assert'; import { resolve } from 'path'; import { pathExistsSync, pathExists } from 'find-up'; import { mkdir, writeFile } from 'fs/promises'; import { magentaBright, bold } from 'colorette'; import { execa } from 'execa'; import { InvalidArgumentError } from 'commander'; var defaultEsbuildConfig_default = (format, tsconfig, outdir = "dist") => ({ platform: "node", format, tsconfig, logLevel: "info", minify: false, outdir: resolve(outdir) }); // src/utilities/preprocessor.ts var declareConstType = (name, type) => String.raw`declare var ${name}: ${type}`; var processEnvType = (env) => { const entries = Object.keys(env); const envBuilder = new StringWriter(); for (const key of entries) { envBuilder.tab().tab().envField(key); } return envBuilder.build(); }; var determineJSONType = (s) => { return typeof JSON.parse(s); }; var writeAmbientFile = async (path, define, writeFile2) => { 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 writeFile2(path, fileContent.build(), "utf8"); }; var writeTsConfig = async (format, configPath, fw) => { 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: "esnext", rootDirs: ["./generated", "../src"] }, include: ["./ambient.d.ts", "../src"] }; await fw(configPath, JSON.stringify(sernTsConfig, null, 3), "utf8"); }; var StringWriter = class { fileString = ""; tab() { this.fileString += " "; return this; } varDecl(name, type) { this.fileString += declareConstType(name, determineJSONType(type)) + "\n"; return this; } println(data) { this.fileString += data + "\n"; return this; } envField(key) { if (/\s|\(|\)/g.test(key)) { this.fileString += `"${key}": string`; } else { this.fileString += key + ":string"; } this.fileString += "\n"; return this; } envFields(env) { this.fileString += processEnvType(env); return this; } build() { return this.fileString; } }; var VALID_EXTENSIONS = [".ts", ".js"]; var CommandHandlerPlugin = (buildConfig, ambientFilePath, sernTsConfigPath) => { return { name: "commandHandler", setup(build2) { const options = build2.initialOptions; const defVersion = () => JSON.stringify(require(p.resolve("package.json")).version); options.define = { ...buildConfig.define ?? {}, __DEV__: `${buildConfig.mode === "development"}`, __PROD__: `${buildConfig.mode === "production"}`, __VERSION__: `${buildConfig.defineVersion ? `${defVersion()}` : "undefined"}` }; writeTsConfig(buildConfig.format, sernTsConfigPath, writeFile); writeAmbientFile(ambientFilePath, options.define, writeFile); } }; }; var CommandOnEndPlugin = (watching, watchCommand) => { let isFirstBuild = true; let currentProcess = null; let restartTimeout = null; return { name: "watchRunCommand", setup(build2) { build2.onEnd(async (result) => { if (!watching || result.errors.length !== 0) return; if (isFirstBuild === true) { isFirstBuild = false; return; } if (watchCommand === "") { console.log("[watch] no command provided, skipping"); return; } if (currentProcess) { console.log("[watch] stopping previous process..."); currentProcess.cancel(); currentProcess = null; } const cmd = watchCommand || (() => { if (pathExistsSync("package-lock.json")) return "npm start"; if (pathExistsSync("yarn.lock")) return "yarn start"; if (pathExistsSync("pnpm-lock.yaml")) return "pnpm start"; if (pathExistsSync("bun.lockb")) return "bun start"; if (pathExistsSync("bun.lock")) return "bun start"; throw new Error("[watch] default package manager start command not found"); })(); if (restartTimeout) clearTimeout(restartTimeout); console.log("[watch] debouncing command for 1.5 seconds..."); restartTimeout = setTimeout(() => { console.log(`[watch] running command: ${cmd}`); currentProcess = execa(cmd, { stdio: "inherit", shell: true }); currentProcess.catch((error) => { if (error.isCanceled) return; console.error(`[watch] command execution error: ${error.message}`); }); }, 1500); }); } }; }; var resolveBuildConfig = (path, language) => { if (language === "javascript") { return path ?? "jsconfig.json"; } return path ?? "tsconfig.json"; }; async function build(options) { if (!options.supressWarnings) { console.info(`${magentaBright("EXPERIMENTAL")}: This API has not been stabilized. add -W or --suppress-warnings flag to suppress`); } if (!options.watch && options.watchCommand) { throw new InvalidArgumentError("enable watch to use --watch-command"); } const sernConfig = await getConfig(); let buildConfig; const buildConfigPath = p.resolve(options.project ?? "sern.build.js"); const defaultBuildConfig = { defineVersion: true, format: options.format ?? "esm", mode: options.mode ?? "development", dropLabels: [], sourcemap: options.sourceMaps, tsconfig: resolveBuildConfig(options.tsconfig, sernConfig.language), env: options.env ?? ".env", include: [], watch: { command: options.watchCommand } }; if (pathExistsSync(buildConfigPath)) { let fileConfig; try { fileConfig = await import("file:///" + buildConfigPath).then((r) => r.default); } catch (e) { console.error("Could not find buildConfigPath"); throw e; } buildConfig = { ...defaultBuildConfig, ...fileConfig }; } else { buildConfig = defaultBuildConfig; console.log("No build config found, defaulting"); } configDotenv({ path: buildConfig.env }); if (process.env.NODE_ENV) { buildConfig.mode = process.env.NODE_ENV; console.log(magentaBright("NODE_ENV:"), "Found NODE_ENV variable, setting `mode` to this."); } assert(buildConfig.mode === "development" || buildConfig.mode === "production", "NODE_ENV is not `production` or `development`"); try { let config = await parseTsConfig(buildConfig.tsconfig); config?.extends && console.warn("Extend the generated tsconfig"); } catch (e) { console.error("no tsconfig / jsconfig found"); console.error(`Please create a ${sernConfig.language === "javascript" ? "jsconfig.json" : "tsconfig.json"}`); console.error('It should have at least extend the generated one sern makes.\n { "extends": "./.sern/tsconfig.json" }'); 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); console.log(" ", magentaBright("sourceMaps"), buildConfig.sourcemap); const sernDir = p.resolve(".sern"), [ambientFilePath, sernTsConfigPath, genDir] = ( // resolves the file paths in the .sern dir ["ambient.d.ts", "tsconfig.json", "generated"].map((f) => p.resolve(sernDir, f)) ); if (!await pathExists(genDir)) { console.log("Making .sern/generated dir, does not exist"); await mkdir(genDir, { recursive: true }); } const entryPoints = await glob(`src/**/*{${VALID_EXTENSIONS.join(",")}}`, { ignore: { ignored: (p2) => p2.name.endsWith(".d.ts") } }); const ctx = await esbuild.context({ entryPoints, plugins: [ CommandHandlerPlugin(buildConfig, ambientFilePath, sernTsConfigPath), CommandOnEndPlugin(options.watch, buildConfig.watch?.command) ], sourcemap: buildConfig.sourcemap, ...defaultEsbuildConfig_default(buildConfig.format, buildConfig.tsconfig), dropLabels: [buildConfig.mode === "production" ? "__DEV__" : "__PROD__", ...buildConfig.dropLabels] }); await ctx.rebuild(); if (options.watch) { await ctx.watch(); } else { await ctx.dispose(); } } export { build }; //# sourceMappingURL=out.js.map //# sourceMappingURL=build.js.map