@nodecg/types
Version:
Dynamic broadcast graphics rendered in a browser
369 lines (354 loc) • 9.44 kB
text/typescript
import { argv } from "yargs";
import { z } from "zod";
import { LogLevels } from "./logger-interface";
const argvSchema = z.object({
bundlesEnabled: z
.string()
.optional()
.transform((val) => val?.split(",")),
bundlesDisabled: z
.string()
.optional()
.transform((val) => val?.split(",")),
bundlesPaths: z
.string()
.optional()
.transform((val) => val?.split(",")),
});
const parsedArgv = argvSchema.parse(argv);
export const nodecgConfigSchema = z
.object({
host: z
.string()
.default("0.0.0.0")
.describe("The IP address or hostname that NodeCG should bind to."),
port: z
.number()
.int()
.positive()
.default(9090)
.describe("The port that NodeCG should listen on."),
baseURL: z
.string()
.optional()
.describe(
"The URL of this instance. Used for things like cookies. Defaults to HOST:PORT. If you use a reverse proxy, you'll likely need to set this value.",
),
exitOnUncaught: z
.boolean()
.default(true)
.describe("Whether or not to exit on uncaught exceptions."),
logging: z
.object({
console: z
.object({
enabled: z
.boolean()
.default(true)
.describe("Whether to enable console logging."),
level: z
.enum(LogLevels)
.default("info")
.describe("The log level to use."),
timestamps: z
.boolean()
.default(true)
.describe("Whether to add timestamps to the console logging."),
replicants: z
.boolean()
.default(false)
.describe(
"Whether to enable logging of the Replicants subsystem. Very spammy.",
),
})
.default({ enabled: true }),
file: z
.object({
enabled: z
.boolean()
.default(false)
.describe("Whether to enable file logging."),
level: z
.enum(LogLevels)
.default("info")
.describe("The log level to use."),
path: z
.string()
.default("logs/nodecg.log")
.describe("The filepath to log to."),
timestamps: z
.boolean()
.default(true)
.describe("Whether to add timestamps to the file logging."),
replicants: z
.boolean()
.default(false)
.describe(
"Whether to enable logging of the Replicants subsystem. Very spammy.",
),
})
.default({ enabled: false }),
})
.default({ console: {} }),
bundles: z
.object({
enabled: z
.array(z.string())
.nullable()
.default(parsedArgv.bundlesEnabled ?? null)
.describe("A whitelist array of bundle names."),
disabled: z
.array(z.string())
.nullable()
.default(parsedArgv.bundlesDisabled ?? null)
.describe("A blacklist array of bundle names."),
paths: z
.array(z.string())
.default(parsedArgv.bundlesPaths ?? [])
.describe("An array of additional paths where bundles are located."),
})
.default({
enabled: null,
disabled: null,
paths: [],
}),
login: z
.object({
enabled: z
.boolean()
.default(false)
.describe("Whether to enable login security."),
sessionSecret: z
.string()
.optional()
.describe("The secret used to salt sessions."),
forceHttpsReturn: z
.boolean()
.default(false)
.describe(
"Forces Steam & Twitch login return URLs to use HTTPS instead of HTTP. Useful in reverse proxy setups.",
),
steam: z
.object({
enabled: z
.boolean()
.default(false)
.describe("Whether to enable Steam authentication."),
apiKey: z
.string()
.optional()
.describe(
"A Steam API Key. Obtained from http://steamcommunity.com/dev/apikey",
),
allowedIds: z
.array(z.string())
.default([])
.describe(
"Which 64 bit Steam IDs to allow. Can be obtained from https://steamid.io/",
),
})
.optional()
.refine(
(val) => (val?.enabled ? typeof val.apiKey === "string" : true),
{
message: '"login.steam.apiKey" must be a string',
},
),
twitch: z
.object({
enabled: z
.boolean()
.default(false)
.describe("Whether to enable Twitch authentication."),
clientID: z
.string()
.optional()
.describe(
"A Twitch application ClientID http://twitch.tv/kraken/oauth2/clients/new",
),
clientSecret: z
.string()
.optional()
.describe(
"A Twitch application ClientSecret http://twitch.tv/kraken/oauth2/clients/new",
),
scope: z
.string()
.default("user_read")
.describe(
"A space-separated string of Twitch application permissions.",
),
allowedUsernames: z
.array(z.string())
.default([])
.describe("Which Twitch usernames to allow."),
allowedIds: z
.array(z.string())
.default([])
.describe(
"Which Twitch IDs to allow. Can be obtained from https://twitchinsights.net/checkuser",
),
})
.optional()
.refine(
(val) => (val?.enabled ? typeof val.clientID === "string" : true),
{
message: '"login.twitch.clientID" must be a string',
},
)
.refine(
(val) =>
val?.enabled ? typeof val.clientSecret === "string" : true,
{
message: '"login.twitch.clientID" must be a string',
},
),
discord: z
.object({
enabled: z
.boolean()
.default(false)
.describe("Whether to enable Discord authentication."),
clientID: z
.string()
.optional()
.describe(
"A Discord application ClientID https://discord.com/developers/applications",
),
clientSecret: z
.string()
.optional()
.describe(
"A Discord application ClientSecret https://discord.com/developers/applications",
),
scope: z
.string()
.default("identify")
.describe(
"A space-separated string of Discord application scopes. https://discord.com/developers/docs/topics/oauth2#shared-resources-oauth2-scopes",
),
allowedUserIDs: z
.array(z.string())
.default([])
.describe("Which Discord user IDs to allow."),
allowedGuilds: z
.array(
z.object({
guildID: z
.string()
.describe(
"Users in this Discord Server are allowed to log in.",
),
allowedRoleIDs: z
.array(z.string())
.default([])
.describe(
"Additionally require one of the roles on the server to log in.",
),
guildBotToken: z
.string()
.default("")
.describe(
"Discord bot token, needed if allowedRoleIDs is used.",
),
}),
)
.default([]),
})
.optional()
.refine(
(val) => (val?.enabled ? typeof val.clientID === "string" : true),
{
message: '"login.discord.clientID" must be a string',
},
)
.refine(
(val) =>
val?.enabled ? typeof val.clientSecret === "string" : true,
{
message: '"login.discord.clientSecret" must be a string',
},
),
local: z
.object({
enabled: z
.boolean()
.default(false)
.describe("Enable Local authentication."),
allowedUsers: z
.array(
z.object({
username: z.string(),
password: z.string(),
}),
)
.default([])
.describe("Which users can log in."),
})
.optional(),
})
.refine(
(val) => (val.enabled ? typeof val.sessionSecret === "string" : true),
{
message: '"login.sessionSecret" must be a string',
},
)
.default({ enabled: false }),
ssl: z
.object({
enabled: z
.boolean()
.default(false)
.describe("Whether to enable SSL/HTTPS encryption."),
allowHTTP: z
.boolean()
.default(false)
.describe(
"Whether to allow insecure HTTP connections while SSL is active.",
),
keyPath: z.string().optional().describe("The path to an SSL key file."),
certificatePath: z
.string()
.optional()
.describe("The path to an SSL certificate file."),
passphrase: z
.string()
.optional()
.describe("The passphrase for the provided key file."),
})
.default({ enabled: false })
.refine((val) => (val.enabled ? typeof val.keyPath === "string" : true), {
message: '"ssl.keyPath" must be a string',
})
.refine(
(val) => (val.enabled ? typeof val.certificatePath === "string" : true),
{
message: '"ssl.certificatePath" must be a string',
},
),
sentry: z
.object({
enabled: z
.boolean()
.default(true)
.describe("Whether to enable Sentry error reporting."),
dsn: z
.string()
.optional()
.describe(
"Your project's DSN, used to route alerts to the correct place.",
),
})
.default({ enabled: false })
.refine((val) => (val.enabled ? typeof val.dsn === "string" : true), {
message: '"sentry.dsn" must be a string',
}),
})
.transform((val) => {
const host = val.host === "0.0.0.0" ? "localhost" : val.host;
return {
...val,
baseURL: val.baseURL ?? `${host}:${val.port}`,
};
});
export type NodeCGConfig = z.infer<typeof nodecgConfigSchema>;