180 lines
4.4 KiB
TypeScript
180 lines
4.4 KiB
TypeScript
const NGX_VERSION = "0.2.1";
|
|
|
|
type ConfigNode = ConfigStatement | ConfigBlock | ConfigBreak | ConfigFile;
|
|
|
|
class ConfigBuildable {
|
|
build(): string {
|
|
throw new Error("build(..) not implemented!");
|
|
}
|
|
}
|
|
|
|
class ConfigBlock extends ConfigBuildable {
|
|
value: string;
|
|
children: ConfigNode[];
|
|
|
|
constructor(value: string, children: ConfigNode[]) {
|
|
super();
|
|
|
|
this.value = value;
|
|
this.children = children;
|
|
}
|
|
|
|
override build(): string {
|
|
let output = this.value;
|
|
output += " {\n ";
|
|
output += this.children
|
|
.map((child) => child.build().split("\n").join("\n "))
|
|
.join("\n ");
|
|
output += "\n}";
|
|
return output;
|
|
}
|
|
}
|
|
|
|
class ConfigStatement extends ConfigBuildable {
|
|
value: string;
|
|
constructor(value: string) {
|
|
super();
|
|
|
|
this.value = value;
|
|
}
|
|
|
|
override build(): string {
|
|
return this.value + ";";
|
|
}
|
|
}
|
|
|
|
class ConfigBreak extends ConfigBuildable {
|
|
override build(): string {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
class ConfigFile extends ConfigBuildable {
|
|
nodes: ConfigNode[];
|
|
constructor(nodes: ConfigNode[]) {
|
|
super();
|
|
|
|
this.nodes = nodes;
|
|
}
|
|
|
|
override build(): string {
|
|
return this.nodes.map((n) => n.build()).join("\n");
|
|
}
|
|
}
|
|
|
|
type LooseConfigNode = ConfigNode | string | LooseConfigNode[];
|
|
|
|
function conform(looseNode: LooseConfigNode): ConfigNode[] {
|
|
if (typeof looseNode === "string") {
|
|
return [new ConfigStatement(looseNode)];
|
|
}
|
|
if (typeof looseNode === "object" && looseNode instanceof Array) {
|
|
if (looseNode.length === 0) {
|
|
return [new ConfigBreak()];
|
|
} else if (looseNode.length === 1) {
|
|
return conform(looseNode[0]);
|
|
} else {
|
|
return looseNode
|
|
.map((n) => conform(n))
|
|
.reduceRight((b, a) =>
|
|
a.length === 1 ? [...a, ...b] : [...a, new ConfigBreak(), ...b],
|
|
);
|
|
}
|
|
}
|
|
return [looseNode];
|
|
}
|
|
|
|
/**
|
|
* create nginx config nodes. behavior depends on arguments:
|
|
*
|
|
* - no args - ConfigBreak
|
|
* - value only - ConfigStatement
|
|
* - value + children - ConfigBlock
|
|
* - children only - ConfigFile
|
|
*
|
|
* accept strings, ConfigNodes, or nested arrays that flatten automatically.
|
|
*
|
|
* ```ts
|
|
* ngx() // ConfigBreak
|
|
* ngx("worker_processes auto") // ConfigStatement
|
|
* ngx("location /", ["proxy_pass http://backend"]) // ConfigBlock
|
|
* ngx(["server { ... }", "server { ... }"]) // ConfigFile
|
|
* ```
|
|
*/
|
|
export function ngx(value?: string, children?: LooseConfigNode[]): ConfigNode {
|
|
const hasValue = value !== undefined && value !== "";
|
|
const hasChildren = children !== undefined;
|
|
|
|
if (!hasValue && !hasChildren) {
|
|
return new ConfigBreak();
|
|
} else if (hasValue && !hasChildren) {
|
|
return new ConfigStatement(value);
|
|
} else if (hasValue && hasChildren) {
|
|
return new ConfigBlock(value, conform(children));
|
|
} else if (!hasValue && hasChildren) {
|
|
return new ConfigFile(conform(children));
|
|
}
|
|
|
|
throw new Error("unreachable");
|
|
}
|
|
|
|
/**
|
|
* create ssl listen directives for ipv4/ipv6 with http/2 enabled.
|
|
*
|
|
* extras modify the listen directives.
|
|
*
|
|
* ```ts
|
|
* listen() // listen 443 ssl, listen [::]:443 ssl, http2 on
|
|
* listen("default_server") // includes "default_server"
|
|
* ```
|
|
*/
|
|
export const listen = (...extras: string[]) =>
|
|
conform([
|
|
`listen 443 ${["ssl", ...extras].join(" ")}`,
|
|
`listen [::]:443 ${["ssl", ...extras].join(" ")}`,
|
|
`http2 on`,
|
|
]);
|
|
|
|
/**
|
|
* create a server_name nginx directive.
|
|
*
|
|
* ```ts
|
|
* serverName("example.com") // "server_name example.com;"
|
|
* ```
|
|
*/
|
|
export const serverName = (name: string) =>
|
|
new ConfigStatement(`server_name ${name}`);
|
|
|
|
/**
|
|
* create ssl certificate directives for let's encrypt certificates.
|
|
*
|
|
* ```ts
|
|
* letsEncrypt("example.com")
|
|
* // ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem
|
|
* // ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem
|
|
* ```
|
|
*/
|
|
export const letsEncrypt = (
|
|
domain: string,
|
|
liveDir = "/etc/letsencrypt/live",
|
|
) =>
|
|
conform([
|
|
`ssl_certificate ${liveDir}/${domain}/fullchain.pem`,
|
|
`ssl_certificate_key ${liveDir}/${domain}/privkey.pem`,
|
|
]);
|
|
|
|
// the default export is both the ngx function and a namespace:
|
|
/**
|
|
* use as a function to create config nodes, or access helpers.
|
|
*
|
|
* ```ts
|
|
* import ngx from './ngx';
|
|
* const config = ngx("location /", ["proxy_pass http://backend"]);
|
|
* console.log(ngx.NGX_VERSION);
|
|
* const ssl = ngx.letsEncrypt("example.com");
|
|
* ```
|
|
*/
|
|
export default Object.assign(
|
|
(value?: string, children?: LooseConfigNode[]) => ngx(value, children),
|
|
{ NGX_VERSION, listen, letsEncrypt, serverName },
|
|
);
|