From 1355df79cd50a8270af211ecbed2be8be43f279b Mon Sep 17 00:00:00 2001 From: guitavano Date: Thu, 18 Jun 2026 16:07:22 -0300 Subject: [PATCH 1/3] feat(cms): export Props for VTEX, blog, and website cms-props Add CMS Props interfaces to published app mods so Tanstack sites can use thin re-export bridges (same pattern as lojabagaggio apps/deco/*.ts). Co-authored-by: Cursor --- blog/mod.ts | 21 +++++++-- package.json | 2 + vtex/mod.ts | 9 ++++ vtex/props.ts | 62 ++++++++++++++++++++++++++ website/cms-props.ts | 103 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 193 insertions(+), 4 deletions(-) create mode 100644 vtex/props.ts create mode 100644 website/cms-props.ts diff --git a/blog/mod.ts b/blog/mod.ts index f62f1b3..7b446f8 100644 --- a/blog/mod.ts +++ b/blog/mod.ts @@ -9,11 +9,19 @@ import type { AppDefinition, ResolveSecretFn } from "../commerce/app-types"; import manifest from "./manifest.gen"; // ------------------------------------------------------------------------- -// State +// CMS Props // ------------------------------------------------------------------------- -// biome-ignore lint/complexity/noBannedTypes: empty state placeholder for future use -export type BlogState = {}; +/** @title Deco Blog */ +export interface Props { + /** + * @title Page Slug + * @description The slug of the BlogPostPage to embed. Use :category and :slug. + */ + pageSlug?: string; +} + +export type BlogState = Props; // ------------------------------------------------------------------------- // Configure @@ -31,9 +39,14 @@ export async function configure( return { name: "blog", manifest, - state: {}, + state: { pageSlug: _block?.pageSlug }, }; } /** Placeholder preview for CMS editor. */ export const preview = undefined; + +/** Default export for schema generation and Deno-style app bridges. */ +export default function Blog(state: Props) { + return { state }; +} diff --git a/package.json b/package.json index d5f74a5..ec9a4ad 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "./vtex": "./vtex/index.ts", "./vtex/commerceLoaders": "./vtex/commerceLoaders.ts", "./vtex/mod": "./vtex/mod.ts", + "./vtex/props": "./vtex/props.ts", "./vtex/client": "./vtex/client.ts", "./vtex/types": "./vtex/types.ts", "./vtex/actions": "./vtex/actions/index.ts", @@ -69,6 +70,7 @@ "./resend/actions/send": "./resend/actions/send.ts", "./website": "./website/index.ts", "./website/mod": "./website/mod.ts", + "./website/cms-props": "./website/cms-props.ts", "./website/client": "./website/client.ts", "./website/types": "./website/types.ts", "./website/components/*": "./website/components/*.tsx", diff --git a/vtex/mod.ts b/vtex/mod.ts index b897f38..ac64d9c 100644 --- a/vtex/mod.ts +++ b/vtex/mod.ts @@ -19,6 +19,10 @@ import type { AppDefinition, AppMiddleware, ResolveSecretFn } from "../commerce/ import { configureVtex, type VtexConfig } from "./client"; import manifest from "./manifest.gen"; import { extractVtexContext, propagateISCookies, vtexCacheControl } from "./middleware"; +import type { Props } from "./props"; + +export type { Props } from "./props"; +export type { Secret } from "../website/cms-props"; // ------------------------------------------------------------------------- // State @@ -81,3 +85,8 @@ export async function configure( /** Placeholder preview for CMS editor — evolves when admin supports it. */ export const preview = undefined; + +/** Default export for schema generation and Deno-style app bridges. */ +export default function VTEX(_props: Props) { + return { state: _props }; +} diff --git a/vtex/props.ts b/vtex/props.ts new file mode 100644 index 0000000..7f7af84 --- /dev/null +++ b/vtex/props.ts @@ -0,0 +1,62 @@ +/** + * VTEX app CMS Props — JSON Schema source for admin and site app bridges. + * Runtime configure() lives in mod.ts; Props mirror deco-cx/apps/vtex/mod.ts. + */ + +import type { Secret } from "../website/cms-props"; + +/** @title VTEX */ +export interface Props { + /** + * @description VTEX Account name + */ + account: string; + + /** + * @title Public store URL + * @description Domain registered on License Manager (e.g. secure.mystore.com.br) + */ + publicUrl: string; + + /** @title App Key */ + appKey?: Secret; + + /** + * @title App Token + * @format password + */ + appToken?: Secret; + + /** + * @title Default Sales Channel + * @deprecated + */ + salesChannel?: string; + + /** + * @title Set Refresh Token + * @default false + */ + setRefreshToken?: boolean; + + defaultSegment?: Record; + + usePortalSitemap?: boolean; + + /** + * @hide true + * @default vtex + */ + platform?: "vtex"; + + advancedConfigs?: { + doNotFetchVariantsForRelatedProducts?: boolean; + removeUTMFromCacheKey?: boolean; + }; + + /** @title Cached Search Terms */ + cachedSearchTerms?: { + terms?: unknown; + extraTerms?: string[]; + }; +} diff --git a/website/cms-props.ts b/website/cms-props.ts new file mode 100644 index 0000000..2f9e3c5 --- /dev/null +++ b/website/cms-props.ts @@ -0,0 +1,103 @@ +/** + * Website app Props for admin JSON Schema generation. + * Tanstack sites extend these in src/apps/site.ts (same as deco-cx/apps/website/mod.ts). + */ + +import type { Props as SecretProps } from "./loaders/secret"; + +export type Script = { src: string }; + +export interface CacheDirectiveBase { + name: string; + value: number; +} + +export interface StaleWhileRevalidate extends CacheDirectiveBase { + name: "stale-while-revalidate"; +} + +export interface MaxAge extends CacheDirectiveBase { + name: "max-age"; +} + +export type CacheDirective = StaleWhileRevalidate | MaxAge; + +export interface Caching { + enabled?: boolean; + directives?: CacheDirective[]; +} + +export interface AbTesting { + enabled?: boolean; + /** @description The name of the A/B test — appears in cookies */ + name?: string; + matcher?: unknown; + /** @description URL to run the A/B test against */ + urlToRunAgainst?: string; + replaces?: unknown[]; + includeScriptsToHead?: { includes?: Script[] }; + includeScriptsToBody?: { includes?: Script[] }; +} + +/** @titleBy framework */ +export interface FreshFlavor { + /** @default fresh */ + framework: "fresh"; +} + +/** @titleBy framework */ +export interface HtmxFlavor { + /** @default htmx */ + framework: "htmx"; +} + +export interface WebsiteProps { + /** @title Routes Map */ + routes?: unknown[]; + + /** @title Global Sections */ + global?: unknown[]; + + /** @title Error Page */ + errorPage?: unknown; + + /** @title Caching configuration of pages */ + caching?: Caching; + + /** + * @title Global Async Rendering (Deprecated) + * @deprecated true + * @default false + */ + firstByteThresholdMS?: boolean; + + /** @title Avoid redirecting to editor */ + avoidRedirectingToEditor?: boolean; + + /** @title AB Testing */ + abTesting?: AbTesting; + + /** @title Flavor */ + flavor?: FreshFlavor | HtmxFlavor; + + /** @title Seo */ + seo?: Record; + + /** @title Theme */ + theme?: unknown; + + /** @hide true */ + sendToClickHouse?: boolean; + + /** @title Default Image Quality */ + defaultImageQuality?: string; + + /** @title Disable image/asset proxy for this site */ + disableProxy?: boolean; + + /** @title Whilelist URL Patterns */ + whilelistURLs?: string[]; +} + +/** Secret block shape in decofile (website/loaders/secret.ts). */ +export type Secret = SecretProps; From ca1be17f4ee6cc163910e3a603df7a55ae8ecc15 Mon Sep 17 00:00:00 2001 From: guitavano Date: Thu, 18 Jun 2026 16:10:20 -0300 Subject: [PATCH 2/3] refactor(cms): consolidate Props into mod.ts for all apps Move VTEX Props and website WebsiteProps into each app's mod.ts (deco-cx pattern). Remove vtex/props.ts and website/cms-props.ts split files. Co-authored-by: Cursor --- package.json | 2 - vtex/mod.ts | 65 ++++++++++++++++++++++++-- vtex/props.ts | 62 ------------------------- website/cms-props.ts | 103 ----------------------------------------- website/mod.ts | 106 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 168 insertions(+), 170 deletions(-) delete mode 100644 vtex/props.ts delete mode 100644 website/cms-props.ts diff --git a/package.json b/package.json index ec9a4ad..d5f74a5 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,6 @@ "./vtex": "./vtex/index.ts", "./vtex/commerceLoaders": "./vtex/commerceLoaders.ts", "./vtex/mod": "./vtex/mod.ts", - "./vtex/props": "./vtex/props.ts", "./vtex/client": "./vtex/client.ts", "./vtex/types": "./vtex/types.ts", "./vtex/actions": "./vtex/actions/index.ts", @@ -70,7 +69,6 @@ "./resend/actions/send": "./resend/actions/send.ts", "./website": "./website/index.ts", "./website/mod": "./website/mod.ts", - "./website/cms-props": "./website/cms-props.ts", "./website/client": "./website/client.ts", "./website/types": "./website/types.ts", "./website/components/*": "./website/components/*.tsx", diff --git a/vtex/mod.ts b/vtex/mod.ts index ac64d9c..ac5c953 100644 --- a/vtex/mod.ts +++ b/vtex/mod.ts @@ -16,13 +16,72 @@ */ import type { AppDefinition, AppMiddleware, ResolveSecretFn } from "../commerce/app-types"; +import type { Secret } from "../website/mod"; import { configureVtex, type VtexConfig } from "./client"; import manifest from "./manifest.gen"; import { extractVtexContext, propagateISCookies, vtexCacheControl } from "./middleware"; -import type { Props } from "./props"; -export type { Props } from "./props"; -export type { Secret } from "../website/cms-props"; +// ------------------------------------------------------------------------- +// CMS Props (mirrors deco-cx/apps/vtex/mod.ts) +// ------------------------------------------------------------------------- + +/** @title VTEX */ +export interface Props { + /** + * @description VTEX Account name + */ + account: string; + + /** + * @title Public store URL + * @description Domain registered on License Manager (e.g. secure.mystore.com.br) + */ + publicUrl: string; + + /** @title App Key */ + appKey?: Secret; + + /** + * @title App Token + * @format password + */ + appToken?: Secret; + + /** + * @title Default Sales Channel + * @deprecated + */ + salesChannel?: string; + + /** + * @title Set Refresh Token + * @default false + */ + setRefreshToken?: boolean; + + defaultSegment?: Record; + + usePortalSitemap?: boolean; + + /** + * @hide true + * @default vtex + */ + platform?: "vtex"; + + advancedConfigs?: { + doNotFetchVariantsForRelatedProducts?: boolean; + removeUTMFromCacheKey?: boolean; + }; + + /** @title Cached Search Terms */ + cachedSearchTerms?: { + terms?: unknown; + extraTerms?: string[]; + }; +} + +export type { Secret }; // ------------------------------------------------------------------------- // State diff --git a/vtex/props.ts b/vtex/props.ts deleted file mode 100644 index 7f7af84..0000000 --- a/vtex/props.ts +++ /dev/null @@ -1,62 +0,0 @@ -/** - * VTEX app CMS Props — JSON Schema source for admin and site app bridges. - * Runtime configure() lives in mod.ts; Props mirror deco-cx/apps/vtex/mod.ts. - */ - -import type { Secret } from "../website/cms-props"; - -/** @title VTEX */ -export interface Props { - /** - * @description VTEX Account name - */ - account: string; - - /** - * @title Public store URL - * @description Domain registered on License Manager (e.g. secure.mystore.com.br) - */ - publicUrl: string; - - /** @title App Key */ - appKey?: Secret; - - /** - * @title App Token - * @format password - */ - appToken?: Secret; - - /** - * @title Default Sales Channel - * @deprecated - */ - salesChannel?: string; - - /** - * @title Set Refresh Token - * @default false - */ - setRefreshToken?: boolean; - - defaultSegment?: Record; - - usePortalSitemap?: boolean; - - /** - * @hide true - * @default vtex - */ - platform?: "vtex"; - - advancedConfigs?: { - doNotFetchVariantsForRelatedProducts?: boolean; - removeUTMFromCacheKey?: boolean; - }; - - /** @title Cached Search Terms */ - cachedSearchTerms?: { - terms?: unknown; - extraTerms?: string[]; - }; -} diff --git a/website/cms-props.ts b/website/cms-props.ts deleted file mode 100644 index 2f9e3c5..0000000 --- a/website/cms-props.ts +++ /dev/null @@ -1,103 +0,0 @@ -/** - * Website app Props for admin JSON Schema generation. - * Tanstack sites extend these in src/apps/site.ts (same as deco-cx/apps/website/mod.ts). - */ - -import type { Props as SecretProps } from "./loaders/secret"; - -export type Script = { src: string }; - -export interface CacheDirectiveBase { - name: string; - value: number; -} - -export interface StaleWhileRevalidate extends CacheDirectiveBase { - name: "stale-while-revalidate"; -} - -export interface MaxAge extends CacheDirectiveBase { - name: "max-age"; -} - -export type CacheDirective = StaleWhileRevalidate | MaxAge; - -export interface Caching { - enabled?: boolean; - directives?: CacheDirective[]; -} - -export interface AbTesting { - enabled?: boolean; - /** @description The name of the A/B test — appears in cookies */ - name?: string; - matcher?: unknown; - /** @description URL to run the A/B test against */ - urlToRunAgainst?: string; - replaces?: unknown[]; - includeScriptsToHead?: { includes?: Script[] }; - includeScriptsToBody?: { includes?: Script[] }; -} - -/** @titleBy framework */ -export interface FreshFlavor { - /** @default fresh */ - framework: "fresh"; -} - -/** @titleBy framework */ -export interface HtmxFlavor { - /** @default htmx */ - framework: "htmx"; -} - -export interface WebsiteProps { - /** @title Routes Map */ - routes?: unknown[]; - - /** @title Global Sections */ - global?: unknown[]; - - /** @title Error Page */ - errorPage?: unknown; - - /** @title Caching configuration of pages */ - caching?: Caching; - - /** - * @title Global Async Rendering (Deprecated) - * @deprecated true - * @default false - */ - firstByteThresholdMS?: boolean; - - /** @title Avoid redirecting to editor */ - avoidRedirectingToEditor?: boolean; - - /** @title AB Testing */ - abTesting?: AbTesting; - - /** @title Flavor */ - flavor?: FreshFlavor | HtmxFlavor; - - /** @title Seo */ - seo?: Record; - - /** @title Theme */ - theme?: unknown; - - /** @hide true */ - sendToClickHouse?: boolean; - - /** @title Default Image Quality */ - defaultImageQuality?: string; - - /** @title Disable image/asset proxy for this site */ - disableProxy?: boolean; - - /** @title Whilelist URL Patterns */ - whilelistURLs?: string[]; -} - -/** Secret block shape in decofile (website/loaders/secret.ts). */ -export type Secret = SecretProps; diff --git a/website/mod.ts b/website/mod.ts index 7bf2aa7..058030a 100644 --- a/website/mod.ts +++ b/website/mod.ts @@ -8,8 +8,114 @@ import type { AppDefinition, ResolveSecretFn } from "../commerce/app-types"; import { configureWebsite } from "./client"; import manifest from "./manifest.gen"; +import type { Props as SecretProps } from "./loaders/secret"; import type { WebsiteConfig } from "./types"; +// ------------------------------------------------------------------------- +// CMS Props (mirrors deco-cx/apps/website/mod.ts) +// ------------------------------------------------------------------------- + +export type Script = { src: string }; + +export interface CacheDirectiveBase { + name: string; + value: number; +} + +export interface StaleWhileRevalidate extends CacheDirectiveBase { + name: "stale-while-revalidate"; +} + +export interface MaxAge extends CacheDirectiveBase { + name: "max-age"; +} + +export type CacheDirective = StaleWhileRevalidate | MaxAge; + +export interface Caching { + enabled?: boolean; + directives?: CacheDirective[]; +} + +export interface AbTesting { + enabled?: boolean; + /** @description The name of the A/B test — appears in cookies */ + name?: string; + matcher?: unknown; + /** @description URL to run the A/B test against */ + urlToRunAgainst?: string; + replaces?: unknown[]; + includeScriptsToHead?: { includes?: Script[] }; + includeScriptsToBody?: { includes?: Script[] }; +} + +/** @titleBy framework */ +export interface FreshFlavor { + /** @default fresh */ + framework: "fresh"; +} + +/** @titleBy framework */ +export interface HtmxFlavor { + /** @default htmx */ + framework: "htmx"; +} + +/** @title Website */ +export interface Props { + /** @title Routes Map */ + routes?: unknown[]; + + /** @title Global Sections */ + global?: unknown[]; + + /** @title Error Page */ + errorPage?: unknown; + + /** @title Caching configuration of pages */ + caching?: Caching; + + /** + * @title Global Async Rendering (Deprecated) + * @deprecated true + * @default false + */ + firstByteThresholdMS?: boolean; + + /** @title Avoid redirecting to editor */ + avoidRedirectingToEditor?: boolean; + + /** @title AB Testing */ + abTesting?: AbTesting; + + /** @title Flavor */ + flavor?: FreshFlavor | HtmxFlavor; + + /** @title Seo */ + seo?: Record; + + /** @title Theme */ + theme?: unknown; + + /** @hide true */ + sendToClickHouse?: boolean; + + /** @title Default Image Quality */ + defaultImageQuality?: string; + + /** @title Disable image/asset proxy for this site */ + disableProxy?: boolean; + + /** @title Whilelist URL Patterns */ + whilelistURLs?: string[]; +} + +/** Alias for site app bridges that extend website Props. */ +export type WebsiteProps = Props; + +/** Secret block shape in decofile (website/loaders/secret.ts). */ +export type Secret = SecretProps; + // ------------------------------------------------------------------------- // State // ------------------------------------------------------------------------- From de25f17c9ed67ca0144b425f9dc047b3da25983e Mon Sep 17 00:00:00 2001 From: guitavano Date: Thu, 18 Jun 2026 17:21:56 -0300 Subject: [PATCH 3/3] fix(lint): organize imports in website/mod and biome-ignore vtex configure Co-authored-by: Cursor --- vtex/mod.ts | 1 + website/mod.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/vtex/mod.ts b/vtex/mod.ts index ac5c953..117a667 100644 --- a/vtex/mod.ts +++ b/vtex/mod.ts @@ -112,6 +112,7 @@ const vtexMiddleware: AppMiddleware = async (request, next) => { * Returns an AppDefinition or null if required fields are missing. */ export async function configure( + // biome-ignore lint/suspicious/noExplicitAny: block data comes from CMS with no fixed schema block: any, resolveSecret: ResolveSecretFn, ): Promise | null> { diff --git a/website/mod.ts b/website/mod.ts index 058030a..cef152e 100644 --- a/website/mod.ts +++ b/website/mod.ts @@ -7,8 +7,8 @@ import type { AppDefinition, ResolveSecretFn } from "../commerce/app-types"; import { configureWebsite } from "./client"; -import manifest from "./manifest.gen"; import type { Props as SecretProps } from "./loaders/secret"; +import manifest from "./manifest.gen"; import type { WebsiteConfig } from "./types"; // -------------------------------------------------------------------------