chore: add prettier

This commit is contained in:
Orion Kindel 2023-06-11 21:17:02 -05:00
parent 4b9a7613fa
commit 5f84009b61
Signed by untrusted user who does not match committer: orion
GPG Key ID: 6D4165AE4C928719
6 changed files with 186 additions and 144 deletions

7
.prettierrc Normal file
View File

@ -0,0 +1,7 @@
{
"trailingComma": "all",
"singleQuote": true,
"semi": false,
"arrowParens": "avoid",
"parser": "babel"
}

View File

@ -6,9 +6,16 @@
"repository": "git@git.orionkindel.com:dnim/srv.git", "repository": "git@git.orionkindel.com:dnim/srv.git",
"author": "Orion Kindel <cakekindel@gmail.com>", "author": "Orion Kindel <cakekindel@gmail.com>",
"license": "MIT", "license": "MIT",
"scripts": {
"fmt:check": "prettier --check 'src/**/*.js'",
"fmt": "prettier --write 'src/**/*.js'"
},
"dependencies": { "dependencies": {
"@matchbook/ts": "^1.0.0", "@matchbook/ts": "^1.0.0",
"fp-ts": "^2.16.0", "fp-ts": "^2.16.0",
"yaml": "^2.3.1" "yaml": "^2.3.1"
},
"devDependencies": {
"prettier": "^2.8.8"
} }
} }

View File

@ -1,34 +1,34 @@
const Either = require("fp-ts/Either"); const Either = require('fp-ts/Either')
const { pipe, identity: id } = require("fp-ts/function"); const { pipe, identity: id } = require('fp-ts/function')
const cp = require("child_process"); const cp = require('child_process')
const run = (script) => const run = script =>
pipe( pipe(
Either.tryCatch( Either.tryCatch(
() => cp.spawnSync(script, { encoding: "utf8", shell: true }), () => cp.spawnSync(script, { encoding: 'utf8', shell: true }),
id id,
), ),
Either.flatMap((o) => (o.error ? Either.left(o.error) : Either.right(o))), Either.flatMap(o => (o.error ? Either.left(o.error) : Either.right(o))),
Either.flatMap((o) => Either.flatMap(o =>
o.status > 0 o.status > 0
? Either.left( ? Either.left(
new Error(JSON.stringify({ reason: "status_gt_0", ...o })) new Error(JSON.stringify({ reason: 'status_gt_0', ...o })),
) )
: Either.right(o) : Either.right(o),
), ),
Either.map((o) => o.stdout.trim()) Either.map(o => o.stdout.trim()),
); )
const ignoringStatus = Either.orElse((e) => const ignoringStatus = Either.orElse(e =>
pipe( pipe(
Either.tryCatch(() => JSON.parse(e.message), id), Either.tryCatch(() => JSON.parse(e.message), id),
Either.flatMap((o) => Either.flatMap(o =>
o.reason && o.reason === "status_gt_0" o.reason && o.reason === 'status_gt_0'
? Either.right(o.stdout.trim()) ? Either.right(o.stdout.trim())
: Either.left(new Error()) : Either.left(new Error()),
), ),
Either.mapLeft(() => e) Either.mapLeft(() => e),
) ),
); )
module.exports = { run, ignoringStatus }; module.exports = { run, ignoringStatus }

View File

@ -1,205 +1,231 @@
const yaml = require("yaml"); const yaml = require('yaml')
const { strike, match, otherwise } = require("@matchbook/ts"); const { strike, match, otherwise } = require('@matchbook/ts')
const Array_ = require("fp-ts/Array"); const Array_ = require('fp-ts/Array')
const Either = require("fp-ts/Either"); const Either = require('fp-ts/Either')
const { identity: id, flow, pipe } = require("fp-ts/function"); const { identity: id, flow, pipe } = require('fp-ts/function')
const parseSegmentLinuxUser = (o) => const parseSegmentLinuxUser = o =>
"linux_user" in o 'linux_user' in o
? "username" in o.linux_user ? 'username' in o.linux_user
? Either.right({ ? Either.right({
username: o.linux_user.username.trim(), username: o.linux_user.username.trim(),
allowedSshPublicKeys: (o.allowed_ssh_public_keys || []).map( allowedSshPublicKeys: (o.allowed_ssh_public_keys || []).map(pubkey =>
(pubkey) => pubkey.trim() pubkey.trim(),
), ),
}) })
: Either.left(new Error("linux_user.username required")) : Either.left(new Error('linux_user.username required'))
: Either.left(new Error("linux_user required")); : Either.left(new Error('linux_user required'))
const parseSegmentPgAdmin = flow( const parseSegmentPgAdmin = flow(
(o) => o =>
"pg_admin" in o 'pg_admin' in o
? Either.right(o.pg_admin) ? Either.right(o.pg_admin)
: Either.left( : Either.left(
new Error( new Error(
["pg_admin required", `in: ${JSON.stringify(o)}`].join("\n") ['pg_admin required', `in: ${JSON.stringify(o)}`].join('\n'),
) ),
), ),
Either.flatMap((pg) => Either.flatMap(pg =>
"username" in pg 'username' in pg
? Either.right(pg) ? Either.right(pg)
: Either.left( : Either.left(
new Error( new Error(
["pg_admin.username required", `in: ${JSON.stringify(pg)}`].join( ['pg_admin.username required', `in: ${JSON.stringify(pg)}`].join(
"\n" '\n',
) ),
) ),
) ),
), ),
Either.flatMap((pg) => Either.flatMap(pg =>
"password" in pg 'password' in pg
? Either.right(pg) ? Either.right(pg)
: Either.left( : Either.left(
new Error( new Error(
["pg_admin.password required", `in: ${JSON.stringify(pg)}`].join( ['pg_admin.password required', `in: ${JSON.stringify(pg)}`].join(
"\n" '\n',
) ),
) ),
) ),
), ),
Either.map((pg) => ({ Either.map(pg => ({
username: pg.username.trim(), username: pg.username.trim(),
password: pg.password.trim(), password: pg.password.trim(),
})) })),
); )
const parseSegmentNetwork = flow( const parseSegmentNetwork = flow(
(o) => o =>
"network" in o 'network' in o
? Either.right(o.network) ? Either.right(o.network)
: Either.left( : Either.left(
new Error(["network required", `in: ${JSON.stringify(o)}`].join("\n")) new Error(
['network required', `in: ${JSON.stringify(o)}`].join('\n'),
),
), ),
Either.flatMap((n) => Either.flatMap(n =>
("interface" in n && n.interface === "public") || n.interface === "local" ('interface' in n && n.interface === 'public') || n.interface === 'local'
? Either.right(n) ? Either.right(n)
: Either.left( : Either.left(
new Error( new Error(
[ [
"network.interface required, and must be 'public' or 'local'", "network.interface required, and must be 'public' or 'local'",
`in: ${JSON.stringify(n)}`, `in: ${JSON.stringify(n)}`,
].join("\n") ].join('\n'),
) ),
) ),
), ),
Either.flatMap((n) => Either.flatMap(n =>
"port" in n && typeof n.port === "number" && Number.isInteger(n.port) 'port' in n && typeof n.port === 'number' && Number.isInteger(n.port)
? Either.right(n) ? Either.right(n)
: Either.left( : Either.left(
new Error( new Error(
[ [
"network.port required and must be an integer", 'network.port required and must be an integer',
`in: ${JSON.stringify(n)}`, `in: ${JSON.stringify(n)}`,
].join("\n") ].join('\n'),
) ),
) ),
), ),
Either.flatMap((n) => Either.flatMap(n =>
n.interface === 'public' || (n.interface === "local" && !"domain" in n) n.interface === 'public' || (n.interface === 'local' && !'domain' in n)
? Either.right(n) ? Either.right(n)
: Either.left( : Either.left(
new Error( new Error(
[ [
"network.interface is 'local', network.domain must not be present.", "network.interface is 'local', network.domain must not be present.",
`in: ${JSON.stringify(n)}`, `in: ${JSON.stringify(n)}`,
].join("\n") ].join('\n'),
) ),
) ),
), ),
Either.flatMap((n) => Either.flatMap(n =>
n.interface === 'public' && "domain" in n && typeof n.domain === "string" && n.domain.length > 0 n.interface === 'public' &&
'domain' in n &&
typeof n.domain === 'string' &&
n.domain.length > 0
? Either.right(n) ? Either.right(n)
: Either.left( : Either.left(
new Error( new Error(
[ [
"network.interface is 'public', network.domain be set to a value.", "network.interface is 'public', network.domain be set to a value.",
`in: ${JSON.stringify(n)}`, `in: ${JSON.stringify(n)}`,
].join("\n") ].join('\n'),
) ),
) ),
), ),
Either.flatMap((n) => Either.flatMap(n =>
"ssl" in n && typeof n.ssl === "boolean" 'ssl' in n && typeof n.ssl === 'boolean'
? Either.right(n) ? Either.right(n)
: Either.left( : Either.left(
new Error( new Error(
[ [
"network.ssl required and must be a bool", 'network.ssl required and must be a bool',
`in: ${JSON.stringify(n)}`, `in: ${JSON.stringify(n)}`,
].join("\n") ].join('\n'),
) ),
) ),
), ),
Either.map((n) => ({ Either.map(n => ({
ssl: n.ssl, ssl: n.ssl,
domain: (n.domain || "").trim(), domain: (n.domain || '').trim(),
interface: n.interface, interface: n.interface,
port: n.port, port: n.port,
})) })),
); )
const parseServiceDb = (svc) => const parseServiceDb = svc =>
pipe( pipe(
Either.Do, Either.Do,
Either.bind("linuxUser", () => parseSegmentLinuxUser(svc)), Either.bind('linuxUser', () => parseSegmentLinuxUser(svc)),
Either.let("persist", () => Either.let('persist', () =>
(svc.persist || []).filter((s) => typeof s === "string" && s.length > 0) (svc.persist || []).filter(s => typeof s === 'string' && s.length > 0),
), ),
Either.bind("network", () => parseSegmentNetwork(svc)), Either.bind('network', () => parseSegmentNetwork(svc)),
Either.bind("pgAdmin", () => parseSegmentPgAdmin(svc)) Either.bind('pgAdmin', () => parseSegmentPgAdmin(svc)),
); )
const parseServiceApi = (svc) => const parseServiceApi = svc =>
pipe( pipe(
Either.Do, Either.Do,
Either.bind("linuxUser", () => parseSegmentLinuxUser(svc)), Either.bind('linuxUser', () => parseSegmentLinuxUser(svc)),
Either.let("persist", () => Either.let('persist', () =>
(svc.persist || []).filter((s) => typeof s === "string" && s.length > 0) (svc.persist || []).filter(s => typeof s === 'string' && s.length > 0),
), ),
Either.bind("network", () => parseSegmentNetwork(svc)) Either.bind('network', () => parseSegmentNetwork(svc)),
); )
const parseServiceUi = (svc) => const parseServiceUi = svc =>
pipe( pipe(
Either.Do, Either.Do,
Either.bind("linuxUser", () => parseSegmentLinuxUser(svc)), Either.bind('linuxUser', () => parseSegmentLinuxUser(svc)),
Either.let("persist", () => Either.let('persist', () =>
(svc.persist || []).filter((s) => typeof s === "string" && s.length > 0) (svc.persist || []).filter(s => typeof s === 'string' && s.length > 0),
), ),
Either.bind("network", () => parseSegmentNetwork(svc)) Either.bind('network', () => parseSegmentNetwork(svc)),
); )
const parseService = (svc) => const parseService = svc =>
"db" in svc 'db' in svc
? parseServiceDb(svc.db) ? parseServiceDb(svc.db)
: "api" in svc : 'api' in svc
? parseServiceApi(svc.api) ? parseServiceApi(svc.api)
: "ui" in svc : 'ui' in svc
? parseServiceUi(svc.ui) ? parseServiceUi(svc.ui)
: Either.left( : Either.left(
new Error( new Error(
[ [
`top-level array elements must be records with a key named "db", "ui", or "api".`, `top-level array elements must be records with a key named "db", "ui", or "api".`,
`in: ${JSON.stringify(svc)}`, `in: ${JSON.stringify(svc)}`,
].join("\n") ].join('\n'),
) ),
); )
const ensureUniq = ({getWith, onConflict, isConflict}) => array => pipe(array, const ensureUniq =
Array_.map(getWith), ({ getWith, onConflict, isConflict }) =>
Array_.reduce(Either.right([]), (res, cur) => array =>
Either.flatMap(seen => pipe(
isConflict(seen, cur) array,
? Either.left(onConflict(cur)) Array_.map(getWith),
: Either.right([...seen, cur]) Array_.reduce(Either.right([]), (res, cur) =>
)(res) Either.flatMap(seen =>
), isConflict(seen, cur)
Either.map(() => array) ? Either.left(onConflict(cur))
); : Either.right([...seen, cur]),
)(res),
),
Either.map(() => array),
)
const ensureServicesDoNotOverlap = flow( const ensureServicesDoNotOverlap = flow(
ensureUniq({getWith: s => s.linuxUser.username, onConflict: usr => new Error(`linux_user.username must be unique: ${usr}`), isConflict: (seen, cur) => seen.includes(cur)}), ensureUniq({
Either.flatMap(ensureUniq({getWith: s => s.network.port, onConflict: port => new Error(`network.port must be unique: ${port}`), isConflict: (seen, cur) => seen.includes(cur)})), getWith: s => s.linuxUser.username,
Either.flatMap(ensureUniq({getWith: s => s.network.domain, onConflict: domain => new Error(`network.domain must be unique: ${domain}`), isConflict: (seen, cur) => seen.includes(cur)})) onConflict: usr => new Error(`linux_user.username must be unique: ${usr}`),
); isConflict: (seen, cur) => seen.includes(cur),
}),
Either.flatMap(
ensureUniq({
getWith: s => s.network.port,
onConflict: port => new Error(`network.port must be unique: ${port}`),
isConflict: (seen, cur) => seen.includes(cur),
}),
),
Either.flatMap(
ensureUniq({
getWith: s => s.network.domain,
onConflict: domain =>
new Error(`network.domain must be unique: ${domain}`),
isConflict: (seen, cur) => seen.includes(cur),
}),
),
)
const parse = flow( const parse = flow(
(cfg) => Either.tryCatch(() => yaml.parse(cfg), id), cfg => Either.tryCatch(() => yaml.parse(cfg), id),
Either.filterOrElse( Either.filterOrElse(
(p) => p instanceof Array, p => p instanceof Array,
() => new Error("config must have top-level array elements") () => new Error('config must have top-level array elements'),
), ),
Either.flatMap((svcs) => Either.sequenceArray(svcs.map(parseService))), Either.flatMap(svcs => Either.sequenceArray(svcs.map(parseService))),
Either.tap(ensureServicesDoNotOverlap) Either.tap(ensureServicesDoNotOverlap),
); )
module.exports = { parse }; module.exports = { parse }

View File

@ -1,10 +1,7 @@
const Config = require("./config.js"); const Config = require('./config.js')
const Cmd = require("./cmd.js"); const Cmd = require('./cmd.js')
const fs = require("fs"); const fs = require('fs')
const {pipe} = require("fp-ts/function"); const { pipe } = require('fp-ts/function')
const Either = require("fp-ts/Either"); const Either = require('fp-ts/Either')
pipe( pipe(Cmd.run('cat ./config.example.yml'), Either.flatMap(Config.parse))
Cmd.run('cat ./config.example.yml'),
Either.flatMap(Config.parse),
);

View File

@ -12,6 +12,11 @@ fp-ts@^2.16.0:
resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-2.16.0.tgz#64e03314dfc1c7ce5e975d3496ac14bc3eb7f92e" resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-2.16.0.tgz#64e03314dfc1c7ce5e975d3496ac14bc3eb7f92e"
integrity sha512-bLq+KgbiXdTEoT1zcARrWEpa5z6A/8b7PcDW7Gef3NSisQ+VS7ll2Xbf1E+xsgik0rWub/8u0qP/iTTjj+PhxQ== integrity sha512-bLq+KgbiXdTEoT1zcARrWEpa5z6A/8b7PcDW7Gef3NSisQ+VS7ll2Xbf1E+xsgik0rWub/8u0qP/iTTjj+PhxQ==
prettier@^2.8.8:
version "2.8.8"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da"
integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==
yaml@^2.3.1: yaml@^2.3.1:
version "2.3.1" version "2.3.1"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.1.tgz#02fe0975d23cd441242aa7204e09fc28ac2ac33b" resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.1.tgz#02fe0975d23cd441242aa7204e09fc28ac2ac33b"