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