Reply time setting, and server settings from process.env only

Inquiries and the sent page promise a reply within the site setting
replyTime (2 days, Greg 2026-10-11). The site reads its settings through one
helper that uses process.env only, so a .env file can never be copied into the
built bundle through import.meta.env.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aSbo2D1qP8cMFQCykf7kx
This commit is contained in:
Claude
2026-10-11 23:18:07 +00:00
parent 0d0b8e4085
commit 60dfeecd7f
6 changed files with 21 additions and 4 deletions
+1
View File
@@ -475,6 +475,7 @@ export const siteSettings = def({
lockup: line({ title: "Lockup line" }), lockup: line({ title: "Lockup line" }),
location: line({ title: "Location" }).optional(), location: line({ title: "Location" }).optional(),
email: line({ title: "Email", description: "Printed on the resume PDF." }).optional(), email: line({ title: "Email", description: "Printed on the resume PDF." }).optional(),
replyTime: line({ title: "Reply time", description: "How soon you answer inquiries, like 2 days." }).optional(),
availability: line({ title: "Availability", description: "Shown on the resume and Inquiries, like Open to freelance web work." }).optional(), availability: line({ title: "Availability", description: "Shown on the resume and Inquiries, like Open to freelance web work." }).optional(),
socials: z socials: z
.array(z.object({ label: line({ title: "Label" }), handle: line({ title: "Handle" }), url: z.url().meta({ title: "Link" }) })) .array(z.object({ label: line({ title: "Label" }), handle: line({ title: "Handle" }), url: z.url().meta({ title: "Link" }) }))
+1 -1
View File
@@ -6,6 +6,7 @@
* background, so a saved note shows within a minute and a slow or restarting * background, so a saved note shows within a minute and a slow or restarting
* content service never takes a page down: the last good copy keeps serving. * content service never takes a page down: the last good copy keeps serving.
*/ */
import { env } from "./env.ts";
import type { NoteData, NoteType } from "@gk/schema"; import type { NoteData, NoteType } from "@gk/schema";
import { SiteIndex } from "./site.ts"; import { SiteIndex } from "./site.ts";
@@ -27,7 +28,6 @@ export interface Redirect {
toPath: string; toPath: string;
} }
const env = (k: string, fallback?: string) => process.env[k] ?? import.meta.env[k] ?? fallback;
const baseUrl = () => (env("CONTENT_URL", "http://localhost:4310") as string).replace(/\/$/, ""); const baseUrl = () => (env("CONTENT_URL", "http://localhost:4310") as string).replace(/\/$/, "");
const token = () => env("CONTENT_TOKEN", "") as string; const token = () => env("CONTENT_TOKEN", "") as string;
/** Preview only: include unpublished notes. Needs an editor or admin token; never set in production. */ /** Preview only: include unpublished notes. Needs an editor or admin token; never set in production. */
+12
View File
@@ -0,0 +1,12 @@
/**
* Server settings, read from process.env at run time only. Never read secrets
* through import.meta.env: Vite replaces it at build time and can copy every
* value, secrets included, into the built bundle. Set variables in the
* environment (Docker env_file, or the shell when running locally).
*/
export function env(key: string): string | undefined;
export function env(key: string, fallback: string): string;
export function env(key: string, fallback?: string): string | undefined {
const v = process.env[key];
return v === undefined || v === "" ? fallback : v;
}
@@ -7,6 +7,7 @@ import Seal from "@gk/ui/components/Seal.astro";
import Star from "@gk/ui/components/Star.astro"; import Star from "@gk/ui/components/Star.astro";
import StatusPill from "@gk/ui/components/StatusPill.astro"; import StatusPill from "@gk/ui/components/StatusPill.astro";
import ChoicePills from "../../components/work/ChoicePills.astro"; import ChoicePills from "../../components/work/ChoicePills.astro";
import { env } from "../../lib/env.ts";
import { site } from "../../lib/page.ts"; import { site } from "../../lib/page.ts";
import { inquiryTypes, parseInquiryType, type InquiryType } from "../../lib/work.ts"; import { inquiryTypes, parseInquiryType, type InquiryType } from "../../lib/work.ts";
@@ -15,7 +16,7 @@ const settings = s.settings();
const text = s.pageText("/inquiries"); const text = s.pageText("/inquiries");
const selected = parseInquiryType(Astro.url.searchParams.get("type")); const selected = parseInquiryType(Astro.url.searchParams.get("type"));
const lede = text?.data.lede ?? "Roles, freelance web work or a custom Patiun order. Pick one and the form asks the right questions."; const lede = text?.data.lede ?? "Roles, freelance web work or a custom Patiun order. Pick one and the form asks the right questions.";
const quickFacts = text?.data.quickFacts?.map((f) => f.text) ?? ["Replies within [PLACEHOLDER] days", "Read by me, not a bot"]; const quickFacts = text?.data.quickFacts?.map((f) => f.text) ?? [`Replies within ${s.settings()?.replyTime ?? "a few days"}`, "Read by me, not a bot"];
const socials = settings?.socials ?? []; const socials = settings?.socials ?? [];
const placeholders: Record<InquiryType | "none", string> = { const placeholders: Record<InquiryType | "none", string> = {
@@ -29,7 +30,6 @@ const timing = [
{ value: "within-a-month", label: "Within a month" }, { value: "within-a-month", label: "Within a month" },
{ value: "date", label: "A specific date" }, { value: "date", label: "A specific date" },
]; ];
const env = (k: string) => (process.env[k] ?? import.meta.env[k]) as string | undefined;
const turnstileKey = env("PUBLIC_TURNSTILE_SITE_KEY") ?? env("TURNSTILE_SITE_KEY"); const turnstileKey = env("PUBLIC_TURNSTILE_SITE_KEY") ?? env("TURNSTILE_SITE_KEY");
--- ---
<PageShell title="Inquiries" family="inquiries" current="inquiries" description="Ask about a role, freelance web work or a custom Patiun order."> <PageShell title="Inquiries" family="inquiries" current="inquiries" description="Ask about a role, freelance web work or a custom Patiun order.">
+3 -1
View File
@@ -12,7 +12,9 @@ import Star from "@gk/ui/components/Star.astro";
import TickRing from "@gk/ui/components/TickRing.astro"; import TickRing from "@gk/ui/components/TickRing.astro";
import TickScale from "@gk/ui/components/TickScale.astro"; import TickScale from "@gk/ui/components/TickScale.astro";
import { inquiryTypes, parseInquiryType, parseSentSummary } from "../../lib/work.ts"; import { inquiryTypes, parseInquiryType, parseSentSummary } from "../../lib/work.ts";
import { site } from "../../lib/page.ts";
const replyTime = (await site().catch(() => null))?.settings()?.replyTime ?? "a few days";
const sent = parseSentSummary(Astro.cookies.get("gk-inquiry")?.value); const sent = parseSentSummary(Astro.cookies.get("gk-inquiry")?.value);
if (sent) Astro.cookies.delete("gk-inquiry", { path: "/" }); if (sent) Astro.cookies.delete("gk-inquiry", { path: "/" });
Astro.response.headers.set("Cache-Control", "private, no-store"); Astro.response.headers.set("Cache-Control", "private, no-store");
@@ -38,7 +40,7 @@ const steps = [
<Seal name="transmutation" size={132} class="seal" /> <Seal name="transmutation" size={132} class="seal" />
<span class="eyebrow">Inquiry sent</span> <span class="eyebrow">Inquiry sent</span>
<h1>Your note is on its way</h1> <h1>Your note is on its way</h1>
<p class="lede">Thanks{firstName ? `, ${firstName}` : ""}. I read every inquiry myself and reply within [PLACEHOLDER] days.</p> <p class="lede">Thanks{firstName ? `, ${firstName}` : ""}. I read every inquiry myself and reply within {replyTime}.</p>
<ol class="steps" aria-label="What happens next"> <ol class="steps" aria-label="What happens next">
{ {
steps.map((st, i) => ( steps.map((st, i) => (
+2
View File
@@ -20,6 +20,8 @@ export const site = {
email: "greg@gregkilmer.com", email: "greg@gregkilmer.com",
/** Resume Interview: open to offers but not looking, so the page says this. */ /** Resume Interview: open to offers but not looking, so the page says this. */
availability: "Open to freelance web work", availability: "Open to freelance web work",
/** Greg, 2026-10-11. */
replyTime: "2 days",
socials: [ socials: [
{ label: "GitHub", handle: "Patiun", url: "https://github.com/patiun" }, { label: "GitHub", handle: "Patiun", url: "https://github.com/patiun" },
{ label: "LinkedIn", handle: "Gregory Kilmer", url: "https://www.linkedin.com/in/gregory-kilmer-36646bb5/" }, { label: "LinkedIn", handle: "Gregory Kilmer", url: "https://www.linkedin.com/in/gregory-kilmer-36646bb5/" },