From 0987303a0b45b7151f95ad27111a54da44aa87da Mon Sep 17 00:00:00 2001 From: BluJ Date: Mon, 10 Apr 2023 15:58:36 -0600 Subject: [PATCH] chore: Add in the converter for the newest of the builder. --- lib/config/builder/value.ts | 30 ++-- lib/health/checkFns/checkPortListening.ts | 11 +- lib/health/checkFns/runHealthScript.ts | 10 +- lib/test/builder.specToBuilder.ts | 164 ++++++++++++++++++++++ lib/test/util.shell.test.ts | 39 ----- lib/types.ts | 27 +++- lib/util/index.ts | 1 - lib/util/parseCommand.ts | 60 -------- package-lock.json | 4 +- package.json | 2 +- scripts/oldSpecToBuilder.ts | 58 ++------ 11 files changed, 219 insertions(+), 187 deletions(-) create mode 100644 lib/test/builder.specToBuilder.ts delete mode 100644 lib/test/util.shell.test.ts delete mode 100644 lib/util/parseCommand.ts diff --git a/lib/config/builder/value.ts b/lib/config/builder/value.ts index 65576d6..147f34f 100644 --- a/lib/config/builder/value.ts +++ b/lib/config/builder/value.ts @@ -35,12 +35,7 @@ const username = Value.string({ ``` */ export class Value extends IBuilder { - static boolean(a: { - name: string; - description?: string | null; - warning?: string | null; - default?: boolean | null; - }) { + static boolean(a: { name: string; description?: string | null; warning?: string | null; default?: boolean | null }) { return new Value({ description: null, warning: null, @@ -148,27 +143,24 @@ export class Value extends IBuilder { ...a, }); } - static object>(a: { - name: string; - description?: string | null; - warning?: string | null; - default?: null | { [k: string]: unknown }; - spec: Spec; - }) { - const { spec: previousSpec, ...rest } = a; + static object>( + a: { + name: string; + description?: string | null; + warning?: string | null; + }, + previousSpec: Spec + ) { const spec = previousSpec.build() as BuilderExtract; return new Value({ type: "object" as const, description: null, warning: null, - default: null, - ...rest, + ...a, spec, }); } - static union< - V extends Variants<{ [key: string]: { name: string; spec: InputSpec } }> - >( + static union>( a: { name: string; description?: string | null; diff --git a/lib/health/checkFns/checkPortListening.ts b/lib/health/checkFns/checkPortListening.ts index b939114..2e7fb64 100644 --- a/lib/health/checkFns/checkPortListening.ts +++ b/lib/health/checkFns/checkPortListening.ts @@ -1,5 +1,4 @@ import { Effects } from "../../types"; -import { parseCommand } from "../../util/parseCommand"; import { CheckResult } from "./CheckResult"; export function containsAddress(x: string, port: number) { const readPorts = x @@ -26,14 +25,8 @@ export async function checkPortListening( } = {} ): Promise { const hasAddress = - containsAddress( - await effects.runCommand(parseCommand("cat /proc/net/tcp")), - port - ) || - containsAddress( - await effects.runCommand(parseCommand("cat /proc/net/udp")), - port - ); + containsAddress(await effects.runCommand(`cat /proc/net/tcp`), port) || + containsAddress(await effects.runCommand("cat /proc/net/udp"), port); if (hasAddress) { return { status: "passing", message }; } diff --git a/lib/health/checkFns/runHealthScript.ts b/lib/health/checkFns/runHealthScript.ts index 0d75b3a..0808972 100644 --- a/lib/health/checkFns/runHealthScript.ts +++ b/lib/health/checkFns/runHealthScript.ts @@ -1,4 +1,4 @@ -import { Effects } from "../../types"; +import { CommandType, Effects } from "../../types"; import { CheckResult } from "./CheckResult"; import { timeoutPromise } from "./index"; @@ -9,18 +9,18 @@ import { timeoutPromise } from "./index"; * @param param0 * @returns */ -export const runHealthScript = async ( +export const runHealthScript = async ( effects: Effects, - runCommand: Parameters[0], + runCommand: CommandType, { timeout = 30000, errorMessage = `Error while running command: ${runCommand}`, message = (res: string) => `Have ran script ${runCommand} and the result: ${res}`, - } + } = {} ): Promise => { const res = await Promise.race([ - effects.runCommand(runCommand), + effects.runCommand(runCommand, { timeoutMillis: timeout }), timeoutPromise(timeout), ]).catch((e) => { effects.warn(errorMessage); diff --git a/lib/test/builder.specToBuilder.ts b/lib/test/builder.specToBuilder.ts new file mode 100644 index 0000000..b9faf60 --- /dev/null +++ b/lib/test/builder.specToBuilder.ts @@ -0,0 +1,164 @@ +import camelCase from "lodash/camelCase"; +import * as fs from "fs"; +import { string } from "ts-matches"; +import { InputSpecRaw } from "../config/configTypesRaw"; +import * as C from "../config/configTypesRaw"; + +export async function writeConvertedFile( + file: string, + inputData: Promise | InputSpecRaw, + options: Parameters[1] +) { + await fs.writeFile(file, await makeFileContent(inputData, options), (err) => console.error(err)); +} +export default async function makeFileContent( + inputData: Promise | InputSpecRaw, + { startSdk = "start-sdk" } = {} +) { + const outputLines: string[] = []; + outputLines.push(` + import {Config, Value, List, Variants} from '${startSdk}/config/builder'; +`); + const data = await inputData; + + const namedConsts = new Set(["Config", "Value", "List"]); + const configName = newConst("InputSpec", convertInputSpec(data)); + const configMatcherName = newConst("matchInputSpec", `${configName}.validator()`); + outputLines.push(`export type InputSpec = typeof ${configMatcherName}._TYPE;`); + + return outputLines.join("\n"); + + function newConst(key: string, data: string) { + const variableName = getNextConstName(camelCase(key)); + outputLines.push(`export const ${variableName} = ${data};`); + return variableName; + } + function convertInputSpec(data: C.InputSpecRaw) { + let answer = "Config.of({"; + for (const [key, value] of Object.entries(data)) { + const variableName = newConst(key, convertValueSpec(value)); + + answer += `${JSON.stringify(key)}: ${variableName},`; + } + return `${answer}});`; + } + function convertValueSpec(value: C.ValueSpec): string { + switch (value.type) { + case "string": + case "textarea": + case "number": + case "boolean": + case "select": + case "multiselect": { + const { type, ...rest } = value; + return `Value.${type}(${JSON.stringify(rest, null, 2)})`; + } + case "object": { + const { type, spec, ...rest } = value; + const specName = newConst(value.name + "_spec", convertInputSpec(spec)); + return `Value.object({ + name: ${JSON.stringify(rest.name || null)}, + description: ${JSON.stringify(rest.description || null)}, + warning: ${JSON.stringify(rest.warning || null)}, + spec: , + }, ${specName})`; + } + case "union": { + const { variants, type, ...rest } = value; + const variantVariable = newConst(value.name + "_variants", convertVariants(variants)); + + return `Value.union(${JSON.stringify(rest)}, ${variantVariable})`; + } + case "list": { + const list = newConst(value.name + "_list", convertList(value)); + return `Value.list(${list})`; + } + case "file": { + throw new Error("File not implemented yet"); + } + } + } + + function convertList(valueSpecList: C.ValueSpecList) { + const { spec, ...value } = valueSpecList; + switch (spec.type) { + case "string": { + return `List.string(${JSON.stringify( + { + name: value.name || null, + range: value.range || null, + default: value.default || null, + description: value.description || null, + warning: value.warning || null, + }, + null, + 2 + )}, ${JSON.stringify({ + masked: spec?.masked || false, + placeholder: spec?.placeholder || null, + pattern: spec?.pattern || null, + patternDescription: spec?.patternDescription || null, + inputMode: spec?.inputmode || null, + })})`; + } + case "number": { + return `List.number(${JSON.stringify( + { + name: value.name || null, + range: value.range || null, + default: value.default || null, + description: value.description || null, + warning: value.warning || null, + }, + null, + 2 + )}, ${JSON.stringify({ + range: spec?.range || null, + integral: spec?.integral || false, + units: spec?.units || null, + placeholder: spec?.placeholder || null, + })})`; + } + case "object": { + const specName = newConst(value.name + "_spec", convertInputSpec(spec.spec)); + return `List.obj({ + name: ${JSON.stringify(value.name || null)}, + range: ${JSON.stringify(value.range || null)}, + default: ${JSON.stringify(value.default || null)}, + description: ${JSON.stringify(value.description || null)}, + warning: ${JSON.stringify(value.warning || null)}, + }, { + spec: ${specName}, + displayAs: ${JSON.stringify(spec?.displayAs || null)}, + uniqueBy: ${JSON.stringify(spec?.uniqueBy || null)}, + })`; + } + } + } + + function convertVariants( + variants: Record< + string, + { + name: string; + spec: C.InputSpecRaw; + } + > + ): string { + let answer = "Variants.of({"; + for (const [key, { name, spec }] of Object.entries(variants)) { + const variantSpec = newConst(key, convertInputSpec(spec)); + answer += `"${key}": {name: "${name}", spec: ${variantSpec}},`; + } + return `${answer}})`; + } + + function getNextConstName(name: string, i = 0): string { + const newName = !i ? name : name + i; + if (namedConsts.has(newName)) { + return getNextConstName(name, i + 1); + } + namedConsts.add(newName); + return newName; + } +} diff --git a/lib/test/util.shell.test.ts b/lib/test/util.shell.test.ts deleted file mode 100644 index 4cc49d0..0000000 --- a/lib/test/util.shell.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { sh } from "../util"; - -describe("Util shell values bluj ", () => { - test("simple", () => { - expect(sh("echo hello")).toEqual({ command: "echo", args: ["hello"] }); - }, 1); - test("simple 2", () => { - expect(sh("echo hello world")).toEqual({ - command: "echo", - args: ["hello", "world"], - }); - }, 1); - test("simple A double quote", () => { - expect(sh('echo "hello world" ')).toEqual({ - command: "echo", - args: ["hello world"], - }); - }, 1); - test("simple A sing quote", () => { - expect(sh("echo 'hello world' ")).toEqual({ - command: "echo", - args: ["hello world"], - }); - }, 1); - test("simple complex", () => { - expect(sh("echo arg1 'arg2 and' arg3 \"arg4 \" ")).toEqual({ - command: "echo", - args: ["arg1", "arg2 and", "arg3", "arg4 "], - }); - }, 1); - test("nested", () => { - expect( - sh(`echo " 'arg1 ' " ' " arg2" ' arg4'"`) - ).toEqual({ - command: "echo", - args: [` 'arg1 ' `, ` " arg2" `, `arg4'"`], - }); - }, 1); -}); diff --git a/lib/types.ts b/lib/types.ts index 3ce899e..133bedc 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -79,6 +79,14 @@ export namespace ExpectedExports { export type TimeMs = number; export type VersionString = string; +type ValidIfNoStupidEscape = A extends + | `${string}'"'"'${string}` + | `${string}\\"${string}` + ? never + : "" extends A & "" + ? never + : A; + export type ConfigRes = { /** This should be the previous config, that way during set config we start with the previous */ config?: null | Record; @@ -98,6 +106,10 @@ export type Daemon = { export type HealthStatus = "passing" | "warning" | "failing" | "disabled"; +export type CommandType = + | ValidIfNoStupidEscape + | [string, ...string[]]; + /** Used to reach out from the pure js runtime */ export type Effects = { /** Usable when not sandboxed */ @@ -129,16 +141,19 @@ export type Effects = { path: string; }): Promise>; - runCommand(input: { - command: string; - args?: string[]; - timeoutMillis?: number; - }): Promise; + runCommand( + command: ValidIfNoStupidEscape | [string, ...string[]], + input?: { + timeoutMillis?: number; + } + ): Promise; runShellDaemon(command: string): { wait(): Promise; term(): Promise; }; - runDaemon(input: { command: string; args?: string[] }): { + runDaemon( + command: ValidIfNoStupidEscape | [string, ...string[]] + ): { wait(): Promise; term(): Promise; [DaemonProof]: never; diff --git a/lib/util/index.ts b/lib/util/index.ts index fcb833d..7ecfe55 100644 --- a/lib/util/index.ts +++ b/lib/util/index.ts @@ -3,7 +3,6 @@ import * as T from "../types"; export { guardAll, typeFromProps } from "./propertiesMatcher"; export { default as nullIfEmpty } from "./nullIfEmpty"; export { FileHelper } from "./fileHelper"; -export { parseCommand as sh } from "./parseCommand"; /** Used to check if the file exists before hand */ export const exists = ( diff --git a/lib/util/parseCommand.ts b/lib/util/parseCommand.ts deleted file mode 100644 index 062fbb3..0000000 --- a/lib/util/parseCommand.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { Effects } from "../types"; - -const match = - (regex: RegExp) => - (s: string): [string, string, string] => { - const matched = s.match(regex); - if (matched === null) { - return [s, "", ""]; - } - const [ - _all, - _firstSet, - notQuote, - _maybeQuote, - doubleQuoted, - singleQuoted, - rest, - noQuotes, - ] = matched; - const quoted = doubleQuoted || singleQuoted; - if (!!noQuotes) { - return [noQuotes || "", "", ""]; - } - if (!notQuote && !quoted) { - return [rest || "", "", ""]; - } - return [notQuote || "", quoted || "", rest || ""]; - }; -const quotes = match(/^((.*?)("([^\"]*)"|'([^\']*)')(.*)|(.*))$/); -const quoteSeperated = (s: string, quote: typeof quotes) => { - const values = []; - - let value = quote(s); - while (true) { - if (value[0].length) { - values.push(...value[0].split(" ")); - } - if (value[1].length) { - values.push(value[1]); - } - if (!value[2].length) { - break; - } - value = quote(value[2]); - } - return values; -}; - -type ValidIfNoStupidEscape = A extends `${string}'"'"'${string}` ? never : A; -export function parseCommand( - shellCommand: ValidIfNoStupidEscape -) { - const [command, ...args] = quoteSeperated(shellCommand, quotes).filter( - Boolean - ); - return { - command, - args, - } satisfies Parameters[0]; -} diff --git a/package-lock.json b/package-lock.json index d0d53f8..aecbf2f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "start-sdk", - "version": "0.4.0-lib0.charlie9", + "version": "0.4.0-lib0.charlie10", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "start-sdk", - "version": "0.4.0-lib0.charlie9", + "version": "0.4.0-lib0.charlie10", "license": "MIT", "dependencies": { "@iarna/toml": "^2.2.5", diff --git a/package.json b/package.json index 01c397e..e72e728 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "start-sdk", - "version": "0.4.0-lib0.charlie9", + "version": "0.4.0-lib0.charlie10", "description": "For making the patterns that are wanted in making services for the startOS.", "main": "./lib/index.js", "types": "./lib/index.d.ts", diff --git a/scripts/oldSpecToBuilder.ts b/scripts/oldSpecToBuilder.ts index ccd0d9f..fc10d4e 100644 --- a/scripts/oldSpecToBuilder.ts +++ b/scripts/oldSpecToBuilder.ts @@ -8,15 +8,10 @@ export async function writeConvertedFile( inputData: Promise | any, options: Parameters[1] ) { - await fs.writeFile(file, await makeFileContent(inputData, options), (err) => - console.error(err) - ); + await fs.writeFile(file, await makeFileContent(inputData, options), (err) => console.error(err)); } -export default async function makeFileContent( - inputData: Promise | any, - { startSdk = "start-sdk" } = {} -) { +export default async function makeFileContent(inputData: Promise | any, { startSdk = "start-sdk" } = {}) { const outputLines: string[] = []; outputLines.push(` import {Config, Value, List, Variants} from '${startSdk}/config/builder'; @@ -25,13 +20,8 @@ export default async function makeFileContent( const namedConsts = new Set(["Config", "Value", "List"]); const configName = newConst("InputSpec", convertInputSpec(data)); - const configMatcherName = newConst( - "matchInputSpec", - `${configName}.validator()` - ); - outputLines.push( - `export type InputSpec = typeof ${configMatcherName}._TYPE;` - ); + const configMatcherName = newConst("matchInputSpec", `${configName}.validator()`); + outputLines.push(`export type InputSpec = typeof ${configMatcherName}._TYPE;`); return outputLines.join("\n"); @@ -111,10 +101,7 @@ export default async function makeFileContent( )})`; } case "enum": { - const allValueNames = new Set([ - ...(value?.["values"] || []), - ...Object.keys(value?.["value-names"] || {}), - ]); + const allValueNames = new Set([...(value?.["values"] || []), ...Object.keys(value?.["value-names"] || {})]); const values = Object.fromEntries( Array.from(allValueNames) .filter(string.test) @@ -134,17 +121,13 @@ export default async function makeFileContent( )} as const)`; } case "object": { - const specName = newConst( - value.name + "_spec", - convertInputSpec(value.spec) - ); + const specName = newConst(value.name + "_spec", convertInputSpec(value.spec)); return `Value.object({ name: ${JSON.stringify(value.name || null)}, description: ${JSON.stringify(value.description || null)}, warning: ${JSON.stringify(value.warning || null)}, default: ${JSON.stringify(value.default || null)}, - spec: ${specName}, - })`; + }, ${specName})`; } case "union": { const variants = newConst( @@ -174,9 +157,7 @@ export default async function makeFileContent( function convertList(value: any) { switch (value.subtype) { case "string": { - return `List.${ - value?.spec?.textarea === true ? "textarea" : "string" - }(${JSON.stringify( + return `List.${value?.spec?.textarea === true ? "textarea" : "string"}(${JSON.stringify( { name: value.name || null, range: value.range || null, @@ -235,10 +216,7 @@ export default async function makeFileContent( )})`; } case "object": { - const specName = newConst( - value.name + "_spec", - convertInputSpec(value.spec.spec) - ); + const specName = newConst(value.name + "_spec", convertInputSpec(value.spec.spec)); return `List.obj({ name: ${JSON.stringify(value.name || null)}, range: ${JSON.stringify(value.range || null)}, @@ -254,19 +232,14 @@ export default async function makeFileContent( case "union": { const variants = newConst( value.name + "_variants", - convertVariants( - value.spec.variants, - value.spec["variant-names"] || {} - ) + convertVariants(value.spec.variants, value.spec["variant-names"] || {}) ); const unionValueName = newConst( value.name + "_union", ` Value.union({ name: ${JSON.stringify(value?.spec?.tag?.name || null)}, - description: ${JSON.stringify( - value?.spec?.tag?.description || null - )}, + description: ${JSON.stringify(value?.spec?.tag?.description || null)}, warning: ${JSON.stringify(value?.spec?.tag?.warning || null)}, required: ${JSON.stringify(!(value?.spec?.tag?.nullable || false))}, default: ${JSON.stringify(value?.spec?.default || null)}, @@ -297,16 +270,11 @@ export default async function makeFileContent( throw new Error(`Unknown subtype "${value.subtype}"`); } - function convertVariants( - variants: Record, - variantNames: Record - ): string { + function convertVariants(variants: Record, variantNames: Record): string { let answer = "Variants.of({"; for (const [key, value] of Object.entries(variants)) { const variantSpec = newConst(key, convertInputSpec(value)); - answer += `"${key}": {name: "${ - variantNames[key] || key - }", spec: ${variantSpec}},`; + answer += `"${key}": {name: "${variantNames[key] || key}", spec: ${variantSpec}},`; } return `${answer}})`; }