Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions app/api/brands/[slug]/logos/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { NextResponse } from "next/server"
import fs from "node:fs/promises"
import { loadBrandProfile } from "@/lib/cast/server/brand-loader"
import {
BrandNotFoundError,
BrandIncompleteError,
BrandInvalidError,
} from "@/lib/cast/errors"
import { getStorageAdapter } from "@/lib/cast/server/storage-adapter"

export const runtime = "nodejs"

Expand Down Expand Up @@ -50,11 +50,12 @@ export async function GET(
)
}

// TODO(symlink-hardening): variant.path was safeJoin-validated by the loader;
// re-validate with realpath when production hardening lands.
// variant.path is a container-relative key validated by the loader,
// e.g. "brands/brisa/logos/droplet-on-dark.png".
const storage = await getStorageAdapter()
let buf: Buffer
try {
buf = await fs.readFile(variant.path)
buf = await storage.readFile("inputs", variant.path)
} catch (err) {
const code = (err as NodeJS.ErrnoException).code
if (code === "ENOENT" || code === "EACCES" || code === "EPERM") {
Expand Down
17 changes: 8 additions & 9 deletions app/api/generate/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ import {
writeBriefSnapshot,
writeReport,
} from "@/lib/cast/server/storage"
import fs from "node:fs/promises"
import { getStorageAdapter } from "@/lib/cast/server/storage-adapter"
import {
emitAssetResolved,
emitComplete,
Expand Down Expand Up @@ -276,12 +276,12 @@ export async function runPipeline(args: RunPipelineArgs): Promise<Manifest> {
let baseImageGenerationError: { stage: ErrorStage; message: string } | undefined
if (resolved.source === "local" || resolved.source === "products") {
try {
// "local" → repo-relative path via readAsset; "products" → absolute
// path resolved by brand-loader, read directly via fs.
// "local" → repo-relative path via readAsset; "products" → container-relative
// key via readBrandAsset.
localBaseImage =
resolved.source === "local"
? await readAsset(resolved.file)
: await readFile(resolved.file)
: await readBrandAsset(resolved.file)
} catch (err) {
const message = errMessage(err)
for (const ratio of brief.ratios) {
Expand Down Expand Up @@ -524,11 +524,10 @@ function errMessage(err: unknown): string {
}

/**
* Read a file from an absolute path (used for brand-can variants whose paths
* are already absolute, resolved by brand-loader). Kept separate from
* `readAsset` which takes repo-relative inputs/-rooted paths.
* Read a brand asset file via StorageAdapter using a container-relative key
* (e.g. `brands/brisa/products/can-citrus.png`).
*/
async function readFile(absPath: string): Promise<Buffer> {
return fs.readFile(absPath)
async function readBrandAsset(key: string): Promise<Buffer> {
return (await getStorageAdapter()).readFile("inputs", key)
}

6 changes: 3 additions & 3 deletions lib/cast/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,11 +239,11 @@ export type BrandProfile = {
theme?: "light" | "dark"
}[]
defaultLogoId: string
/** Absolute path to the brand's display font. `font.ttf` or `font.otf`. */
/** Container-relative key to the brand's display font, e.g. `brands/acme/font.otf`. */
fontPath: string
/**
* Can/product PNG variants from products.json.
* `file` is an absolute path (resolved by brand-loader).
* `file` is a container-relative key (e.g. `brands/acme/products/can.png`).
* Empty array when products.json is absent (e.g. Volt).
*/
canVariants: Array<{
Expand All @@ -255,7 +255,7 @@ export type BrandProfile = {
}>
/**
* Background image variants from backgrounds.json.
* `file` is an absolute path (resolved by brand-loader).
* `file` is a container-relative key (e.g. `brands/acme/backgrounds/bg.png`).
* Empty array when backgrounds.json is absent (e.g. Volt).
*/
backgroundVariants: Array<{
Expand Down
Loading