Use env vars

This commit is contained in:
schroda
2025-08-02 23:13:50 +02:00
parent 4f5754399b
commit 5543e134fa
16 changed files with 63 additions and 46 deletions

View File

@@ -11,4 +11,4 @@ dist/
build/
out/
tools/scripts/tokens.json
.env

9
.env.template Normal file
View File

@@ -0,0 +1,9 @@
PORT = 3000
ALLOWED_HOSTS =
VITE_SERVER_URL_DEFAULT = http://localhost:4567
CODEGEN_SERVER_URL_GQL = http://localhost:4567/api/graphql
GITHUB_TOKEN =
WEBLATE_TOKEN =

View File

@@ -41,5 +41,5 @@ jobs:
- name: Update i18n resources
run: |
cd master
echo '{"weblateToken": "${{ secrets.WEBLATE_TOKEN }}"}' > tools/scripts/tokens.json
echo "WEBLATE_TOKEN=${{ secrets.MY_SECRET }}" > .env
yarn i18n:gen-resources

2
.gitignore vendored
View File

@@ -6,6 +6,4 @@ node_modules/
build/*
tools/scripts/tokens.json
src/lib/graphql/schema.json

View File

@@ -7,10 +7,12 @@
*/
import type { CodegenConfig } from '@graphql-codegen/cli';
// eslint-disable-next-line import/no-extraneous-dependencies
import 'dotenv/config';
const config: CodegenConfig = {
overwrite: true,
schema: 'http://localhost:4567/api/graphql',
schema: process.env.CODEGEN_SERVER_URL_GQL,
documents: [
'src/lib/graphql/queries/**',
'src/lib/graphql/mutations/**',

View File

@@ -98,6 +98,7 @@
"@typescript-eslint/parser": "7.16.1",
"@vitejs/plugin-legacy": "7.0.0",
"@vitejs/plugin-react-swc": "3.10.2",
"dotenv": "17.2.1",
"eslint": "8.57.0",
"eslint-config-airbnb": "19.0.4",
"eslint-config-airbnb-typescript": "18.0.0",

View File

@@ -14,11 +14,7 @@ export abstract class BaseClient<Client, ClientConfig, Fetcher> {
public abstract readonly fetcher: Fetcher;
public getBaseUrl(): string {
const { hostname, port, protocol } = window.location;
// if port is 3000 it's probably running from webpack development server
const inferredPort = import.meta.env.DEV && port === '3000' ? '4567' : port;
return AppStorage.local.getItemParsed('serverBaseURL', `${protocol}//${hostname}:${inferredPort}`);
return AppStorage.local.getItemParsed('serverBaseURL', import.meta.env.VITE_SERVER_URL_DEFAULT);
}
public abstract updateConfig(config: Partial<ClientConfig>): void;

View File

@@ -99,7 +99,10 @@ export const ServerSettings = () => {
});
const [mutateSettings] = requestManager.useUpdateServerSettings();
const [serverAddress, setServerAddress] = useLocalStorage<string>('serverBaseURL', window.location.origin);
const [serverAddress, setServerAddress] = useLocalStorage<string>(
'serverBaseURL',
import.meta.env.VITE_SERVER_URL_DEFAULT,
);
const handleServerAddressChange = (address: string) => {
const serverBaseUrl = address.replaceAll(/(\/)+$/g, '');

View File

@@ -32,7 +32,7 @@ import { TTrackerSearch } from '@/modules/tracker/Tracker.types.ts';
export const SettingsTrackerCard = ({ tracker }: { tracker: TTrackerSearch }) => {
const { t } = useTranslation();
const [serverAddress] = useLocalStorage('serverBaseURL', window.location.origin);
const [serverAddress] = useLocalStorage('serverBaseURL', import.meta.env.VITE_SERVER_URL_DEFAULT);
const [loginTrackerCredentials, { loading: isCredentialLoginInProgress }] =
requestManager.useLoginToTrackerCredentials();

15
src/vite-env.d.ts vendored
View File

@@ -7,3 +7,18 @@
*/
/// <reference types="vite/client" />
interface ViteTypeOptions {
// By adding this line, you can make the type of ImportMetaEnv strict
// to disallow unknown keys.
strictImportMetaEnv: unknown;
}
interface ImportMetaEnv {
readonly VITE_SERVER_URL_DEFAULT: string;
// more env variables...
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}

View File

@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import tokens from '../tokens.json';
import 'dotenv/config';
type GithubAuthor = {
name: string;
@@ -115,7 +115,7 @@ const fetchCommits = async (
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${tokens.githubToken}`,
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
},
body: JSON.stringify({ query, variables }),
})

View File

@@ -10,7 +10,6 @@ import path from 'path';
import fs from 'fs';
import readline from 'readline';
import { ControlledPromise } from '@/lib/ControlledPromise.ts';
import tokens from '../tokens.json';
import {
ActionByTranslationUrlByUserUrl,
ContributionsByLanguage,
@@ -74,25 +73,23 @@ const extractContributionInfoFromChanges = (
const getUsernameByUserUrlMap = async (
userUrlsByTranslationUrl: UserUrlsByTranslationUrl,
authToken: string,
): Promise<Record<string, string>> =>
Object.fromEntries(
(await Promise.all(
Object.values(userUrlsByTranslationUrl)
.flat()
.map(async (userUrl) => [userUrl, await getUsername(userUrl, authToken)]),
.map(async (userUrl) => [userUrl, await getUsername(userUrl)]),
)) satisfies [string, string][],
);
const getLanguageNameByTranslationUrl = async (
userUrlsByTranslationUrl: UserUrlsByTranslationUrl,
authToken: string,
): Promise<LanguageNameByTranslationUrl> =>
Object.fromEntries(
(await Promise.all(
Object.keys(userUrlsByTranslationUrl)
.flat()
.map(async (translationUrl) => [translationUrl, await getLanguageName(translationUrl, authToken)]),
.map(async (translationUrl) => [translationUrl, await getLanguageName(translationUrl)]),
)) satisfies [string, string][],
);
@@ -129,8 +126,8 @@ const doesLanguageOfChangeMeetTranslatePercentThreshold = (
return meetsTranslatedPercentThreshold(langaugeStats.translated_percent);
};
const getContributorsForRange = async (url: string, authToken: string): Promise<ContributionsByLanguage> => {
const weblateChanges = await fetchWeblateChanges(url, authToken);
const getContributorsForRange = async (url: string): Promise<ContributionsByLanguage> => {
const weblateChanges = await fetchWeblateChanges(url);
const weblateLanguageStats = await fetchWeblateLanguageStats();
const validWeblateChanges = weblateChanges.filter((change) =>
@@ -147,8 +144,8 @@ const getContributorsForRange = async (url: string, authToken: string): Promise<
const { userUrlsByTranslationUrl, actionsByTranslationUrlByUserUrl } =
extractContributionInfoFromChanges(validWeblateChanges);
const usernameByUserUrl = await getUsernameByUserUrlMap(userUrlsByTranslationUrl, authToken);
const languageNameByTranslationUrl = await getLanguageNameByTranslationUrl(userUrlsByTranslationUrl, authToken);
const usernameByUserUrl = await getUsernameByUserUrlMap(userUrlsByTranslationUrl);
const languageNameByTranslationUrl = await getLanguageNameByTranslationUrl(userUrlsByTranslationUrl);
return getUserContributionByLanguage(
userUrlsByTranslationUrl,
@@ -238,7 +235,7 @@ export const createTranslationChangelog = async (
const url = `https://hosted.weblate.org/api/components/suwayomi/suwayomi-webui/changes/?timestamp_after=${timestampAfter}&timestamp_before=${timestampBefore}&${creditRelevantActions.map((action) => `action=${action}`).join('&')}`;
const contributorsForRange = await getContributorsForRange(url, tokens.weblateToken);
const contributorsForRange = await getContributorsForRange(url);
const knownContributorsByLanguage = await getKnownContributorsByLanguage();
const contributionInfo: { language: string; contributors: string[] }[] = Object.entries(contributorsForRange).map(

View File

@@ -1,4 +0,0 @@
{
"githubToken": "",
"weblateToken": ""
}

View File

@@ -13,14 +13,14 @@ import {
WeblateLanguageStatistic,
WeblateUserPayload,
} from '@/../tools/scripts/weblate/Weblate.types.ts';
import tokens from '../tokens.json';
import 'dotenv/config';
import { TRANSLATED_PERCENT_THRESHOLD } from '@/../tools/scripts/weblate/Weblate.constants.ts';
export const fetchData = async <T = any>(url: string, authToken: string): Promise<T> => {
export const fetchData = async <T = any>(url: string): Promise<T> => {
const response = await fetch(url, {
method: 'GET',
headers: {
Authorization: `Token ${authToken}`,
...(process.env.WEBLATE_TOKEN ? { Authorization: `Token ${process.env.WEBLATE_TOKEN}` } : {}),
Accept: 'application/json',
},
});
@@ -34,36 +34,29 @@ export const fetchData = async <T = any>(url: string, authToken: string): Promis
export const fetchWeblateChanges = async (
url: string,
authToken: string,
weblateChangeResults: WeblateChangeResult[] = [],
): Promise<WeblateChangeResult[]> => {
const weblateChangePage = await fetchData<WeblateChangePayload>(url, authToken);
const weblateChangePage = await fetchData<WeblateChangePayload>(url);
if (!weblateChangePage.next) {
return [...weblateChangePage.results, ...weblateChangeResults];
}
return fetchWeblateChanges(weblateChangePage.next, authToken, [
...weblateChangePage.results,
...weblateChangeResults,
]);
return fetchWeblateChanges(weblateChangePage.next, [...weblateChangePage.results, ...weblateChangeResults]);
};
export const getUsername = async (url: string, authToken: string): Promise<string> => {
const userPayload = await fetchData<WeblateUserPayload>(url, authToken);
export const getUsername = async (url: string): Promise<string> => {
const userPayload = await fetchData<WeblateUserPayload>(url);
return userPayload.full_name;
};
export const getLanguageName = async (url: string, authToken: string): Promise<string> => {
const languagePayload = await fetchData<WeblateLanguagePayload>(url, authToken);
export const getLanguageName = async (url: string): Promise<string> => {
const languagePayload = await fetchData<WeblateLanguagePayload>(url);
return languagePayload.language.name.replace(/\(([a-zA-Z]+) Han script\)/g, '($1)');
};
export const fetchWeblateLanguageStats = async () =>
fetchData(
'https://hosted.weblate.org/api/components/suwayomi/suwayomi-webui/statistics/?format=json-flat',
tokens.weblateToken,
);
fetchData('https://hosted.weblate.org/api/components/suwayomi/suwayomi-webui/statistics/?format=json-flat');
const dateRegex = /[0-9]{4}-[0-9]{2}-[0-9]{2}/g;
const isValidIS08601Date = (date: string): boolean => !!date.match(dateRegex);

View File

@@ -14,6 +14,7 @@ import react from '@vitejs/plugin-react-swc';
import viteTsconfigPaths from 'vite-tsconfig-paths';
import legacy from '@vitejs/plugin-legacy';
import { nodePolyfills } from 'vite-plugin-node-polyfills';
import 'dotenv/config';
// eslint-disable-next-line import/no-default-export
export default defineConfig(() => ({
@@ -21,7 +22,8 @@ export default defineConfig(() => ({
outDir: 'build',
},
server: {
port: 3000,
port: Number(process.env.PORT),
allowedHosts: process.env.ALLOWED_HOSTS.split(','),
},
resolve: {
alias: {

View File

@@ -4511,6 +4511,11 @@ dot-case@^3.0.4:
no-case "^3.0.4"
tslib "^2.0.3"
dotenv@17.2.1:
version "17.2.1"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-17.2.1.tgz#6f32e10faf014883515538dc922a0fb8765d9b32"
integrity sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ==
dotenv@^16.0.0:
version "16.4.5"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f"