chore: Add in the converter for the newest of the builder.

This commit is contained in:
BluJ
2023-04-10 15:58:36 -06:00
parent 4efa94ec10
commit 0987303a0b
11 changed files with 219 additions and 187 deletions

View File

@@ -35,12 +35,7 @@ const username = Value.string({
```
*/
export class Value<A extends ValueSpec> extends IBuilder<A> {
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<A extends ValueSpec> extends IBuilder<A> {
...a,
});
}
static object<Spec extends Config<InputSpec>>(a: {
name: string;
description?: string | null;
warning?: string | null;
default?: null | { [k: string]: unknown };
spec: Spec;
}) {
const { spec: previousSpec, ...rest } = a;
static object<Spec extends Config<InputSpec>>(
a: {
name: string;
description?: string | null;
warning?: string | null;
},
previousSpec: Spec
) {
const spec = previousSpec.build() as BuilderExtract<Spec>;
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<V extends Variants<{ [key: string]: { name: string; spec: InputSpec } }>>(
a: {
name: string;
description?: string | null;

View File

@@ -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<CheckResult> {
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 };
}

View File

@@ -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 <A extends string>(
effects: Effects,
runCommand: Parameters<Effects["runCommand"]>[0],
runCommand: CommandType<A>,
{
timeout = 30000,
errorMessage = `Error while running command: ${runCommand}`,
message = (res: string) =>
`Have ran script ${runCommand} and the result: ${res}`,
}
} = {}
): Promise<CheckResult> => {
const res = await Promise.race([
effects.runCommand(runCommand),
effects.runCommand(runCommand, { timeoutMillis: timeout }),
timeoutPromise(timeout),
]).catch((e) => {
effects.warn(errorMessage);

View File

@@ -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> | InputSpecRaw,
options: Parameters<typeof makeFileContent>[1]
) {
await fs.writeFile(file, await makeFileContent(inputData, options), (err) => console.error(err));
}
export default async function makeFileContent(
inputData: Promise<InputSpecRaw> | 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;
}
}

View File

@@ -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);
});

View File

@@ -79,6 +79,14 @@ export namespace ExpectedExports {
export type TimeMs = number;
export type VersionString = string;
type ValidIfNoStupidEscape<A> = 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<string, unknown>;
@@ -98,6 +106,10 @@ export type Daemon = {
export type HealthStatus = "passing" | "warning" | "failing" | "disabled";
export type CommandType<A extends string> =
| ValidIfNoStupidEscape<A>
| [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<Record<string, unknown>>;
runCommand(input: {
command: string;
args?: string[];
timeoutMillis?: number;
}): Promise<string>;
runCommand<A extends string>(
command: ValidIfNoStupidEscape<A> | [string, ...string[]],
input?: {
timeoutMillis?: number;
}
): Promise<string>;
runShellDaemon(command: string): {
wait(): Promise<string>;
term(): Promise<void>;
};
runDaemon(input: { command: string; args?: string[] }): {
runDaemon<A extends string>(
command: ValidIfNoStupidEscape<A> | [string, ...string[]]
): {
wait(): Promise<string>;
term(): Promise<void>;
[DaemonProof]: never;

View File

@@ -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 = (

View File

@@ -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> = A extends `${string}'"'"'${string}` ? never : A;
export function parseCommand<T extends string>(
shellCommand: ValidIfNoStupidEscape<T>
) {
const [command, ...args] = quoteSeperated(shellCommand, quotes).filter(
Boolean
);
return {
command,
args,
} satisfies Parameters<Effects["runCommand"]>[0];
}