mirror of
https://github.com/Start9Labs/start-sdk.git
synced 2026-03-26 10:21:55 +00:00
chore: Add in the converter for the newest of the builder.
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
164
lib/test/builder.specToBuilder.ts
Normal file
164
lib/test/builder.specToBuilder.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
27
lib/types.ts
27
lib/types.ts
@@ -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;
|
||||
|
||||
@@ -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 = (
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
Reference in New Issue
Block a user