Skip to content
This repository was archived by the owner on Nov 7, 2025. It is now read-only.

Commit 1578231

Browse files
committed
chore: improve environment config
- Rename run command to start command - Move server settings to its own section - Rename serverless to serverless functions - Deprecate server folder (use output folder instead)
1 parent 280edee commit 1578231

File tree

13 files changed

+203
-207
lines changed

13 files changed

+203
-207
lines changed

src/layouts/AppLayout/_components/DeployModal.spec.tsx

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
44
import { waitFor, fireEvent } from "@testing-library/react";
55
import mockApp from "~/testing/data/mock_app";
66
import mockEnv from "~/testing/data/mock_environment";
7-
import { mockFetchRepoMeta } from "~/testing/nocks/nock_environment";
87
import { mockDeployNow } from "~/testing/nocks/nock_deployments";
98
import userEvent from "@testing-library/user-event";
109
import DeployModal from "./DeployModal";
@@ -59,16 +58,7 @@ describe("~/layouts/AppLayout/_components/DeployModal.tsx", () => {
5958
currentEnv.build.buildCmd = "";
6059
currentEnv.build.distFolder = "";
6160

62-
const fetchMetaScope = mockFetchRepoMeta({
63-
name: currentEnv.name,
64-
appId: currentApp.id,
65-
});
66-
6761
createWrapper({ app: currentApp, env: currentEnv });
68-
69-
await waitFor(() => {
70-
expect(fetchMetaScope.isDone()).toBe(true);
71-
});
7262
});
7363

7464
afterEach(() => {

src/layouts/AppLayout/_components/DeployModal.tsx

Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import { useState } from "react";
22
import { useNavigate } from "react-router";
33
import { deploy } from "~/pages/apps/actions";
4-
import { useFetchRepoMeta } from "~/pages/apps/[id]/environments/[env-id]/config/actions";
5-
import { isFrameworkRecognized } from "~/pages/apps/[id]/environments/[env-id]/config/helpers";
64
import Link from "@mui/material/Link";
75
import Box from "@mui/material/Box";
86
import Button from "@mui/lab/LoadingButton";
@@ -16,7 +14,6 @@ import EnvironmentSelector from "~/components/EnvironmentSelector";
1614
import Card from "~/components/Card";
1715
import CardHeader from "~/components/CardHeader";
1816
import CardFooter from "~/components/CardFooter";
19-
import Spinner from "~/components/Spinner";
2017

2118
interface Props {
2219
app: App;
@@ -35,7 +32,6 @@ export default function DeployModal({
3532
const [selectedEnv, setSelectedEnv] = useState<Environment | undefined>(
3633
environment
3734
);
38-
const fetchResult = useFetchRepoMeta({ app, env: selectedEnv });
3935
const [cmd, setCmd] = useState(environment?.build?.buildCmd || "");
4036
const [dist, setDist] = useState(environment?.build?.distFolder || "");
4137
const [branch, setBranch] = useState(environment?.branch || "");
@@ -44,7 +40,6 @@ export default function DeployModal({
4440
environment?.autoPublish || false
4541
);
4642
const [loading, setLoading] = useState<boolean>(false);
47-
const { meta, loading: metaLoading } = fetchResult;
4843

4944
const clearForm = () => {
5045
setCmd("");
@@ -152,26 +147,16 @@ export default function DeployModal({
152147
/>
153148
</Box>
154149
<Box sx={{ mb: 4 }}>
155-
{!metaLoading && isFrameworkRecognized(meta?.framework) ? (
156-
<Box sx={{ cursor: "not-allowed" }}>
157-
<span className="fa fa-info-circle mr-2 ml-1" />
158-
Output folder read from framework configuration file.
159-
</Box>
160-
) : (
161-
<TextField
162-
value={dist}
163-
variant="filled"
164-
label="Output folder"
165-
fullWidth
166-
name="build.distFolder"
167-
onChange={e => setDist(e.target.value)}
168-
placeholder="Defaults to `build`, `dist`, `output` or `.stormkit`"
169-
helperText="The folder where the build artifacts are located"
170-
InputProps={{
171-
endAdornment: metaLoading && <Spinner width={4} height={4} />,
172-
}}
173-
/>
174-
)}
150+
<TextField
151+
value={dist}
152+
variant="filled"
153+
label="Output folder"
154+
fullWidth
155+
name="build.distFolder"
156+
onChange={e => setDist(e.target.value)}
157+
placeholder="Defaults to `build`, `dist`, `output` or `.stormkit`"
158+
helperText="The folder where the build artifacts are located"
159+
/>
175160
</Box>
176161
<Box sx={{ bgcolor: "container.paper", p: 1.75, pt: 1, mb: 4 }}>
177162
<FormControlLabel

src/pages/apps/[id]/environments/[env-id]/config/EnvironmentConfig.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ import ListItem from "@mui/material/ListItem";
77
import Typography from "@mui/material/Typography";
88
import { AppContext } from "~/pages/apps/[id]/App.context";
99
import { EnvironmentContext } from "~/pages/apps/[id]/environments/Environment.context";
10+
import { isSelfHosted } from "~/utils/helpers/instance";
1011
import TabDomainConfig from "./_components/TabDomainConfig/TabDomainConfig";
1112
import TabConfigEnvVars from "./_components/TabConfigEnvVars";
1213
import TabConfigGeneral from "./_components/TabConfigGeneral";
1314
import TabConfigBuild from "./_components/TabConfigBuild";
15+
import TabConfigServer from "./_components/TabConfigServer";
1416
import TabConfigHeaders from "./_components/TabConfigHeaders";
1517
import TabConfigRedirects from "./_components/TabConfigRedirects";
1618
import TabConfigServerless from "./_components/TabConfigServerless";
@@ -19,9 +21,12 @@ import TabStatusChecks from "./_components/TabStatusChecks";
1921
import TabAPIKey from "./_components/TabAPIKey";
2022
import TabMailer from "./_components/TabMailer";
2123

24+
const selfHosted = isSelfHosted();
25+
2226
interface NavItem {
2327
path: string;
2428
text: string;
29+
visible?: boolean;
2530
}
2631

2732
interface NavItemParent {
@@ -35,14 +40,15 @@ const listItems: NavItemParent[] = [
3540
children: [
3641
{ path: "#general", text: "General" },
3742
{ path: "#build", text: "Build" },
43+
{ path: "#server", text: "Server", visible: selfHosted },
3844
{ path: "#env-vars", text: "Environment variables" },
3945
{ path: "#status-checks", text: "Status checks" },
40-
],
46+
].filter(i => i.visible !== false),
4147
},
4248
{
4349
title: "Runtime Settings",
4450
children: [
45-
{ path: "#serverless", text: "Serverless" },
51+
{ path: "#serverless", text: "Serverless functions" },
4652
{ path: "#headers", text: "Headers" },
4753
{ path: "#redirects", text: "Redirects" },
4854
],
@@ -102,6 +108,7 @@ export default function EnvironmentConfig() {
102108
case "":
103109
case "#general":
104110
case "#build":
111+
case "#server":
105112
case "#env-vars":
106113
case "#status-checks":
107114
return ({ app, environment, setRefreshToken }: TabProps) => (
@@ -116,6 +123,13 @@ export default function EnvironmentConfig() {
116123
environment={environment}
117124
setRefreshToken={setRefreshToken}
118125
/>
126+
{selfHosted && (
127+
<TabConfigServer
128+
app={app}
129+
environment={environment}
130+
setRefreshToken={setRefreshToken}
131+
/>
132+
)}
119133
<TabConfigEnvVars
120134
app={app}
121135
environment={environment}

src/pages/apps/[id]/environments/[env-id]/config/_components/TabConfigBuild.tsx

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,7 @@ import Button from "@mui/lab/LoadingButton";
66
import Card from "~/components/Card";
77
import CardHeader from "~/components/CardHeader";
88
import CardFooter from "~/components/CardFooter";
9-
import Spinner from "~/components/Spinner";
10-
import { isFrameworkRecognized } from "../helpers";
11-
import {
12-
updateEnvironment,
13-
buildFormValues,
14-
useFetchRepoMeta,
15-
} from "../actions";
9+
import { updateEnvironment, buildFormValues } from "../actions";
1610

1711
interface Props {
1812
app: App;
@@ -29,7 +23,6 @@ export default function TabConfigGeneral({
2923
const [success, setSuccess] = useState<string>();
3024
const [isLoading, setLoading] = useState(false);
3125
const [root, setRoot] = useState(env?.build?.vars?.["SK_CWD"] || "./");
32-
const { meta, loading: metaLoading } = useFetchRepoMeta({ app, env });
3326

3427
if (!env) {
3528
return <></>;
@@ -95,22 +88,27 @@ export default function TabConfigGeneral({
9588
label="Output folder"
9689
variant="filled"
9790
autoComplete="off"
98-
defaultValue={env?.build.distFolder || ""}
91+
defaultValue={
92+
env?.build.distFolder || env?.build.serverFolder || "./"
93+
}
9994
fullWidth
100-
disabled={metaLoading || isFrameworkRecognized(meta?.framework)}
10195
name="build.distFolder"
102-
InputProps={{
103-
endAdornment: metaLoading && <Spinner width={4} height={4} />,
104-
}}
105-
placeholder={
106-
!metaLoading && !isFrameworkRecognized(meta?.framework)
107-
? "Output folder is not needed. It is taken from the framework configuration file."
108-
: "Defaults to `build`, `dist`, `output` or `.stormkit`"
109-
}
96+
placeholder="Defaults to `build`, `dist`, `output` or `.stormkit`"
11097
helperText={
111-
!metaLoading &&
112-
!isFrameworkRecognized(meta?.framework) &&
113-
"The content of this folder will be served by Stormkit."
98+
<>
99+
The folder containing your built assets. For many projects, this
100+
is either{" "}
101+
<Box component="code" sx={{ fontSize: 11, px: 0.5, py: 0.25 }}>
102+
dist
103+
</Box>{" "}
104+
<Box component="code" sx={{ fontSize: 11, px: 0.5, py: 0.25 }}>
105+
build
106+
</Box>{" "}
107+
<Box component="code" sx={{ fontSize: 11, px: 0.5, py: 0.25 }}>
108+
output
109+
</Box>
110+
.
111+
</>
114112
}
115113
/>
116114
</Box>
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { RenderResult, waitFor } from "@testing-library/react";
2+
import { describe, expect, it, vi, type Mock } from "vitest";
3+
import { fireEvent, render } from "@testing-library/react";
4+
import userEvent from "@testing-library/user-event";
5+
import mockApp from "~/testing/data/mock_app";
6+
import mockEnvironments from "~/testing/data/mock_environments";
7+
import { mockUpdateEnvironment } from "~/testing/nocks/nock_environment";
8+
import TabConfigServer from "./TabConfigServer";
9+
10+
interface WrapperProps {
11+
app?: App;
12+
environment?: Environment;
13+
setRefreshToken?: () => void;
14+
}
15+
16+
describe("~/pages/apps/[id]/environments/[env-id]/config/_components/TabConfigServer.tsx", () => {
17+
let wrapper: RenderResult;
18+
let currentApp: App;
19+
let currentEnv: Environment;
20+
let setRefreshToken: Mock;
21+
22+
const createWrapper = ({ app, environment }: WrapperProps) => {
23+
setRefreshToken = vi.fn();
24+
currentApp = app || mockApp();
25+
currentEnv = environment || mockEnvironments({ app: currentApp })[0];
26+
27+
wrapper = render(
28+
<TabConfigServer
29+
app={currentApp}
30+
environment={currentEnv}
31+
setRefreshToken={setRefreshToken}
32+
/>
33+
);
34+
};
35+
36+
it("should have a form", () => {
37+
createWrapper({});
38+
39+
expect(wrapper.getByText("Server settings")).toBeTruthy();
40+
expect(
41+
wrapper.getByText("Configure your long-running processes.")
42+
).toBeTruthy();
43+
44+
const startCmdInput = wrapper.getByLabelText(
45+
"Start command"
46+
) as HTMLInputElement;
47+
48+
expect(startCmdInput.value).toBe("");
49+
});
50+
51+
it("should submit the form", async () => {
52+
createWrapper({});
53+
54+
await userEvent.type(
55+
wrapper.getByLabelText("Start command"),
56+
"npm run start"
57+
);
58+
59+
const scope = mockUpdateEnvironment({
60+
environment: {
61+
...currentEnv,
62+
build: {
63+
...currentEnv.build,
64+
serverCmd: "npm run start",
65+
},
66+
},
67+
status: 200,
68+
response: {
69+
ok: true,
70+
},
71+
});
72+
73+
fireEvent.click(wrapper.getByText("Save"));
74+
75+
await waitFor(() => {
76+
expect(scope.isDone()).toBe(true);
77+
});
78+
});
79+
});
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import Box from "@mui/material/Box";
2+
import TextField from "@mui/material/TextField";
3+
import Button from "@mui/lab/LoadingButton";
4+
import Card from "~/components/Card";
5+
import CardHeader from "~/components/CardHeader";
6+
import CardFooter from "~/components/CardFooter";
7+
import { useSubmitHandler } from "../actions";
8+
9+
interface Props {
10+
app: App;
11+
environment: Environment;
12+
setRefreshToken: (v: number) => void;
13+
}
14+
15+
export default function TabConfigServer({
16+
environment: env,
17+
app,
18+
setRefreshToken,
19+
}: Props) {
20+
const { error, success, isLoading, submitHandler } = useSubmitHandler({
21+
env,
22+
app,
23+
setRefreshToken,
24+
});
25+
26+
if (!env) {
27+
return <></>;
28+
}
29+
30+
return (
31+
<Card
32+
id="server"
33+
component="form"
34+
sx={{ mb: 2 }}
35+
error={error}
36+
success={success}
37+
onSubmit={submitHandler}
38+
>
39+
<CardHeader
40+
title="Server settings"
41+
subtitle="Configure your long-running processes."
42+
/>
43+
<Box sx={{ mb: 4 }}>
44+
<TextField
45+
label="Start command"
46+
variant="filled"
47+
autoComplete="off"
48+
defaultValue={env?.build.serverCmd || ""}
49+
name="build.serverCmd"
50+
fullWidth
51+
InputLabelProps={{
52+
shrink: true,
53+
}}
54+
placeholder="npm run start"
55+
helperText={
56+
"The command to run your server application. Leave empty if you don't have any."
57+
}
58+
sx={{ mb: 4 }}
59+
/>
60+
</Box>
61+
62+
<CardFooter>
63+
<Button
64+
type="submit"
65+
variant="contained"
66+
color="secondary"
67+
loading={isLoading}
68+
>
69+
Save
70+
</Button>
71+
</CardFooter>
72+
</Card>
73+
);
74+
}

0 commit comments

Comments
 (0)