diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..991a44c --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "trailingComma": "all", + "singleQuote": true, + "semi": false, + "arrowParens": "avoid", + "parser": "babel" +} diff --git a/package.json b/package.json index b300500..c0023cb 100644 --- a/package.json +++ b/package.json @@ -6,9 +6,16 @@ "repository": "git@git.orionkindel.com:dnim/srv.git", "author": "Orion Kindel ", "license": "MIT", + "scripts": { + "fmt:check": "prettier --check 'src/**/*.js'", + "fmt": "prettier --write 'src/**/*.js'" + }, "dependencies": { "@matchbook/ts": "^1.0.0", "fp-ts": "^2.16.0", "yaml": "^2.3.1" + }, + "devDependencies": { + "prettier": "^2.8.8" } } diff --git a/src/cmd.js b/src/cmd.js index bd89c03..fdee471 100644 --- a/src/cmd.js +++ b/src/cmd.js @@ -1,34 +1,34 @@ -const Either = require("fp-ts/Either"); -const { pipe, identity: id } = require("fp-ts/function"); -const cp = require("child_process"); +const Either = require('fp-ts/Either') +const { pipe, identity: id } = require('fp-ts/function') +const cp = require('child_process') -const run = (script) => +const run = script => pipe( Either.tryCatch( - () => cp.spawnSync(script, { encoding: "utf8", shell: true }), - id + () => cp.spawnSync(script, { encoding: 'utf8', shell: true }), + id, ), - Either.flatMap((o) => (o.error ? Either.left(o.error) : Either.right(o))), - Either.flatMap((o) => + Either.flatMap(o => (o.error ? Either.left(o.error) : Either.right(o))), + Either.flatMap(o => o.status > 0 ? 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( Either.tryCatch(() => JSON.parse(e.message), id), - Either.flatMap((o) => - o.reason && o.reason === "status_gt_0" + Either.flatMap(o => + o.reason && o.reason === 'status_gt_0' ? 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 } diff --git a/src/config.js b/src/config.js index b8fd9f2..83c18e4 100644 --- a/src/config.js +++ b/src/config.js @@ -1,205 +1,231 @@ -const yaml = require("yaml"); -const { strike, match, otherwise } = require("@matchbook/ts"); -const Array_ = require("fp-ts/Array"); -const Either = require("fp-ts/Either"); -const { identity: id, flow, pipe } = require("fp-ts/function"); +const yaml = require('yaml') +const { strike, match, otherwise } = require('@matchbook/ts') +const Array_ = require('fp-ts/Array') +const Either = require('fp-ts/Either') +const { identity: id, flow, pipe } = require('fp-ts/function') -const parseSegmentLinuxUser = (o) => - "linux_user" in o - ? "username" in o.linux_user +const parseSegmentLinuxUser = o => + 'linux_user' in o + ? 'username' in o.linux_user ? Either.right({ username: o.linux_user.username.trim(), - allowedSshPublicKeys: (o.allowed_ssh_public_keys || []).map( - (pubkey) => pubkey.trim() + allowedSshPublicKeys: (o.allowed_ssh_public_keys || []).map(pubkey => + pubkey.trim(), ), }) - : Either.left(new Error("linux_user.username required")) - : Either.left(new Error("linux_user required")); + : Either.left(new Error('linux_user.username required')) + : Either.left(new Error('linux_user required')) const parseSegmentPgAdmin = flow( - (o) => - "pg_admin" in o + o => + 'pg_admin' in o ? Either.right(o.pg_admin) : Either.left( new Error( - ["pg_admin required", `in: ${JSON.stringify(o)}`].join("\n") - ) + ['pg_admin required', `in: ${JSON.stringify(o)}`].join('\n'), + ), ), - Either.flatMap((pg) => - "username" in pg + Either.flatMap(pg => + 'username' in pg ? Either.right(pg) : Either.left( new Error( - ["pg_admin.username required", `in: ${JSON.stringify(pg)}`].join( - "\n" - ) - ) - ) + ['pg_admin.username required', `in: ${JSON.stringify(pg)}`].join( + '\n', + ), + ), + ), ), - Either.flatMap((pg) => - "password" in pg + Either.flatMap(pg => + 'password' in pg ? Either.right(pg) : Either.left( new Error( - ["pg_admin.password required", `in: ${JSON.stringify(pg)}`].join( - "\n" - ) - ) - ) + ['pg_admin.password required', `in: ${JSON.stringify(pg)}`].join( + '\n', + ), + ), + ), ), - Either.map((pg) => ({ + Either.map(pg => ({ username: pg.username.trim(), password: pg.password.trim(), - })) -); + })), +) const parseSegmentNetwork = flow( - (o) => - "network" in o + o => + 'network' in o ? Either.right(o.network) : 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) => - ("interface" in n && n.interface === "public") || n.interface === "local" + Either.flatMap(n => + ('interface' in n && n.interface === 'public') || n.interface === 'local' ? Either.right(n) : Either.left( new Error( [ "network.interface required, and must be 'public' or 'local'", `in: ${JSON.stringify(n)}`, - ].join("\n") - ) - ) + ].join('\n'), + ), + ), ), - Either.flatMap((n) => - "port" in n && typeof n.port === "number" && Number.isInteger(n.port) + Either.flatMap(n => + 'port' in n && typeof n.port === 'number' && Number.isInteger(n.port) ? Either.right(n) : Either.left( new Error( [ - "network.port required and must be an integer", + 'network.port required and must be an integer', `in: ${JSON.stringify(n)}`, - ].join("\n") - ) - ) + ].join('\n'), + ), + ), ), - Either.flatMap((n) => - n.interface === 'public' || (n.interface === "local" && !"domain" in n) + Either.flatMap(n => + n.interface === 'public' || (n.interface === 'local' && !'domain' in n) ? Either.right(n) : Either.left( new Error( [ "network.interface is 'local', network.domain must not be present.", `in: ${JSON.stringify(n)}`, - ].join("\n") - ) - ) + ].join('\n'), + ), + ), ), - Either.flatMap((n) => - n.interface === 'public' && "domain" in n && typeof n.domain === "string" && n.domain.length > 0 + Either.flatMap(n => + n.interface === 'public' && + 'domain' in n && + typeof n.domain === 'string' && + n.domain.length > 0 ? Either.right(n) : Either.left( new Error( [ "network.interface is 'public', network.domain be set to a value.", `in: ${JSON.stringify(n)}`, - ].join("\n") - ) - ) + ].join('\n'), + ), + ), ), - Either.flatMap((n) => - "ssl" in n && typeof n.ssl === "boolean" + Either.flatMap(n => + 'ssl' in n && typeof n.ssl === 'boolean' ? Either.right(n) : Either.left( new Error( [ - "network.ssl required and must be a bool", + 'network.ssl required and must be a bool', `in: ${JSON.stringify(n)}`, - ].join("\n") - ) - ) + ].join('\n'), + ), + ), ), - Either.map((n) => ({ + Either.map(n => ({ ssl: n.ssl, - domain: (n.domain || "").trim(), + domain: (n.domain || '').trim(), interface: n.interface, port: n.port, - })) -); + })), +) -const parseServiceDb = (svc) => +const parseServiceDb = svc => pipe( Either.Do, - Either.bind("linuxUser", () => parseSegmentLinuxUser(svc)), - Either.let("persist", () => - (svc.persist || []).filter((s) => typeof s === "string" && s.length > 0) + Either.bind('linuxUser', () => parseSegmentLinuxUser(svc)), + Either.let('persist', () => + (svc.persist || []).filter(s => typeof s === 'string' && s.length > 0), ), - Either.bind("network", () => parseSegmentNetwork(svc)), - Either.bind("pgAdmin", () => parseSegmentPgAdmin(svc)) - ); + Either.bind('network', () => parseSegmentNetwork(svc)), + Either.bind('pgAdmin', () => parseSegmentPgAdmin(svc)), + ) -const parseServiceApi = (svc) => +const parseServiceApi = svc => pipe( Either.Do, - Either.bind("linuxUser", () => parseSegmentLinuxUser(svc)), - Either.let("persist", () => - (svc.persist || []).filter((s) => typeof s === "string" && s.length > 0) + Either.bind('linuxUser', () => parseSegmentLinuxUser(svc)), + Either.let('persist', () => + (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( Either.Do, - Either.bind("linuxUser", () => parseSegmentLinuxUser(svc)), - Either.let("persist", () => - (svc.persist || []).filter((s) => typeof s === "string" && s.length > 0) + Either.bind('linuxUser', () => parseSegmentLinuxUser(svc)), + Either.let('persist', () => + (svc.persist || []).filter(s => typeof s === 'string' && s.length > 0), ), - Either.bind("network", () => parseSegmentNetwork(svc)) - ); + Either.bind('network', () => parseSegmentNetwork(svc)), + ) -const parseService = (svc) => - "db" in svc +const parseService = svc => + 'db' in svc ? parseServiceDb(svc.db) - : "api" in svc + : 'api' in svc ? parseServiceApi(svc.api) - : "ui" in svc + : 'ui' in svc ? parseServiceUi(svc.ui) : Either.left( new Error( [ `top-level array elements must be records with a key named "db", "ui", or "api".`, `in: ${JSON.stringify(svc)}`, - ].join("\n") - ) - ); + ].join('\n'), + ), + ) -const ensureUniq = ({getWith, onConflict, isConflict}) => array => pipe(array, - Array_.map(getWith), - Array_.reduce(Either.right([]), (res, cur) => - Either.flatMap(seen => - isConflict(seen, cur) - ? Either.left(onConflict(cur)) - : Either.right([...seen, cur]) - )(res) - ), - Either.map(() => array) - ); +const ensureUniq = + ({ getWith, onConflict, isConflict }) => + array => + pipe( + array, + Array_.map(getWith), + Array_.reduce(Either.right([]), (res, cur) => + Either.flatMap(seen => + isConflict(seen, cur) + ? Either.left(onConflict(cur)) + : Either.right([...seen, cur]), + )(res), + ), + Either.map(() => array), + ) 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)}), - 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)})) -); + ensureUniq({ + getWith: s => s.linuxUser.username, + 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( - (cfg) => Either.tryCatch(() => yaml.parse(cfg), id), + cfg => Either.tryCatch(() => yaml.parse(cfg), id), Either.filterOrElse( - (p) => p instanceof Array, - () => new Error("config must have top-level array elements") + p => p instanceof Array, + () => new Error('config must have top-level array elements'), ), - Either.flatMap((svcs) => Either.sequenceArray(svcs.map(parseService))), - Either.tap(ensureServicesDoNotOverlap) -); + Either.flatMap(svcs => Either.sequenceArray(svcs.map(parseService))), + Either.tap(ensureServicesDoNotOverlap), +) -module.exports = { parse }; +module.exports = { parse } diff --git a/src/index.js b/src/index.js index dfed2b6..39ed27b 100644 --- a/src/index.js +++ b/src/index.js @@ -1,10 +1,7 @@ -const Config = require("./config.js"); -const Cmd = require("./cmd.js"); -const fs = require("fs"); -const {pipe} = require("fp-ts/function"); -const Either = require("fp-ts/Either"); +const Config = require('./config.js') +const Cmd = require('./cmd.js') +const fs = require('fs') +const { pipe } = require('fp-ts/function') +const Either = require('fp-ts/Either') -pipe( - Cmd.run('cat ./config.example.yml'), - Either.flatMap(Config.parse), -); +pipe(Cmd.run('cat ./config.example.yml'), Either.flatMap(Config.parse)) diff --git a/yarn.lock b/yarn.lock index 5f35f6d..1059c36 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12,6 +12,11 @@ fp-ts@^2.16.0: resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-2.16.0.tgz#64e03314dfc1c7ce5e975d3496ac14bc3eb7f92e" 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: version "2.3.1" resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.1.tgz#02fe0975d23cd441242aa7204e09fc28ac2ac33b"