Skip to content

Commit 0f940c6

Browse files
committed
feat(env): implement unified file loading in console-web
refs #313
1 parent 1fec106 commit 0f940c6

File tree

103 files changed

+762
-551
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

103 files changed

+762
-551
lines changed

.dockerignore

-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
**/.env.*.local
99
**/.env.*.test
1010

11-
apps/deploy-web/.env*
1211
apps/indexer/.env*
1312
apps/landing/.env*
1413
apps/provider-console/.env*

.gitignore

-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ build.ps1
6565
.yarn-integrity
6666

6767
# dotenv environment variable files
68-
apps/deploy-web/.env
6968
apps/indexer/.env
7069
apps/provider-console/.env
7170
apps/provider-proxy/.env

apps/deploy-web/.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,5 @@ sentry.properties
4444
/playwright-report/
4545
/blob-report/
4646
/playwright/.cache/
47+
48+
env-config.schema.js

apps/deploy-web/README.md

+30-18
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,33 @@ The website should be accessible: [http://localhost:3000/](http://localhost:3000
1414

1515
## Environment Variables
1616

17-
When running the api locally the following environment variables can be set in a `.env.local` file.
18-
19-
It is possible to run the website locally without any environment variables, but the login feature will be unavailable.
20-
21-
|Name|Value|Note|
22-
|-|-|-
23-
|NEXT_PUBLIC_GA_MEASUREMENT_ID|ex: `G-87H3KK3D`|Google Analytics ID
24-
|NEXT_PUBLIC_SENTRY_DSN|ex: `"https://[email protected]/1234"`|[Sentry DSN](https://docs.sentry.io/product/sentry-basics/dsn-explainer/) used when initializing Sentry in [sentry.client.config.js](./sentry.client.config.js) and [sentry.server.config.js](./sentry.server.config.js)
25-
|AUTH0_SECRET||
26-
|AUTH0_BASE_URL||
27-
|AUTH0_ISSUER_BASE_URL||
28-
|AUTH0_CLIENT_ID||
29-
|AUTH0_CLIENT_SECRET||
30-
|AUTH0_AUDIENCE||
31-
|AUTH0_SCOPE||
32-
|AUTH0_M2M_DOMAIN||
33-
|AUTH0_M2M_CLIENT_ID||
34-
|AUTH0_M2M_CLIENT_SECRET||
17+
### Overview
18+
Environment variables in this Next.js app follow the standard Next.js behavior, as documented in the [Next.js environment variables documentation](https://nextjs.org/docs/basic-features/environment-variables). This means that files like `.env.local` or `.env.production` will be automatically loaded based on the environment in which the app is running.
19+
20+
However, we have extended this functionality to support more granular environment-specific configurations. Environment variables are stored in the `./env` directory, where multiple `.env` files exist for different deployment environments (stages):
21+
22+
- `.env` - Loaded for any environment
23+
- `.env.production` - Loaded for the production stage
24+
- `.env.staging` - Loaded for the staging stage
25+
26+
### How Environment Variables Are Loaded
27+
We use **dotenvx** to manage and load environment variables. This allows us to take advantage of its features, such as **variable interpolation** (i.e., using other environment variables within variable values).
28+
29+
### Validation with Zod
30+
Environment variables are validated using **Zod** schemas, ensuring that all required variables are present and have valid values. The validation logic can be found in the file `src/config/env-config.schema.ts`.
31+
32+
We use two separate Zod schemas:
33+
- **Static Build-Time Schema**: Validates variables at build time. If any variables are missing or invalid during the build process, the build will fail.
34+
- **Dynamic Server Runtime Schema**: Validates variables at server startup. If any variables are missing or invalid at this stage, the server will fail to start.
35+
36+
This validation ensures that both build and runtime configurations are secure and complete before the app runs.
37+
38+
### App Configuration
39+
App configurations, including environment variables, are located in the `src/config` directory. In our setup:
40+
- **Environment configs** are handled separately from **hardcoded configs**.
41+
- Hardcoded configs are organized by domain to maintain a clear structure and separation of concerns.
42+
43+
### Sample Environment Variables
44+
All environment variables required for the app, along with their expected structure and types, can be found in the `env/.env.sample` file. This sample file serves as a template for setting up your environment variables and ensures that all necessary variables are accounted for in each environment.
45+
46+
By organizing environment variables and configuration this way, we ensure a consistent, safe, and scalable approach to managing different deployment environments.

apps/deploy-web/env.config.js

-26
This file was deleted.

apps/deploy-web/env/.env

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
AUTH0_AUDIENCE=https://api.cloudmos.io
2+
AUTH0_SCOPE=openid profile email offline_access

apps/deploy-web/env/.env.production

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
AUTH0_BASE_URL=https://console.akash.network
2+
AUTH0_ISSUER_BASE_URL=https://auth.cloudmos.io
3+
4+
NEXT_PUBLIC_BILLING_ENABLED=false
5+
NEXT_PUBLIC_MANAGED_WALLET_NETWORK_ID=mainnet
6+
NEXT_PUBLIC_MANAGED_WALLET_DENOM=usdc
7+
# TODO: replace with a fresh master wallet before release
8+
NEXT_PUBLIC_MASTER_WALLET_ADDRESS=akash1ss0d2yw38r6e7ew8ndye9h7kg62sem36zak4d5
9+
10+
NEXT_PUBLIC_STATS_APP_URL=https://stats.akash.network
11+
NEXT_PUBLIC_PROVIDER_PROXY_URL=https://providerproxy.cloudmos.io
12+
NEXT_PUBLIC_PROVIDER_PROXY_URL_WS=wss://providerproxy.cloudmos.io
13+
NEXT_PUBLIC_NODE_ENV=$NODE_ENV
14+
NEXT_PUBLIC_BASE_API_MAINNET_URL=https://api.cloudmos.io
15+
NEXT_PUBLIC_BASE_API_SANDBOX_URL=https://api-sandbox.cloudmos.io
16+
NEXT_PUBLIC_BASE_API_TESTNET_URL=https://api-testnet.cloudmos.io
17+
NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_BASE_API_MAINNET_URL

apps/deploy-web/env/.env.sample

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
DEPLOYMENT_ENV
2+
3+
AUTH0_BASE_URL=
4+
AUTH0_ISSUER_BASE_URL=
5+
AUTH0_SECRET
6+
AUTH0_CLIENT_ID
7+
AUTH0_CLIENT_SECRET
8+
9+
NEXT_PUBLIC_BILLING_ENABLED=
10+
NEXT_PUBLIC_MANAGED_WALLET_NETWORK_ID=
11+
NEXT_PUBLIC_MANAGED_WALLET_DENOM=
12+
NEXT_PUBLIC_MASTER_WALLET_ADDRESS=
13+
NEXT_PUBLIC_STATS_APP_URL=
14+
NEXT_PUBLIC_PROVIDER_PROXY_URL=
15+
NEXT_PUBLIC_PROVIDER_PROXY_URL_WS=
16+
NEXT_PUBLIC_NODE_ENV=
17+
NEXT_PUBLIC_BASE_API_MAINNET_URL=
18+
NEXT_PUBLIC_BASE_API_SANDBOX_URL=
19+
NEXT_PUBLIC_BASE_API_TESTNET_URL=
20+
NEXT_PUBLIC_API_BASE_URL=
21+
NEXT_PUBLIC_SENTRY_DSN=
22+
NEXT_PUBLIC_SENTRY_SERVER_NAME

apps/deploy-web/env/.env.staging

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
AUTH0_BASE_URL=https://console-beta.akash.network
2+
AUTH0_ISSUER_BASE_URL=https://dev-5aprb0lr.us.auth0.com
3+
4+
NEXT_PUBLIC_BILLING_ENABLED=true
5+
NEXT_PUBLIC_MANAGED_WALLET_NETWORK_ID=mainnet
6+
NEXT_PUBLIC_MANAGED_WALLET_DENOM=usdc
7+
NEXT_PUBLIC_MASTER_WALLET_ADDRESS=akash1ss0d2yw38r6e7ew8ndye9h7kg62sem36zak4d5
8+
9+
NEXT_PUBLIC_STATS_APP_URL=https://stats.akash.network
10+
NEXT_PUBLIC_PROVIDER_PROXY_URL=https://deployproxybeta.cloudmos.io
11+
NEXT_PUBLIC_PROVIDER_PROXY_URL_WS=wss://deployproxybeta.cloudmos.io
12+
NEXT_PUBLIC_NODE_ENV=$NODE_ENV
13+
NEXT_PUBLIC_BASE_API_MAINNET_URL=https://api-mainnet-staging.cloudmos.io
14+
NEXT_PUBLIC_BASE_API_SANDBOX_URL=https://api-sandbox-staging.cloudmos.io
15+
NEXT_PUBLIC_BASE_API_TESTNET_URL=$NEXT_PUBLIC_BASE_API_MAINNET_URL
16+
NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_BASE_API_MAINNET_URL

apps/deploy-web/next.config.js

+14-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
require("./env.config");
1+
require("@akashnetwork/env-loader");
22
const withBundleAnalyzer = require("@next/bundle-analyzer")({
33
enabled: process.env.ANALYZE === "true"
44
});
@@ -10,6 +10,16 @@ const withPWA = require("next-pwa")({
1010
});
1111
const { withSentryConfig } = require("@sentry/nextjs");
1212

13+
try {
14+
const { browserEnvSchema } = require("./env-config.schema");
15+
16+
browserEnvSchema.parse(process.env);
17+
} catch (error) {
18+
if (error.message.includes("Cannot find module")) {
19+
console.warn("No env-config.schema.js found, skipping env validation");
20+
}
21+
}
22+
1323
/**
1424
* @type {import('next').NextConfig}
1525
*/
@@ -30,10 +40,9 @@ const moduleExports = {
3040
ignoreDuringBuilds: true
3141
},
3242
transpilePackages: ["geist", "@akashnetwork/ui"],
33-
// experimental: {
34-
// // outputStandalone: true,
35-
// externalDir: true // to make the import from shared parent folder work https://github.com/vercel/next.js/issues/9474#issuecomment-810212174
36-
// },
43+
experimental: {
44+
instrumentationHook: true
45+
},
3746
publicRuntimeConfig: {
3847
version
3948
},

apps/deploy-web/package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
"license": "Apache-2.0",
77
"author": "Akash Network",
88
"scripts": {
9-
"build": "next build",
10-
"build-analyze": "set ANALYZE=true&& next build",
9+
"build": "npm run build-env-schemas && next build",
10+
"build-env-schemas": "tsc src/config/env-config.schema.ts --outDir . --skipLibCheck",
1111
"dev": "next",
1212
"format": "prettier --write ./*.{ts,js,json} **/*.{ts,tsx,js,json}",
1313
"lint": "eslint .",
@@ -17,6 +17,7 @@
1717
"dependencies": {
1818
"@akashnetwork/akash-api": "^1.3.0",
1919
"@akashnetwork/akashjs": "^0.10.0",
20+
"@akashnetwork/env-loader": "*",
2021
"@akashnetwork/http-sdk": "*",
2122
"@akashnetwork/ui": "*",
2223
"@auth0/nextjs-auth0": "^3.5.0",

apps/deploy-web/src/components/authorizations/AllowanceModal.tsx

+4-4
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ import { event } from "nextjs-google-analytics";
1010
import { z } from "zod";
1111

1212
import { LinkTo } from "@src/components/shared/LinkTo";
13+
import { UAKT_DENOM } from "@src/config/denom.config";
1314
import { useWallet } from "@src/context/WalletProvider";
1415
import { useDenomData } from "@src/hooks/useWalletBalance";
1516
import { AllowanceType } from "@src/types/grant";
1617
import { AnalyticsEvents } from "@src/utils/analytics";
17-
import { uAktDenom } from "@src/utils/constants";
1818
import { aktToUakt, coinToDenom } from "@src/utils/priceUtils";
1919
import { TransactionMessageData } from "@src/utils/TransactionMessageData";
2020

@@ -46,7 +46,7 @@ export const AllowanceModal: React.FunctionComponent<Props> = ({ editingAllowanc
4646
});
4747
const { handleSubmit, control, watch, clearErrors, setValue } = form;
4848
const { amount, granteeAddress, expiration } = watch();
49-
const denomData = useDenomData(uAktDenom);
49+
const denomData = useDenomData(UAKT_DENOM);
5050

5151
const onDepositClick = event => {
5252
event.preventDefault();
@@ -64,7 +64,7 @@ export const AllowanceModal: React.FunctionComponent<Props> = ({ editingAllowanc
6464
if (editingAllowance) {
6565
messages.push(TransactionMessageData.getRevokeAllowanceMsg(address, granteeAddress));
6666
}
67-
messages.push(TransactionMessageData.getGrantBasicAllowanceMsg(address, granteeAddress, spendLimit, uAktDenom, expirationDate));
67+
messages.push(TransactionMessageData.getGrantBasicAllowanceMsg(address, granteeAddress, spendLimit, UAKT_DENOM, expirationDate));
6868
const response = await signAndBroadcastTx(messages);
6969

7070
if (response) {
@@ -144,7 +144,7 @@ export const AllowanceModal: React.FunctionComponent<Props> = ({ editingAllowanc
144144
min={0}
145145
step={0.000001}
146146
max={denomData?.inputMax}
147-
startIcon={<span className="text-xs pl-2">{denomData?.label}</span>}
147+
startIcon={<span className="pl-2 text-xs">{denomData?.label}</span>}
148148
/>
149149
);
150150
}}

apps/deploy-web/src/components/authorizations/GrantModal.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ import { event } from "nextjs-google-analytics";
2323
import { z } from "zod";
2424

2525
import { LinkTo } from "@src/components/shared/LinkTo";
26+
import { UAKT_DENOM } from "@src/config/denom.config";
2627
import { useWallet } from "@src/context/WalletProvider";
2728
import { getUsdcDenom, useUsdcDenom } from "@src/hooks/useDenom";
2829
import { useDenomData } from "@src/hooks/useWalletBalance";
2930
import { GrantType } from "@src/types/grant";
3031
import { AnalyticsEvents } from "@src/utils/analytics";
31-
import { uAktDenom } from "@src/utils/constants";
3232
import { denomToUdenom } from "@src/utils/mathHelpers";
3333
import { aktToUakt, coinToDenom } from "@src/utils/priceUtils";
3434
import { TransactionMessageData } from "@src/utils/TransactionMessageData";
@@ -69,7 +69,7 @@ export const GrantModal: React.FunctionComponent<Props> = ({ editingGrant, addre
6969
const { handleSubmit, control, watch, clearErrors, setValue } = form;
7070
const { amount, granteeAddress, expiration, token } = watch();
7171
const selectedToken = supportedTokens.find(x => x.id === token);
72-
const denom = token === "akt" ? uAktDenom : usdcDenom;
72+
const denom = token === "akt" ? UAKT_DENOM : usdcDenom;
7373
const denomData = useDenomData(denom);
7474

7575
const onDepositClick = event => {
@@ -82,7 +82,7 @@ export const GrantModal: React.FunctionComponent<Props> = ({ editingGrant, addre
8282
clearErrors();
8383
const spendLimit = token === "akt" ? aktToUakt(amount) : denomToUdenom(amount);
8484
const usdcDenom = getUsdcDenom();
85-
const denom = token === "akt" ? uAktDenom : usdcDenom;
85+
const denom = token === "akt" ? UAKT_DENOM : usdcDenom;
8686

8787
const expirationDate = new Date(expiration);
8888
const message = TransactionMessageData.getGrantMsg(address, granteeAddress, spendLimit, expirationDate, denom);

apps/deploy-web/src/components/deployments/DeploymentDepositModal.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,13 @@ import { event } from "nextjs-google-analytics";
2525
import { useSnackbar } from "notistack";
2626
import { z } from "zod";
2727

28+
import { UAKT_DENOM } from "@src/config/denom.config";
2829
import { useSettings } from "@src/context/SettingsProvider";
2930
import { useWallet } from "@src/context/WalletProvider";
3031
import { useUsdcDenom } from "@src/hooks/useDenom";
3132
import { useDenomData } from "@src/hooks/useWalletBalance";
3233
import { useGranteeGrants } from "@src/queries/useGrantsQuery";
3334
import { AnalyticsEvents } from "@src/utils/analytics";
34-
import { uAktDenom } from "@src/utils/constants";
3535
import { denomToUdenom, udenomToDenom } from "@src/utils/mathHelpers";
3636
import { coinToUDenom, uaktToAKT } from "@src/utils/priceUtils";
3737
import { LinkTo } from "../shared/LinkTo";
@@ -185,7 +185,7 @@ export const DeploymentDepositModal: React.FunctionComponent<Props> = ({ handleC
185185
category: "deployments",
186186
label: "Use depositor to deposit in deployment"
187187
});
188-
} else if (denom === uAktDenom && deposit > uaktBalance) {
188+
} else if (denom === UAKT_DENOM && deposit > uaktBalance) {
189189
setError(`You can't deposit more than you currently have in your balance. Current balance is: ${uaktToAKT(uaktBalance)} AKT.`);
190190
return;
191191
} else if (denom === usdcIbcDenom && deposit > usdcBalance) {

apps/deploy-web/src/components/deployments/DeploymentDetail.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ import { useWallet } from "@src/context/WalletProvider";
1414
import { useDeploymentDetail } from "@src/queries/useDeploymentQuery";
1515
import { useDeploymentLeaseList } from "@src/queries/useLeaseQuery";
1616
import { useProviderList } from "@src/queries/useProvidersQuery";
17+
import { RouteStep } from "@src/types/route-steps.type";
1718
import { AnalyticsEvents } from "@src/utils/analytics";
18-
import { RouteStepKeys } from "@src/utils/constants";
1919
import { getDeploymentLocalData } from "@src/utils/deploymentLocalDataUtils";
2020
import { cn } from "@src/utils/styleUtils";
2121
import { UrlService } from "@src/utils/urlUtils";
@@ -63,7 +63,7 @@ export function DeploymentDetail({ dseq }: React.PropsWithChildren<{ dseq: strin
6363
if (_leases) {
6464
// Redirect to select bids if has no lease
6565
if (deployment?.state === "active" && _leases.length === 0) {
66-
router.replace(UrlService.newDeployment({ dseq, step: RouteStepKeys.createLeases }));
66+
router.replace(UrlService.newDeployment({ dseq, step: RouteStep.createLeases }));
6767
}
6868

6969
// Set the array of refs for lease rows

apps/deploy-web/src/components/deployments/DeploymentLeaseShell.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { OpenInWindow, OpenNewWindow } from "iconoir-react";
55
import Link from "next/link";
66

77
import ViewPanel from "@src/components/shared/ViewPanel";
8+
import { browserEnvConfig } from "@src/config/browser-env.config";
89
import { useCertificate } from "@src/context/CertificateProvider";
910
import { useCustomWebSocket } from "@src/hooks/useCustomWebSocket";
1011
import { XTerm } from "@src/lib/XTerm";
@@ -13,7 +14,6 @@ import { useLeaseStatus } from "@src/queries/useLeaseQuery";
1314
import { useProviderList } from "@src/queries/useProvidersQuery";
1415
import { LeaseDto } from "@src/types/deployment";
1516
import { LeaseShellCode } from "@src/types/shell";
16-
import { PROVIDER_PROXY_URL_WS } from "@src/utils/constants";
1717
import { cn } from "@src/utils/styleUtils";
1818
import { UrlService } from "@src/utils/urlUtils";
1919
import { LeaseSelect } from "./LeaseSelect";
@@ -47,7 +47,7 @@ export const DeploymentLeaseShell: React.FunctionComponent<Props> = ({ leases })
4747
});
4848
const currentUrl = useRef<string | null>(null);
4949
const terminalRef = useRef<XTermRefType>(null);
50-
const { sendJsonMessage } = useCustomWebSocket(PROVIDER_PROXY_URL_WS, {
50+
const { sendJsonMessage } = useCustomWebSocket(browserEnvConfig.NEXT_PUBLIC_PROVIDER_PROXY_URL_WS, {
5151
onOpen: () => {
5252
console.log("opened");
5353
},

apps/deploy-web/src/components/deployments/DeploymentLogs.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ import { LinearLoadingSkeleton } from "@src/components/shared/LinearLoadingSkele
1414
import { MemoMonaco } from "@src/components/shared/MemoMonaco";
1515
import { SelectCheckbox } from "@src/components/shared/SelectCheckbox";
1616
import ViewPanel from "@src/components/shared/ViewPanel";
17+
import { browserEnvConfig } from "@src/config/browser-env.config";
1718
import { useBackgroundTask } from "@src/context/BackgroundTaskProvider";
1819
import { useCertificate } from "@src/context/CertificateProvider";
1920
import { useThrottledCallback } from "@src/hooks/useThrottle";
2021
import { useLeaseStatus } from "@src/queries/useLeaseQuery";
2122
import { useProviderList } from "@src/queries/useProvidersQuery";
2223
import { LeaseDto } from "@src/types/deployment";
2324
import { AnalyticsEvents } from "@src/utils/analytics";
24-
import { PROVIDER_PROXY_URL_WS } from "@src/utils/constants";
2525
import { cn } from "@src/utils/styleUtils";
2626
import { LeaseSelect } from "./LeaseSelect";
2727

@@ -57,7 +57,7 @@ export const DeploymentLogs: React.FunctionComponent<Props> = ({ leases, selecte
5757
} = useLeaseStatus(providerInfo?.hostUri || "", selectedLease as LeaseDto, {
5858
enabled: false
5959
});
60-
const { sendJsonMessage } = useWebSocket(PROVIDER_PROXY_URL_WS, {
60+
const { sendJsonMessage } = useWebSocket(browserEnvConfig.NEXT_PUBLIC_PROVIDER_PROXY_URL_WS, {
6161
onOpen: () => {},
6262
onMessage: onLogReceived,
6363
onError: error => console.error("error", error),

0 commit comments

Comments
 (0)