@sern/cli
Version:
Official CLI for @sern/handler
259 lines (256 loc) • 9.1 kB
JavaScript
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