mirror of
https://github.com/moghtech/komodo.git
synced 2026-02-14 16:18:59 -06:00
saving
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
FROM node:alpine
|
||||
|
||||
RUN apk update && apk add docker git openrc yarn --no-cache
|
||||
RUN apk update && apk add docker git openrc yarn python3 --no-cache
|
||||
RUN addgroup root docker && rc-update add docker boot
|
||||
|
||||
COPY ./frontend /frontend
|
||||
|
||||
@@ -40,7 +40,11 @@ async function build(
|
||||
const { cliBuild, dockerBuildArgs } = build;
|
||||
try {
|
||||
const cli = cliBuild
|
||||
? await execute(`cd ${cliBuild.path} && ${cliBuild.command}`)
|
||||
? await execute(
|
||||
`cd ${BUILD_REPO_PATH}${
|
||||
cliBuild.path ? (cliBuild.path[0] === "/" ? "" : "/") : ""
|
||||
}${cliBuild.path} && ${cliBuild.command}`
|
||||
)
|
||||
: undefined;
|
||||
const docker = dockerBuildArgs
|
||||
? await dockerBuild(
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Deployment, User } from "@monitor/types";
|
||||
import { deploymentChangelog, UPDATE_DEPLOYMENT } from "@monitor/util";
|
||||
import { FastifyInstance } from "fastify";
|
||||
import { PERMISSIONS_DENY_LOG } from "../../config";
|
||||
import { remove } from "fs-extra";
|
||||
import { DEPLOYMENT_REPO_PATH, PERMISSIONS_DENY_LOG } from "../../config";
|
||||
import { clonePeriphery } from "../../util/periphery/git";
|
||||
import { addDeploymentUpdate } from "../../util/updates";
|
||||
import cloneRepo from "./clone";
|
||||
@@ -36,10 +37,18 @@ async function updateDeployment(
|
||||
deployment.serverID === app.core._id
|
||||
? undefined
|
||||
: await app.servers.findById(deployment.serverID!);
|
||||
if (server) {
|
||||
await clonePeriphery(server, deployment);
|
||||
if (deployment.repo) {
|
||||
if (server) {
|
||||
await clonePeriphery(server, deployment);
|
||||
} else {
|
||||
await cloneRepo(app, user, deployment);
|
||||
}
|
||||
} else {
|
||||
await cloneRepo(app, user, deployment);
|
||||
if (server) {
|
||||
// need to make this route
|
||||
} else {
|
||||
await remove(DEPLOYMENT_REPO_PATH + deployment.containerName); // need to have this on periphery as well
|
||||
}
|
||||
}
|
||||
}
|
||||
// make sure owners cant be updated this way
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Component, Show } from "solid-js";
|
||||
import { BUILD } from "../../state/actions";
|
||||
import { useAppState } from "../../state/StateProvider";
|
||||
import { combineClasses } from "../../util/helpers";
|
||||
import ConfirmButton from "../util/ConfirmButton";
|
||||
@@ -8,21 +9,23 @@ import Grid from "../util/layout/Grid";
|
||||
import s from "./build.module.css";
|
||||
|
||||
const Actions: Component<{}> = (p) => {
|
||||
const { builds, selected } = useAppState();
|
||||
const { builds, selected, ws } = useAppState();
|
||||
const build = () => builds.get(selected.id())!;
|
||||
return (
|
||||
<Show when={build()}>
|
||||
<Grid class={combineClasses(s.Card, "shadow")}>
|
||||
<h1>actions</h1>
|
||||
<Flex class={combineClasses(s.Action, "shadow")}>
|
||||
build{" "}
|
||||
<ConfirmButton color="green">
|
||||
<Icon type="build" />
|
||||
</ConfirmButton>
|
||||
</Flex>
|
||||
</Grid>
|
||||
</Show>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Show when={build()}>
|
||||
<Grid class={combineClasses(s.Card, "shadow")}>
|
||||
<h1>actions</h1>
|
||||
<Flex class={combineClasses(s.Action, "shadow")}>
|
||||
build{" "}
|
||||
<ConfirmButton color="green" onConfirm={() => {
|
||||
ws.send(BUILD, {})
|
||||
}}>
|
||||
<Icon type="build" />
|
||||
</ConfirmButton>
|
||||
</Flex>
|
||||
</Grid>
|
||||
</Show>
|
||||
);
|
||||
};
|
||||
|
||||
export default Actions;
|
||||
export default Actions;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Build, Update } from "@monitor/types";
|
||||
import { Component, createContext, createEffect, onCleanup, useContext } from "solid-js";
|
||||
import { Accessor, Component, createContext, createEffect, createSignal, onCleanup, useContext } from "solid-js";
|
||||
import { createStore, DeepReadonly, SetStoreFunction } from "solid-js/store";
|
||||
import { ADD_UPDATE, UPDATE_BUILD } from "../../../../state/actions";
|
||||
import { useAppState } from "../../../../state/StateProvider";
|
||||
import { getBuild } from "../../../../util/query";
|
||||
import { ADD_UPDATE, UPDATE_BUILD } from "../../../state/actions";
|
||||
import { useAppState } from "../../../state/StateProvider";
|
||||
import { getBuild } from "../../../util/query";
|
||||
|
||||
type ConfigBuild = Build & { loaded: boolean; updated: boolean };
|
||||
type ConfigBuild = Build & { loaded: boolean; updated: boolean; saving: boolean };
|
||||
|
||||
type State = {
|
||||
build: DeepReadonly<ConfigBuild>;
|
||||
@@ -22,12 +22,14 @@ export const ConfigProvider: Component<{ build: Build }> = (p) => {
|
||||
...p.build,
|
||||
loaded: false,
|
||||
updated: false,
|
||||
saving: false,
|
||||
});
|
||||
const setBuild = (...args: any) => {
|
||||
const setBuild: SetStoreFunction<ConfigBuild> = (...args: any) => {
|
||||
// @ts-ignore
|
||||
set(...args);
|
||||
set("updated", true);
|
||||
};
|
||||
|
||||
const load = () => {
|
||||
console.log("load server");
|
||||
getBuild(p.build._id!).then((build) => {
|
||||
@@ -35,11 +37,14 @@ export const ConfigProvider: Component<{ build: Build }> = (p) => {
|
||||
...build,
|
||||
loaded: true,
|
||||
updated: false,
|
||||
saving: false
|
||||
});
|
||||
});
|
||||
};
|
||||
createEffect(load);
|
||||
|
||||
const save = () => {
|
||||
setBuild("saving", true);
|
||||
ws.send(UPDATE_BUILD, { build });
|
||||
};
|
||||
|
||||
@@ -3,8 +3,9 @@ import { useAppState } from "../../../state/StateProvider";
|
||||
import { combineClasses } from "../../../util/helpers";
|
||||
import Tabs from "../../util/tabs/Tabs";
|
||||
import s from "../build.module.css";
|
||||
import Config from "./config/Config";
|
||||
import { ConfigProvider } from "./config/Provider";
|
||||
import BuildConfig from "./build-config/BuildConfig";
|
||||
import GitConfig from "./git-config/GitConfig";
|
||||
import { ConfigProvider } from "./Provider";
|
||||
|
||||
const BuildTabs: Component<{}> = (p) => {
|
||||
const { builds, selected } = useAppState();
|
||||
@@ -16,9 +17,13 @@ const BuildTabs: Component<{}> = (p) => {
|
||||
containerClass={combineClasses(s.Card, s.Tabs, "shadow")}
|
||||
tabs={[
|
||||
{
|
||||
title: "config",
|
||||
element: <Config />,
|
||||
title: "repo",
|
||||
element: <GitConfig />,
|
||||
},
|
||||
{
|
||||
title: "build",
|
||||
element: <BuildConfig />
|
||||
}
|
||||
]}
|
||||
localStorageKey="build-tab"
|
||||
/>
|
||||
|
||||
@@ -7,18 +7,16 @@ import Grid from "../../../util/layout/Grid";
|
||||
import s from "../../build.module.css";
|
||||
import CliBuild from "./CliBuild";
|
||||
import Docker from "./Docker";
|
||||
import Git from "./Git";
|
||||
import { useConfig } from "./Provider";
|
||||
import { useConfig } from "../Provider";
|
||||
|
||||
const Config: Component<{}> = (p) => {
|
||||
const { build, reset, save } = useConfig();
|
||||
return (
|
||||
const BuildConfig: Component<{}> = (p) => {
|
||||
const { build, reset, save } = useConfig();
|
||||
return (
|
||||
<Show when={build.loaded}>
|
||||
<Grid class={s.Config}>
|
||||
<Grid class={combineClasses(s.ConfigItems, "scroller")}>
|
||||
<Git />
|
||||
<CliBuild />
|
||||
<Docker />
|
||||
<Docker />
|
||||
</Grid>
|
||||
<Show when={build.updated}>
|
||||
<Flex style={{ "place-self": "center", padding: "1rem" }}>
|
||||
@@ -35,6 +33,6 @@ const Config: Component<{}> = (p) => {
|
||||
</Grid>
|
||||
</Show>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default Config;
|
||||
export default BuildConfig;
|
||||
@@ -4,7 +4,7 @@ import Input from "../../../util/Input";
|
||||
import Flex from "../../../util/layout/Flex";
|
||||
import Grid from "../../../util/layout/Grid";
|
||||
import s from "../../build.module.css";
|
||||
import { useConfig } from "./Provider";
|
||||
import { useConfig } from "../Provider";
|
||||
|
||||
const CliBuild: Component<{}> = (p) => {
|
||||
const { build, setBuild } = useConfig();
|
||||
@@ -17,7 +17,7 @@ const CliBuild: Component<{}> = (p) => {
|
||||
<Input
|
||||
placeholder="from root of repo"
|
||||
value={build.cliBuild?.path}
|
||||
onEdit={(value) => setBuild("cliBuild", "path", value)}
|
||||
onEdit={(path) => setBuild("cliBuild", { path })}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex justifyContent="space-between" alignItems="center">
|
||||
@@ -25,7 +25,7 @@ const CliBuild: Component<{}> = (p) => {
|
||||
<Input
|
||||
placeholder="ie. yarn build"
|
||||
value={build.cliBuild?.command}
|
||||
onEdit={(value) => setBuild("cliBuild", "command", value)}
|
||||
onEdit={(command) => setBuild("cliBuild", { command })}
|
||||
/>
|
||||
</Flex>
|
||||
</Grid>
|
||||
@@ -4,7 +4,7 @@ import Input from "../../../util/Input";
|
||||
import Flex from "../../../util/layout/Flex";
|
||||
import Grid from "../../../util/layout/Grid";
|
||||
import s from "../../build.module.css";
|
||||
import { useConfig } from "./Provider";
|
||||
import { useConfig } from "../Provider";
|
||||
|
||||
const Docker: Component<{}> = (p) => {
|
||||
const { build, setBuild } = useConfig();
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Component } from "solid-js";
|
||||
import { combineClasses } from "../../../../util/helpers";
|
||||
import Grid from "../../../util/layout/Grid";
|
||||
import { useConfig } from "./Provider";
|
||||
import { useConfig } from "../Provider";
|
||||
import s from "../../build.module.css";
|
||||
import Flex from "../../../util/layout/Flex";
|
||||
import Input from "../../../util/Input";
|
||||
@@ -39,4 +39,4 @@ const Git: Component<{}> = (p) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default Git;
|
||||
export default Git;
|
||||
43
frontend/src/components/builds/tabs/git-config/GitConfig.tsx
Normal file
43
frontend/src/components/builds/tabs/git-config/GitConfig.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import { Component, Show } from "solid-js";
|
||||
import { combineClasses } from "../../../../util/helpers";
|
||||
import ConfirmButton from "../../../util/ConfirmButton";
|
||||
import Icon from "../../../util/icons/Icon";
|
||||
import Flex from "../../../util/layout/Flex";
|
||||
import Grid from "../../../util/layout/Grid";
|
||||
import Loading from "../../../util/loading/Loading";
|
||||
import s from "../../build.module.css";
|
||||
import { useConfig } from "../Provider";
|
||||
import Git from "./Git";
|
||||
import OnClone from "./OnClone";
|
||||
|
||||
const GitConfig: Component<{}> = (p) => {
|
||||
const { build, reset, save } = useConfig();
|
||||
return (
|
||||
<Show when={build.loaded}>
|
||||
<Grid class={s.Config}>
|
||||
<Grid class={combineClasses(s.ConfigItems, "scroller")}>
|
||||
<Git />
|
||||
<OnClone />
|
||||
</Grid>
|
||||
<Show when={build.updated}>
|
||||
<Show when={!build.saving} fallback={<button class="green">
|
||||
<Loading type="spinner" />
|
||||
</button>}>
|
||||
<Flex style={{ "place-self": "center", padding: "1rem" }}>
|
||||
<button onClick={reset}>
|
||||
reset
|
||||
<Icon type="reset" />
|
||||
</button>
|
||||
<ConfirmButton onConfirm={save} color="green">
|
||||
save
|
||||
<Icon type="floppy-disk" />
|
||||
</ConfirmButton>
|
||||
</Flex>
|
||||
</Show>
|
||||
</Show>
|
||||
</Grid>
|
||||
</Show>
|
||||
);
|
||||
};
|
||||
|
||||
export default GitConfig;
|
||||
31
frontend/src/components/builds/tabs/git-config/OnClone.tsx
Normal file
31
frontend/src/components/builds/tabs/git-config/OnClone.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Component } from "solid-js";
|
||||
import { combineClasses } from "../../../../util/helpers";
|
||||
import Input from "../../../util/Input";
|
||||
import Grid from "../../../util/layout/Grid";
|
||||
import { useConfig } from "../Provider";
|
||||
import s from "../../build.module.css";
|
||||
|
||||
const OnClone: Component = () => {
|
||||
const { build, setBuild } = useConfig();
|
||||
return (
|
||||
<Grid class={combineClasses(s.ConfigItem, "shadow")}>
|
||||
<h1>on clone</h1>
|
||||
<Input
|
||||
placeholder="path"
|
||||
value={build.onClone?.path}
|
||||
onEdit={(path) => {
|
||||
setBuild("onClone", { path });
|
||||
}}
|
||||
/>
|
||||
<Input
|
||||
placeholder="command"
|
||||
value={build.onClone?.command}
|
||||
onEdit={(command) => {
|
||||
setBuild("onClone", { command });
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default OnClone;
|
||||
@@ -89,7 +89,7 @@ const Update: Component<{ update: UpdateType; showName: boolean }> = (p) => {
|
||||
</pre>
|
||||
<Show when={p.update.log.stderr}>
|
||||
<div>stderr</div>
|
||||
<pre class={s.Log}>{p.update.log.stderr}</pre>
|
||||
<pre class={combineClasses(s.Log, "scroller")}>{p.update.log.stderr}</pre>
|
||||
</Show>
|
||||
<Show when={p.update.log.stdout}>
|
||||
<div>stdout</div>
|
||||
|
||||
26
frontend/src/components/util/toggle/Toggle.tsx
Normal file
26
frontend/src/components/util/toggle/Toggle.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Component, JSXElement } from "solid-js";
|
||||
import Flex from "../layout/Flex";
|
||||
|
||||
const Toggle: Component<{
|
||||
label?: JSXElement;
|
||||
toggled: boolean;
|
||||
onChange?: (toggled: boolean) => void;
|
||||
}> = (p) => {
|
||||
return (
|
||||
<Flex alignItems="center" justifyContent="space-between">
|
||||
{p.label}
|
||||
<label class="switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={p.toggled}
|
||||
onInput={() => {
|
||||
p.onChange && p.onChange(!p.toggled);
|
||||
}}
|
||||
/>
|
||||
<span class="slider round"></span>
|
||||
</label>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default Toggle;
|
||||
65
frontend/src/components/util/toggle/css.ts
Normal file
65
frontend/src/components/util/toggle/css.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
export function toggleCss(scale = 0.75) {
|
||||
return `
|
||||
.toggle {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: ${60 * scale}px;
|
||||
height: ${34 * scale}px;
|
||||
}
|
||||
|
||||
/* Hide default HTML checkbox */
|
||||
.toggle input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/* The slider */
|
||||
.slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #ccc;
|
||||
-webkit-transition: .4s;
|
||||
transition: .4s;
|
||||
}
|
||||
|
||||
.slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
left: 4px;
|
||||
bottom: 4px;
|
||||
background-color: white;
|
||||
-webkit-transition: .4s;
|
||||
transition: .4s;
|
||||
}
|
||||
|
||||
input:checked + .slider {
|
||||
background-color: #2196F3;
|
||||
}
|
||||
|
||||
input:focus + .slider {
|
||||
box-shadow: 0 0 1px #2196F3;
|
||||
}
|
||||
|
||||
input:checked + .slider:before {
|
||||
-webkit-transform: translateX(${26 * scale}px);
|
||||
-ms-transform: translateX(${26 * scale}px);
|
||||
transform: translateX(${26 * scale}px);
|
||||
}
|
||||
|
||||
/* Rounded sliders */
|
||||
.slider.round {
|
||||
border-radius: ${30 * scale}px;
|
||||
}
|
||||
|
||||
.slider.round:before {
|
||||
border-radius: 50%;
|
||||
}
|
||||
`;
|
||||
}
|
||||
2
types/types.d.ts
vendored
2
types/types.d.ts
vendored
@@ -60,7 +60,7 @@ export type DockerBuildArgs = {
|
||||
// maybe best for admins to add premade curated command strings, so user dev can't input them directly, only give path to run in.
|
||||
export type Command = {
|
||||
name: string;
|
||||
path: string;
|
||||
path?: string;
|
||||
command: string; // no cd and no sudo
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import { Build, Conversion, Deployment, EnvironmentVar, Server } from "@monitor/types";
|
||||
import {
|
||||
Build,
|
||||
Conversion,
|
||||
Deployment,
|
||||
EnvironmentVar,
|
||||
Server,
|
||||
} from "@monitor/types";
|
||||
import { objFrom2Arrays } from "./helpers";
|
||||
|
||||
const deploymentViewFields = [
|
||||
const deploymentViewFields: (keyof Deployment)[] = [
|
||||
"name",
|
||||
"image",
|
||||
"buildID",
|
||||
@@ -9,10 +15,7 @@ const deploymentViewFields = [
|
||||
"volumes",
|
||||
"environment",
|
||||
"network",
|
||||
"useServerRoot",
|
||||
"restart",
|
||||
"logTail",
|
||||
"autoDeploy",
|
||||
"containerUser",
|
||||
"postImage",
|
||||
]; // the fields shown in the update log
|
||||
@@ -196,18 +199,12 @@ function volumesChangelog(oldVols?: Conversion[], newVols?: Conversion[]) {
|
||||
return "";
|
||||
} else {
|
||||
return `Added Volumes:\n${newVols
|
||||
.map(
|
||||
(vol) =>
|
||||
`\t${vol.local}: ${vol.container},\n`
|
||||
)
|
||||
.map((vol) => `\t${vol.local}: ${vol.container},\n`)
|
||||
.reduce((prev, curr) => prev + curr)}`;
|
||||
}
|
||||
} else if (!newVols) {
|
||||
return `Removed Volumes:\n${oldVols
|
||||
.map(
|
||||
(vol) =>
|
||||
`\t${vol.local}: ${vol.container},\n`
|
||||
)
|
||||
.map((vol) => `\t${vol.local}: ${vol.container},\n`)
|
||||
.reduce((prev, curr) => prev + curr)}`;
|
||||
} else {
|
||||
const oldLocal = oldVols.map((vol) => vol.local);
|
||||
@@ -221,10 +218,7 @@ function volumesChangelog(oldVols?: Conversion[], newVols?: Conversion[]) {
|
||||
const deletions: string[] = [];
|
||||
oldLocal.forEach((local) => {
|
||||
if (newLocal.includes(local)) {
|
||||
if (
|
||||
oldObj[local] !== newObj[local]
|
||||
)
|
||||
changes.push(local);
|
||||
if (oldObj[local] !== newObj[local]) changes.push(local);
|
||||
} else {
|
||||
deletions.push(local);
|
||||
}
|
||||
@@ -239,10 +233,7 @@ function volumesChangelog(oldVols?: Conversion[], newVols?: Conversion[]) {
|
||||
additions.length > 0
|
||||
? `\tAdditions:\n` +
|
||||
additions
|
||||
.map(
|
||||
(addition) =>
|
||||
`\t\t${addition}: ${newObj[addition]},\n`
|
||||
)
|
||||
.map((addition) => `\t\t${addition}: ${newObj[addition]},\n`)
|
||||
.reduce((prev, curr) => prev + curr)
|
||||
: "";
|
||||
const changesString =
|
||||
@@ -259,10 +250,7 @@ function volumesChangelog(oldVols?: Conversion[], newVols?: Conversion[]) {
|
||||
deletions.length > 0
|
||||
? `\tDeletions:\n` +
|
||||
deletions
|
||||
.map(
|
||||
(deletion) =>
|
||||
`\t\t${deletion}: ${oldObj[deletion]},\n`
|
||||
)
|
||||
.map((deletion) => `\t\t${deletion}: ${oldObj[deletion]},\n`)
|
||||
.reduce((prev, curr) => prev + curr)
|
||||
: "";
|
||||
const show =
|
||||
@@ -275,14 +263,13 @@ function volumesChangelog(oldVols?: Conversion[], newVols?: Conversion[]) {
|
||||
}
|
||||
}
|
||||
|
||||
const buildViewFields = [
|
||||
const buildViewFields: (keyof Build)[] = [
|
||||
"name",
|
||||
"repoURL",
|
||||
"repoName",
|
||||
"repo",
|
||||
"branch",
|
||||
"buildPath",
|
||||
"dockerfilePath",
|
||||
"owner",
|
||||
"onClone",
|
||||
"cliBuild",
|
||||
"dockerBuildArgs",
|
||||
]; // the fields shown in the update log
|
||||
|
||||
export function buildChangelog(oldBuild: Build, newBuild: Build) {
|
||||
|
||||
@@ -69,12 +69,12 @@ export function generateQuery(query?: Collection<string | number | undefined>) {
|
||||
}
|
||||
|
||||
export function mergeCommandLogError(
|
||||
...cle: ({ name: string; cle: CommandLogError | undefined })[]
|
||||
...cle: { name: string; cle: CommandLogError | undefined }[]
|
||||
) {
|
||||
const _cle = cle.filter((cle) => cle.cle) as {
|
||||
name: string;
|
||||
cle: CommandLogError;
|
||||
}[];
|
||||
}[];
|
||||
const command = _cle.reduce((prev, curr) => {
|
||||
return prev + (prev && "\n\n") + `${curr.name}: ${curr.cle.command}`;
|
||||
}, "");
|
||||
@@ -82,12 +82,16 @@ export function mergeCommandLogError(
|
||||
(log, curr) => {
|
||||
log.stdout =
|
||||
log.stdout +
|
||||
(log.stdout && "\n\n") +
|
||||
`${curr.name}:\n${curr.cle.log.stdout}`;
|
||||
(log.stdout && (curr.cle.log.stdout ? "\n\n" : "")) +
|
||||
curr.cle.log.stdout
|
||||
? `${curr.name}:\n${curr.cle.log.stdout}`
|
||||
: "";
|
||||
log.stderr =
|
||||
log.stderr +
|
||||
(log.stderr && "\n\n") +
|
||||
`${curr.name}:\n${curr.cle.log.stderr}`;
|
||||
(log.stderr && (curr.cle.log.stderr ? "\n\n" : "")) +
|
||||
curr.cle.log.stderr
|
||||
? `${curr.name}:\n${curr.cle.log.stderr}`
|
||||
: "";
|
||||
return log;
|
||||
},
|
||||
{ stdout: "", stderr: "" }
|
||||
|
||||
Reference in New Issue
Block a user