fix: build a mount-independent artifact with runtime config
This commit is contained in:
parent
ccb6993d69
commit
0222f68691
@ -3,11 +3,15 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="icon" type="image/svg+xml" href="/zino.svg" />
|
||||
<!--BASE_HREF-->
|
||||
<link rel="icon" type="image/svg+xml" href="zino.svg" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
||||
<title>P2P</title>
|
||||
<!-- Written by frontgen at placement time; absent under `npm run dev`,
|
||||
where resolveConfigValue falls back to import.meta.env. -->
|
||||
<script src="./config.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import { APP_ID } from "../config";
|
||||
import { resolveConfigValue, basePath } from "../runtimeConfig";
|
||||
|
||||
const TOKEN_KEY = "p2p_token";
|
||||
|
||||
function baseUrl(): string {
|
||||
return (import.meta.env.VITE_ZINO_API_URL || "https://studio.getzino.in").replace(/\/+$/, "");
|
||||
return resolveConfigValue("VITE_ZINO_API_URL", "https://studio.getzino.in").replace(/\/+$/, "");
|
||||
}
|
||||
|
||||
function headers(): Record<string, string> {
|
||||
@ -19,7 +20,7 @@ async function parseResponse<T>(res: Response): Promise<T> {
|
||||
if (res.status === 401) {
|
||||
localStorage.removeItem(TOKEN_KEY);
|
||||
localStorage.removeItem("p2p_user");
|
||||
window.location.href = `${import.meta.env.BASE_URL}login`;
|
||||
window.location.href = `${basePath()}login`;
|
||||
throw new Error("Unauthorized");
|
||||
}
|
||||
if (!res.ok) {
|
||||
@ -622,7 +623,7 @@ export async function downloadFieldFile(blobPath: string, originalName?: string)
|
||||
if (!res.ok) {
|
||||
if (res.status === 401) {
|
||||
localStorage.removeItem(TOKEN_KEY);
|
||||
window.location.href = `${import.meta.env.BASE_URL}login`;
|
||||
window.location.href = `${basePath()}login`;
|
||||
}
|
||||
throw new Error(`Could not load the document (${res.status})`);
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { basePath } from "@/runtimeConfig";
|
||||
import {
|
||||
FileText,
|
||||
HelpCircle,
|
||||
@ -151,7 +152,7 @@ export default function DynamicHeader({
|
||||
here would double it up. */}
|
||||
{sidebar.isMobile && (
|
||||
<img
|
||||
src={import.meta.env.BASE_URL + "zino.svg"}
|
||||
src={basePath() + "zino.svg"}
|
||||
alt="Zino"
|
||||
className="h-5 w-auto dark:brightness-0 dark:invert"
|
||||
/>
|
||||
@ -270,7 +271,7 @@ function SidebarBody({
|
||||
{/* 43×20 wordmark — fits the 56px rail as-is, so it needs no separate
|
||||
collapsed mark. Inverted in dark mode like the top-bar copy. */}
|
||||
<img
|
||||
src={import.meta.env.BASE_URL + "zino.svg"}
|
||||
src={basePath() + "zino.svg"}
|
||||
alt="Zino"
|
||||
className="h-5 w-auto shrink-0 dark:brightness-0 dark:invert"
|
||||
/>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { useState, useCallback } from "react";
|
||||
import type { User } from "../zino-sdk/types";
|
||||
import { resolveConfigValue } from "../runtimeConfig";
|
||||
|
||||
const TOKEN_KEY = "p2p_token";
|
||||
const USER_KEY = "p2p_user";
|
||||
@ -31,7 +32,7 @@ export function useAuth() {
|
||||
});
|
||||
|
||||
const apiUrl = (
|
||||
import.meta.env.VITE_ZINO_API_URL || "https://studio.getzino.in"
|
||||
resolveConfigValue("VITE_ZINO_API_URL", "https://studio.getzino.in")
|
||||
).replace(/\/+$/, "");
|
||||
|
||||
const login = useCallback(
|
||||
|
||||
@ -3,6 +3,7 @@ import { createRoot } from 'react-dom/client'
|
||||
import { BrowserRouter } from 'react-router-dom'
|
||||
import { NuqsAdapter } from 'nuqs/adapters/react-router/v6'
|
||||
import { ZinoProvider } from './zino-sdk'
|
||||
import { resolveConfigValue, basePath } from './runtimeConfig'
|
||||
import { AuthProvider } from './hooks/AuthContext'
|
||||
import { ThemeProvider } from './hooks/ThemeContext'
|
||||
import { TooltipProvider } from './components/ui/tooltip'
|
||||
@ -10,13 +11,15 @@ import { Toaster } from './components/ui/sonner'
|
||||
import './index.css'
|
||||
import App from './App'
|
||||
|
||||
const apiUrl = import.meta.env.VITE_ZINO_API_URL || 'https://studio.getzino.in'
|
||||
const apiUrl = resolveConfigValue('VITE_ZINO_API_URL', 'https://studio.getzino.in')
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<ThemeProvider>
|
||||
<ZinoProvider baseUrl={apiUrl}>
|
||||
<BrowserRouter basename={import.meta.env.BASE_URL}>
|
||||
{/* Not import.meta.env.BASE_URL — with a relative Vite base that is
|
||||
'./'. The mount path comes from the document's <base href>. */}
|
||||
<BrowserRouter basename={basePath()}>
|
||||
{/* NuqsAdapter must sit inside the router — it reads and writes the
|
||||
router's search params, so table state can live in the URL. */}
|
||||
<NuqsAdapter>
|
||||
|
||||
@ -6,6 +6,7 @@ import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { useAuthContext } from "@/hooks/AuthContext";
|
||||
import { ORG_ID } from "@/config";
|
||||
import { basePath } from "@/runtimeConfig";
|
||||
|
||||
const HIGHLIGHTS = [
|
||||
{
|
||||
@ -76,7 +77,7 @@ export default function Login() {
|
||||
</div>
|
||||
|
||||
<img
|
||||
src={import.meta.env.BASE_URL + "zino.svg"}
|
||||
src={basePath() + "zino.svg"}
|
||||
alt="Zino"
|
||||
className="absolute left-16 top-10 z-10 h-5 w-auto brightness-0 invert"
|
||||
/>
|
||||
@ -118,7 +119,7 @@ export default function Login() {
|
||||
<div className="flex flex-1 items-center justify-center px-6">
|
||||
<div className="w-full max-w-[340px]">
|
||||
<img
|
||||
src={import.meta.env.BASE_URL + "zino.svg"}
|
||||
src={basePath() + "zino.svg"}
|
||||
alt="Zino"
|
||||
className="mb-10 h-5 w-auto dark:invert lg:hidden"
|
||||
/>
|
||||
@ -201,7 +202,7 @@ export default function Login() {
|
||||
<div className="mt-10 flex items-center justify-center gap-1.5">
|
||||
<span className="text-xs text-muted-foreground">Powered by</span>
|
||||
<img
|
||||
src={import.meta.env.BASE_URL + "zino.svg"}
|
||||
src={basePath() + "zino.svg"}
|
||||
alt="Zino"
|
||||
className="h-3.5 w-auto dark:invert"
|
||||
/>
|
||||
|
||||
36
src/runtimeConfig.ts
Normal file
36
src/runtimeConfig.ts
Normal file
@ -0,0 +1,36 @@
|
||||
// Runtime configuration, per frontgen's placement contract.
|
||||
//
|
||||
// A deployed artifact carries no environment values. When frontgen places a
|
||||
// build it writes config.js next to index.html and rewrites the
|
||||
// <!--BASE_HREF--> placeholder into a real <base href>. That is what lets one
|
||||
// artifact serve any mount path — /p2p/ here, something else after promotion —
|
||||
// without being rebuilt. See sm2/frontgen internal/deploy/place.go and the
|
||||
// checks in internal/builder/assert.go.
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
__RUNTIME_CONFIG__?: Record<string, string>;
|
||||
}
|
||||
}
|
||||
|
||||
// SAME_ORIGIN is what placement writes for a custom-domain mount, where the
|
||||
// platform is proxied under the serving host. An empty string could not express
|
||||
// it — it is falsy, so it would fall through the || chain below.
|
||||
export function resolveConfigValue(key: string, fallback = ""): string {
|
||||
const runtime = (window.__RUNTIME_CONFIG__ || {})[key];
|
||||
const devFallback = import.meta.env.DEV ? (import.meta.env[key] as string) : "";
|
||||
const value = runtime || devFallback || fallback;
|
||||
return value === "SAME_ORIGIN" ? window.location.origin.replace(/\/$/, "") : value;
|
||||
}
|
||||
|
||||
// The mount path this app is served under, read from the <base href> placement
|
||||
// writes. Drives the router basename and every public-asset URL.
|
||||
//
|
||||
// The <base> guard is for `npm run dev`, where the placeholder is still an HTML
|
||||
// comment: without it document.baseURI is just the current page URL, so a deep
|
||||
// link like /invoices/12 would yield "/invoices/12" as the basename and the
|
||||
// router would match nothing.
|
||||
export function basePath(): string {
|
||||
if (!document.querySelector("base")) return "/";
|
||||
return new URL(document.baseURI).pathname;
|
||||
}
|
||||
@ -11,7 +11,10 @@ export default defineConfig({
|
||||
resolve: {
|
||||
alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) },
|
||||
},
|
||||
base: process.env.VITE_BASE_URL ?? '/p2p/',
|
||||
// Relative, never a mount path: frontgen's placement step writes the real
|
||||
// <base href> into index.html, so one artifact serves any mount without a
|
||||
// rebuild. An absolute base fails internal/builder/assert.go.
|
||||
base: './',
|
||||
server: {
|
||||
port: 3007,
|
||||
proxy: {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user