Skip to content
Draft
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,6 @@ storybook-static
./playwright

.local_data

# mock-notify store
.notifications.json
29 changes: 8 additions & 21 deletions core/actions/_lib/interpolationContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import type {
import type { FullAutomation, Json } from "db/types"
import type { CommunityStage } from "~/lib/server/stages"

import { createPubProxy } from "./pubProxy"
import { createPubProxy, type IncomingRelations } from "./pubProxy"

export type InterpolationContextBase = {
community: InterpolationCommunity
Expand All @@ -23,26 +23,12 @@ export type InterpolationContextBase = {
env: InterpolationEnv
}

export type InterpolationContextWithPub = InterpolationContextBase & {
pub: ReturnType<typeof createPubProxy>
json?: Json
}

export type InterpolationContextWithJson = InterpolationContextBase & {
json: Json
export type InterpolationContext = InterpolationContextBase & {
pub?: ReturnType<typeof createPubProxy>
json?: Json
site?: { base: string }
}

export type InterpolationContextWithBoth = InterpolationContextBase & {
pub: ReturnType<typeof createPubProxy>
json: Json
}

export type InterpolationContext =
| InterpolationContextWithPub
| InterpolationContextWithJson
| InterpolationContextWithBoth

type InterpolationCommunity = Pick<Communities, "id" | "name" | "slug" | "avatar">

type InterpolationStage = Pick<CommunityStage, "id" | "name">
Expand All @@ -66,6 +52,7 @@ type BuildInterpolationContextArgsBase = {
automation: FullAutomation
automationRun: InterpolationAutomationRun
user: InterpolationUser | null
incomingRelations?: IncomingRelations
}

type BuildInterpolationContextArgs =
Expand Down Expand Up @@ -107,7 +94,7 @@ type BuildInterpolationContextArgs =
export function buildInterpolationContext(
args: BuildInterpolationContextArgs
): InterpolationContext {
const baseContext: Omit<InterpolationContext, "json" | "pub"> = {
const baseContext: Omit<InterpolationContext, "json" | "pub" | "site"> = {
env: {
PUBPUB_URL: args.env.PUBPUB_URL,
},
Expand Down Expand Up @@ -158,15 +145,15 @@ export function buildInterpolationContext(
// Both pub and json provided
return {
...baseContext,
pub: createPubProxy(args.pub, args.community.slug),
pub: createPubProxy(args.pub, args.community.slug, args.incomingRelations),
json: args.json,
}
}

if (args.pub) {
return {
...baseContext,
pub: createPubProxy(args.pub, args.community.slug),
pub: createPubProxy(args.pub, args.community.slug, args.incomingRelations),
}
}

Expand Down
107 changes: 15 additions & 92 deletions core/actions/_lib/pubProxy.ts
Original file line number Diff line number Diff line change
@@ -1,93 +1,3 @@
// import type { ProcessedPub } from "contracts"

// export const createPubProxy = (pub: ProcessedPub, communitySlug: string): any => {
// const valuesMap = new Map<string, ProcessedPub["values"][number]>()

// // these are just so that if you do `$.values`/`$.out`/`$.fields` you can see what fields are available
// const fields: Record<string, undefined> = {}
// const relations: Record<string, undefined> = {}

// for (const value of pub.values) {
// fields[value.fieldSlug] = undefined
// fields[value.fieldSlug.replace(`${communitySlug}:`, "")] = undefined
// if (value.relatedPub) {
// relations[value.fieldSlug] = undefined
// relations[value.fieldSlug.replace(`${communitySlug}:`, "")] = undefined
// }
// valuesMap.set(value.fieldSlug, value)
// }

// const pubWithAdditionalFields = { ...pub, fields: undefined, out: relations }

// return new Proxy(pubWithAdditionalFields, {
// get(target, prop) {
// const propStr = String(prop)
// const _lowerProp = propStr.toLowerCase()

// if (prop === "fields") {
// return new Proxy(fields, {
// get(_, fieldSlug: string) {
// return fields[fieldSlug] || fields[fieldSlug.toLowerCase()]
// },
// })
// }
// if (prop === "values") {
// return new Proxy(fields, {
// get(_, fieldSlug: string) {
// const lowerFieldSlug = fieldSlug.toLowerCase()
// const val =
// valuesMap.get(`${communitySlug}:${fieldSlug}`) ??
// valuesMap.get(fieldSlug) ??
// valuesMap.get(`${communitySlug}:${lowerFieldSlug}`) ??
// valuesMap.get(lowerFieldSlug)
// return val?.value
// },
// })
// }

// if (prop === "out") {
// return new Proxy(relations, {
// get(_, fieldSlug: string) {
// if (typeof fieldSlug !== "string") {
// return undefined
// }

// if (fieldSlug === "out") {
// return relations
// }

// const lowerFieldSlug = fieldSlug.toLowerCase()

// const val =
// valuesMap.get(`${communitySlug}:${fieldSlug}`) ??
// valuesMap.get(fieldSlug) ??
// valuesMap.get(`${communitySlug}:${lowerFieldSlug}`) ??
// valuesMap.get(lowerFieldSlug)
// if (val && "relatedPub" in val && val.relatedPub) {
// return createPubProxy(val.relatedPub, communitySlug)
// }
// return undefined
// },
// })
// }

// if (prop === "in") {
// return new Proxy(relations, {
// get(_, fieldSlug: string) {
// const _lowerFieldSlug = fieldSlug.toLowerCase()
// // For "in", we look for pubs that point to this one via fieldSlug
// // This proxy doesn't currently support "in" metadata easily as it's not pre-loaded in valuesMap in the same way.
// // However, if we had it, we'd look it up here.
// return undefined
// },
// })
// }

// return target[prop as keyof typeof target]
// },
// })
// }

import type { ProcessedPub } from "contracts"

// properties that should never be forwarded through the proxy
Expand Down Expand Up @@ -146,9 +56,12 @@ const createLookupProxy = <T>(
})
}

export type IncomingRelations = Record<string, ProcessedPub[]>

export const createPubProxy = (
pub: ProcessedPub,
communitySlug: string
communitySlug: string,
incomingRelations?: IncomingRelations
): Record<string, unknown> => {
// build plain objects for all lookups
const fields: Record<string, true> = {}
Expand All @@ -166,9 +79,19 @@ export const createPubProxy = (
}
}

// build incoming relations lookup: field slug -> array of pub proxies
const inObj: Record<string, Record<string, unknown>[]> = {}
if (incomingRelations) {
for (const [slug, pubs] of Object.entries(incomingRelations)) {
const shortSlug = slug.replace(`${communitySlug}:`, "")
inObj[shortSlug] = pubs.map((p) => createPubProxy(p, communitySlug))
}
}

const fieldsProxy = createLookupProxy(fields, communitySlug)
const valuesProxy = createLookupProxy(values, communitySlug)
const outProxy = createLookupProxy(out, communitySlug)
const inProxy = createLookupProxy(inObj, communitySlug)

// build the base object with all pub properties except values (which we override)
const base: Record<string, unknown> = {}
Expand All @@ -180,7 +103,7 @@ export const createPubProxy = (
base.fields = fieldsProxy
base.values = valuesProxy
base.out = outProxy
base.in = {} // not implemented yet
base.in = inProxy

return new Proxy(base, {
get(target, prop) {
Expand Down
7 changes: 6 additions & 1 deletion core/actions/_lib/runAutomation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import { db } from "~/kysely/database"
import { getAutomation } from "~/lib/db/queries"
import { env } from "~/lib/env/env"
import { createLastModifiedBy } from "~/lib/lastModifiedBy"
import { ApiError, getPubsWithRelatedValues } from "~/lib/server"
import { ApiError, getIncomingRelations, getPubsWithRelatedValues } from "~/lib/server"
import { getActionConfigDefaults, getAutomationRunById } from "~/lib/server/actions"
import { MAX_STACK_DEPTH } from "~/lib/server/automations"
import { autoRevalidate } from "~/lib/server/cache/autoRevalidate"
Expand Down Expand Up @@ -408,6 +408,10 @@ const runActionInstance = async (args: RunActionInstanceArgs): Promise<ActionIns
.validate()
const mergedConfig = actionConfigBuilder.getMergedConfig()

const incomingRelations = pub
? await getIncomingRelations(pub.id, args.community.id)
: undefined

const interpolationData = buildInterpolationContext({
env: {
PUBPUB_URL: env.PUBPUB_URL,
Expand All @@ -422,6 +426,7 @@ const runActionInstance = async (args: RunActionInstanceArgs): Promise<ActionIns
},
automationRun: args.automationRun,
user: args.user ?? null,
incomingRelations,
...(pub ? { pub, json: args.json } : { json: args.json ?? ({} as Json) }),
})

Expand Down
61 changes: 10 additions & 51 deletions core/actions/buildSite/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,50 +5,6 @@ import { Action } from "db/public"

import { defineAction } from "../types"

// default CSS for built sites
export const DEFAULT_SITE_CSS = `
:root {
--color-bg: #ffffff;
--color-text: #1a1a1a;
--color-muted: #6b7280;
--color-border: #e5e7eb;
--color-accent: #3b82f6;
--font-sans: system-ui, -apple-system, sans-serif;
--font-mono: ui-monospace, monospace;
}

* { box-sizing: border-box; margin: 0; padding: 0; }

body {
font-family: var(--font-sans);
line-height: 1.6;
color: var(--color-text);
background: var(--color-bg);
max-width: 800px;
margin: 0 auto;
padding: 2rem 1rem;
}

h1, h2, h3 { line-height: 1.3; margin-bottom: 0.5em; }
h1 { font-size: 2rem; }
h2 { font-size: 1.5rem; color: var(--color-muted); }

.pub-field { margin-bottom: 1.5rem; }
.pub-field-label {
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--color-muted);
margin-bottom: 0.25rem;
}
.pub-field-value { font-size: 1rem; }
.pub-field-value:empty::after { content: "—"; color: var(--color-muted); }

a { color: var(--color-accent); }
pre, code { font-family: var(--font-mono); font-size: 0.875rem; }
pre { background: #f3f4f6; padding: 1rem; border-radius: 0.5rem; overflow-x: auto; }
`.trim()

// default template that iterates through all pub values
// $.pub.values is an object with field slugs as keys
export const DEFAULT_PAGE_TEMPLATE = `
Expand Down Expand Up @@ -77,25 +33,28 @@ const schema = z.object({
.describe(
"Subpath for deployment (e.g., 'journal-2024'). If not provided, uses the automation run ID."
),
css: z
.string()
.optional()
.describe("Custom CSS for the generated pages. Leave empty to use the default styles."),
pages: z
.array(
z.object({
filter: z
.string()
.describe("A filter expression that selects which pubs to include"),
slug: z.string().describe("JSONata expression for the page URL slug"),
.optional()
.describe(
"A filter expression that selects which pubs to include. If omitted, the group produces a single static file with no pub context."
),
slug: z
.string()
.describe(
"JSONata expression for the page URL slug. If the slug references $.pub (e.g. $.pub.id), one page is produced per matched pub with $.pub in context. If the slug is static (e.g. 'index'), a single page is produced with all matched pubs available as $.pubs."
),
transform: z
.string()
.describe("JSONata expression that outputs content for the page"),
extension: z
.string()
.default("html")
.describe(
"File extension for the generated output (e.g., 'html', 'json', 'xml'). Only 'html' pages are wrapped in an HTML shell."
"File extension for the generated output (e.g., 'html', 'json', 'xml', 'css'). Only 'html' pages are wrapped in an HTML shell. If content starts with <!DOCTYPE, it is used as-is without wrapping."
),
})
)
Expand Down
34 changes: 1 addition & 33 deletions core/actions/buildSite/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { QueryBuilder } from "ui/queryBuilder"
import { useServerAction } from "~/lib/serverActions"
import { ActionField } from "../_lib/ActionField"
import { useActionForm } from "../_lib/ActionForm"
import { DEFAULT_PAGE_TEMPLATE, DEFAULT_SITE_CSS } from "./action"
import { DEFAULT_PAGE_TEMPLATE } from "./action"
import { previewResult } from "./formActions"

export default function BuildSiteActionForm() {
Expand Down Expand Up @@ -60,11 +60,6 @@ export default function BuildSiteActionForm() {
form.setValue(`${pagesPath}.${index}.transform`, DEFAULT_PAGE_TEMPLATE)
}

const insertDefaultCss = () => {
const cssPath = path ? `${path}.css` : "css"
form.setValue(cssPath, DEFAULT_SITE_CSS)
}

return (
<FieldSet className="space-y-6">
<div className="space-y-4">
Expand All @@ -73,33 +68,6 @@ export default function BuildSiteActionForm() {
label="Deployment Path"
description="URL path for this build (e.g., 'v1' or '2024'). Defaults to a unique ID."
/>

<div>
<div className="mb-2 flex items-center justify-between">
<FieldLabel>Custom CSS</FieldLabel>
<Button
type="button"
variant="ghost"
size="sm"
className="h-7 text-xs"
onClick={insertDefaultCss}
>
Insert default
</Button>
</div>
<ActionField
name="css"
render={({ field }) => (
<MonacoFormField
field={field}
language="css"
theme={theme}
height="150px"
expandedHeight="50vh"
/>
)}
/>
</div>
</div>

<div className="space-y-3">
Expand Down
Loading
Loading