Skip to content
Closed
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
54 changes: 13 additions & 41 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -30,37 +30,9 @@ ifeq ($(APP_ENV),production)
COMPOSE_COMMAND=docker-compose -f docker-compose.yml -f docker-compose.prod.yml
endif

#----------------------
# Terminal
#----------------------
HELP_NAME := docs

GREEN := $(shell tput -Txterm setaf 2)
WHITE := $(shell tput -Txterm setaf 7)
YELLOW := $(shell tput -Txterm setaf 3)
RESET := $(shell tput -Txterm sgr0)

#------------------------------------------------------------------
# - Add the following 'help' target to your Makefile
# - Add help text after each target name starting with '\#\#'
# - A category can be added with @category
#------------------------------------------------------------------

.PHONY: build test

HELP_FUN = \
%help; \
while(<>) { \
push @{$$help{$$2 // 'options'}}, [$$1, $$3] if /^([a-zA-Z\-]+)\s*:.*\#\#(?:@([a-zA-Z\-]+))?\s(.*)$$/ }; \
print "\n"; \
for (sort keys %help) { \
print "${WHITE}$$_${RESET \
}\n"; \
for (@{$$help{$$_}}) { \
$$sep = " " x (32 - length $$_->[0]); \
print " ${YELLOW}$$_->[0]${RESET}$$sep${GREEN}$$_->[1]${RESET}\n"; \
}; \
print ""; \
}
HELP_FUN = %help; while (<>) { /^([A-Za-z0-9_-]+)\s*:.*\#\#(?:@([A-Za-z0-9_-]+))?\s(.*)$$/ or next; push @{$$help{$$2 || "other"}}, [$$1, $$3]; $$width = length($$1) if length($$1) > $$width } print "\e[1;97m$(or $(HELP_NAME),$(notdir $(CURDIR)))\e[0m\n\n"; for $$category (sort keys %help) { print "\e[1;97m$$category\e[0m\n"; for $$entry (@{$$help{$$category}}) { printf " \e[1;32m%-*s\e[0m \e[90m%s\e[0m\n", $$width, $$entry->[0], $$entry->[1] } }

help: ##@other Show this help.
@perl -e '$(HELP_FUN)' $(MAKEFILE_LIST)
Expand All @@ -69,27 +41,27 @@ help: ##@other Show this help.
# docs
#----------------------

docs-generate: ##@docs Generate docs pages and example manifest
docs-generate: ##@documentation Generate docs pages and example manifest
@cd backend && go run . docs:generate

docs-proof-stats: ##@docs Refresh checked-in proof statistics from sibling repositories
docs-proof-refresh: ##@documentation Refresh checked-in proof statistics from sibling repositories
@cd docs && npm run proof:refresh

docs-check-proof-stats: ##@docs Verify checked-in proof statistics match sibling repositories
docs-proof-check: ##@documentation Verify checked-in proof statistics match sibling repositories
@cd docs && npm run proof:check

docs-check-scenarios: ##@docs Verify generated scenario pages match framework specs
docs-scenarios-check: ##@documentation Verify generated scenario pages match framework specs
@cd ../goforj && go run ./cmd/forj scenario:generate --all --check

docs-build: docs-check-proof-stats docs-check-scenarios ##@docs Verify generated evidence and build VitePress docs
docs-build: docs-proof-check docs-scenarios-check ##@documentation Verify generated evidence and build VitePress docs
@cd docs && npm run build

docs-embed: ##@docs Copy built docs into backend embed folder
docs-embed: ##@documentation Copy built docs into backend embed folder
@rm -rf backend/frontend/dist
@mkdir -p backend/frontend/dist
@cp -R docs/.vitepress/dist/. backend/frontend/dist/

docs-package: ##@docs Generate + build docs and stage for backend
docs-package: ##@documentation Generate + build docs and stage for backend
@$(MAKE) docs-generate
@$(MAKE) docs-build
@$(MAKE) docs-embed
Expand All @@ -101,21 +73,21 @@ docs-package: ##@docs Generate + build docs and stage for backend
DOCKER_PROD_IMAGE ?= docs-web:latest
DOCKER_PROD_PUSH ?= 0

docker-production: ##@docker Build the production web image
docker-build-prod: ##@docker Build the production web image
@docker buildx build \
-f containers/web/Dockerfile \
--build-arg GA_MEASUREMENT_ID=$(GA_MEASUREMENT_ID) \
-t $(DOCKER_PROD_IMAGE) \
$(if $(filter 1 true yes,$(DOCKER_PROD_PUSH)),--push,--load) \
.

docker-build-prod: docker-generate-docs-prod docker-production ##@docker Generate docs and build production image
docker-package-prod: docker-generate-prod docker-build-prod ##@docker Generate docs and build the production image

docker-generate-docs-prod: ##@docker Generate docs from upstream repos in a one-off container (updates docs/libraries/*.md)
docker-generate-prod: ##@docker Generate docs from upstream repositories in a one-off container
@$(COMPOSE_COMMAND) run --rm --build docs-generate

docker-deploy-prod: ##@docker Generate docs, build prod image, and roll web container
@$(MAKE) docker-build-prod
@$(MAKE) docker-package-prod
@$(COMPOSE_COMMAND) up -d --force-recreate web

#----------------------
Expand Down
16 changes: 8 additions & 8 deletions backend/internal/docs/docs_generate_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func (c *GenerateCommand) Run() error {
FrameworkGuide: FrameworkGuide{
Title: "HTTP Services",
Path: "/applications/http-services",
Summary: "Generated Apps register web routes and controllers through the HTTP runtime. Keep server wiring in framework providers and inject application services into controllers.",
Summary: "GoForj Apps register web routes and controllers through the HTTP runtime. Keep server wiring in framework providers and inject application services into controllers.",
},
},
{
Expand Down Expand Up @@ -114,7 +114,7 @@ func (c *GenerateCommand) Run() error {
FrameworkGuide: FrameworkGuide{
Title: "Scheduler",
Path: "/async/scheduler",
Summary: "Generated Apps register schedules in the scheduler runtime and inject the jobs they run. Keep recurring business work in jobs instead of the schedule registry.",
Summary: "GoForj Apps register schedules in the scheduler runtime and inject the jobs they run. Keep recurring business work in jobs instead of the schedule registry.",
},
},
{
Expand All @@ -127,7 +127,7 @@ func (c *GenerateCommand) Run() error {
FrameworkGuide: FrameworkGuide{
Title: "Queues",
Path: "/async/queues",
Summary: "Generated Apps expose named queues through generated accessors. Dispatch jobs through those accessors and keep backend selection in queue configuration.",
Summary: "GoForj Apps expose named queues through generated accessors. Dispatch jobs through those accessors and keep backend selection in queue configuration.",
},
},
{
Expand All @@ -140,7 +140,7 @@ func (c *GenerateCommand) Run() error {
FrameworkGuide: FrameworkGuide{
Title: "Events",
Path: "/async/events",
Summary: "Generated Apps expose named event buses through generated accessors. Publish through those accessors and keep driver selection in event configuration.",
Summary: "GoForj Apps expose named event buses through generated accessors. Publish through those accessors and keep driver selection in event configuration.",
},
},
{
Expand All @@ -153,7 +153,7 @@ func (c *GenerateCommand) Run() error {
FrameworkGuide: FrameworkGuide{
Title: "Mail",
Path: "/applications/mail",
Summary: "Generated Apps expose named mailers through generated accessors. Send through those accessors and keep transport selection and credentials in configuration.",
Summary: "GoForj Apps expose named mailers through generated accessors. Send through those accessors and keep transport selection and credentials in configuration.",
},
},
{
Expand All @@ -166,7 +166,7 @@ func (c *GenerateCommand) Run() error {
FrameworkGuide: FrameworkGuide{
Title: "Cache Patterns",
Path: "/data/cache-patterns",
Summary: "Generated Apps expose named caches through generated accessors. Use those accessors in application services and keep backend selection in cache configuration.",
Summary: "GoForj Apps expose named caches through generated accessors. Use those accessors in application services and keep backend selection in cache configuration.",
},
},
{
Expand All @@ -187,7 +187,7 @@ func (c *GenerateCommand) Run() error {
FrameworkGuide: FrameworkGuide{
Title: "Storage Patterns",
Path: "/data/storage-patterns",
Summary: "Generated Apps expose named disks through generated accessors. Use those accessors in application services and keep backend selection in storage configuration.",
Summary: "GoForj Apps expose named disks through generated accessors. Use those accessors in application services and keep backend selection in storage configuration.",
},
},
{
Expand All @@ -200,7 +200,7 @@ func (c *GenerateCommand) Run() error {
FrameworkGuide: FrameworkGuide{
Title: "Metrics",
Path: "/operations/metrics",
Summary: "Generated Apps expose metrics through the observability and HTTP runtime. Keep registration close to the behavior being measured and configure scrape exposure through the App runtime.",
Summary: "GoForj Apps expose metrics through the observability and HTTP runtime. Keep registration close to the behavior being measured and configure scrape exposure through the App runtime.",
},
},
{
Expand Down
2 changes: 1 addition & 1 deletion backend/internal/docs/readme_transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func appendFrameworkGuide(content string, guide FrameworkGuide) string {

content = strings.TrimRight(content, "\n")
return fmt.Sprintf(
"%s\n\n## Using with GoForj\n\n%s\n\nFor generated App integration, see [%s](%s).\n",
"%s\n\n## Using with GoForj\n\n%s\n\nFor the App workflow, see [%s](%s).\n",
content,
guide.Summary,
guide.Title,
Expand Down
6 changes: 3 additions & 3 deletions backend/internal/docs/readme_transform_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,16 @@ func TestTransformReadmeAppendsFrameworkGuide(t *testing.T) {
FrameworkGuide: FrameworkGuide{
Title: "Queues",
Path: "/async/queues",
Summary: "Generated Apps expose named queues through generated accessors.",
Summary: "GoForj Apps expose named queues through generated accessors.",
},
}

got := transformReadme("# Queue\n\nStandalone package documentation.\n", repo, "https://raw.githubusercontent.com/goforj/queue/main/")
wants := []string{
`description: "Queued work with pluggable backend drivers."`,
"## Using with GoForj {#using-with-goforj}",
"Generated Apps expose named queues through generated accessors.",
"For generated App integration, see [Queues](/async/queues).",
"GoForj Apps expose named queues through generated accessors.",
"For the App workflow, see [Queues](/async/queues).",
}
for _, want := range wants {
if !strings.Contains(got, want) {
Expand Down
2 changes: 1 addition & 1 deletion backend/internal/docs/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type RepoConfig struct {
FrameworkGuide FrameworkGuide
}

// FrameworkGuide links a standalone library page to its canonical generated App guide.
// FrameworkGuide links a standalone library page to its canonical App guide.
type FrameworkGuide struct {
Title string
Path string
Expand Down
5 changes: 3 additions & 2 deletions docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -933,7 +933,7 @@ const frontendSidebar = sectionSidebar('Frontend', [

const dataSidebar = sectionSidebar('Data and Persistence', [
{ text: 'Overview', link: '/data/' },
{ text: 'Database Strategy', link: '/data/database-strategy' },
{ text: 'Database Connections', link: '/data/database-strategy' },
{ text: 'Migrations', link: '/data/migrations' },
{ text: 'Repositories', link: '/data/repositories' },
{ text: 'Transactions', link: '/data/transactions' },
Expand Down Expand Up @@ -989,6 +989,7 @@ const operationsSidebar = sectionSidebar('Operations', [
{ text: 'Metrics', link: '/operations/metrics' },
{ text: 'Inspects', link: '/operations/inspects' },
{ text: 'Lighthouse', link: '/operations/lighthouse' },
{ text: 'Performance Benchmarks', link: '/operations/performance-benchmarks' },
{ text: 'Backup and Restore', link: '/operations/backups' }
])

Expand Down Expand Up @@ -1029,7 +1030,7 @@ const referenceSidebar = sectionSidebar('Reference', [
{ text: 'CLI Reference', link: '/reference/cli' },
{ text: 'Environment Reference', link: '/reference/env-vars' },
{ text: 'Configuration Reference', link: '/reference/configuration' },
{ text: 'Generated Files', link: '/reference/generated-files' },
{ text: 'File Ownership', link: '/reference/generated-files' },
{ text: 'Generation Commands', link: '/reference/generation-commands' },
{ text: 'Make Commands', link: '/reference/make-commands' },
{ text: 'Naming Conventions', link: '/reference/naming-conventions' },
Expand Down
3 changes: 3 additions & 0 deletions docs/.vitepress/scripts/audit-content-value.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,9 @@ function auditForbiddenPatterns() {
if (relativePath.startsWith('operations/') && /\|\s*Development alias\s*\|/i.test(source)) {
editorialFailures.push(`${relativePath}: operations pages must lead with supervised binary commands; link to development guidance instead of adding an alias column`)
}
if (/\bgenerated\s+(?:Apps?|applications?)\b/i.test(source)) {
editorialFailures.push(`${relativePath}: call the runnable boundary an App; attach generated to the specific file, accessor, provider, or output instead`)
}
}
}

Expand Down
108 changes: 108 additions & 0 deletions docs/.vitepress/theme/components/LighthouseProductView.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<template>
<figure class="gf-lighthouse" aria-labelledby="gf-lighthouse-caption">
<div class="gf-lighthouse__chrome">
<span></span><span></span><span></span>
<strong>Lighthouse</strong>
<em>connected</em>
</div>
<div class="gf-lighthouse__body">
<aside class="gf-lighthouse__rail" aria-label="Lighthouse views">
<b>Overview</b>
<span>Inspects</span>
<span>Routes</span>
<span>Queues</span>
<span>Schedules</span>
<span>Cache</span>
<span>Storage</span>
<span>Benchmarks</span>
</aside>
<div class="gf-lighthouse__workspace">
<div class="gf-lighthouse__identity">
<div><small>PROJECT</small><strong>photodrop</strong></div>
<div><small>APP</small><strong>app</strong></div>
<div><small>RUNTIME</small><strong>http</strong></div>
<span>live</span>
</div>
<div class="gf-lighthouse__grid">
<section class="gf-lighthouse__panel gf-lighthouse__panel--wide">
<header><strong>Recent inspects</strong><small>execution timeline</small></header>
<div class="gf-lighthouse__inspect">
<span class="is-method">GET</span><b>/-/health</b><span class="is-ok">OK</span><time>http</time>
</div>
<div class="gf-lighthouse__timeline" aria-label="Inspect event timeline">
<i></i><span>request started</span>
<i></i><span>route matched</span>
<i></i><span>response written</span>
</div>
</section>
<section class="gf-lighthouse__panel">
<header><strong>Routes</strong><small>registered</small></header>
<p><span class="is-method">GET</span><code>/-/health</code></p>
<p><span class="is-method is-post">POST</span><code>/api/photos</code></p>
</section>
<section class="gf-lighthouse__panel">
<header><strong>Runtime resources</strong><small>discovered</small></header>
<p><b>Queues</b><span>default</span></p>
<p><b>Schedules</b><span>registered</span></p>
<p><b>Storage</b><span>local</span></p>
</section>
</div>
</div>
</div>
<figcaption id="gf-lighthouse-caption">A code-native view of the shipped Lighthouse workspace. The available views follow the components compiled into the selected App.</figcaption>
</figure>
</template>

<style scoped>
.gf-lighthouse {
margin: 1.75rem 0 2.25rem;
overflow: hidden;
border: 1px solid color-mix(in srgb, var(--vp-c-divider) 78%, #ffb648 22%);
border-radius: 18px;
background: #100d13;
box-shadow: 0 26px 70px rgb(0 0 0 / 28%);
color: #eee9f2;
}
.gf-lighthouse__chrome { display: flex; align-items: center; gap: 7px; padding: 11px 14px; border-bottom: 1px solid #2b2431; background: #18131c; }
.gf-lighthouse__chrome > span { width: 8px; height: 8px; border-radius: 50%; background: #665c6d; }
.gf-lighthouse__chrome > span:first-child { background: #ff725e; }
.gf-lighthouse__chrome > span:nth-child(2) { background: #ffc24d; }
.gf-lighthouse__chrome > span:nth-child(3) { background: #5fcfa8; }
.gf-lighthouse__chrome strong { margin-left: 8px; font-size: 12px; letter-spacing: .08em; text-transform: uppercase; }
.gf-lighthouse__chrome em { margin-left: auto; color: #78dbb8; font-size: 11px; font-style: normal; }
.gf-lighthouse__body { display: grid; grid-template-columns: 142px minmax(0, 1fr); min-height: 390px; }
.gf-lighthouse__rail { display: flex; flex-direction: column; gap: 3px; padding: 17px 10px; border-right: 1px solid #2b2431; color: #9990a1; font-size: 11px; }
.gf-lighthouse__rail span, .gf-lighthouse__rail b { padding: 7px 10px; border-radius: 7px; }
.gf-lighthouse__rail b { background: #2b2231; color: #fff; }
.gf-lighthouse__workspace { min-width: 0; padding: 18px; background: radial-gradient(circle at 75% 5%, rgb(125 72 148 / 13%), transparent 38%), #100d13; }
.gf-lighthouse__identity { display: grid; grid-template-columns: 1.4fr 1fr 1fr auto; gap: 8px; margin-bottom: 12px; }
.gf-lighthouse__identity > div { padding: 9px 11px; border: 1px solid #312838; border-radius: 9px; background: #18141c; }
.gf-lighthouse__identity small { display: block; color: #84798c; font-size: 8px; letter-spacing: .16em; }
.gf-lighthouse__identity strong { display: block; margin-top: 2px; font-size: 12px; }
.gf-lighthouse__identity > span { align-self: center; padding: 4px 9px; border: 1px solid rgb(95 207 168 / 30%); border-radius: 99px; background: rgb(95 207 168 / 9%); color: #78dbb8; font-size: 10px; }
.gf-lighthouse__grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.gf-lighthouse__panel { min-width: 0; padding: 13px; border: 1px solid #312838; border-radius: 11px; background: rgb(27 22 31 / 92%); }
.gf-lighthouse__panel--wide { grid-row: span 2; }
.gf-lighthouse__panel header { display: flex; justify-content: space-between; gap: 8px; padding-bottom: 10px; border-bottom: 1px solid #2b2431; font-size: 11px; }
.gf-lighthouse__panel header small { color: #84798c; }
.gf-lighthouse__inspect { display: grid; grid-template-columns: auto 1fr auto auto; align-items: center; gap: 8px; margin-top: 11px; padding: 9px; border-radius: 8px; background: #100d13; font-size: 10px; }
.gf-lighthouse__inspect time { color: #84798c; }
.is-method { display: inline-flex; width: max-content; padding: 2px 5px; border: 1px solid rgb(86 170 255 / 35%); border-radius: 5px; color: #8dc6ff; font-size: 9px; font-weight: 700; }
.is-post { border-color: rgb(198 137 255 / 35%); color: #d4a8ff; }
.is-ok { color: #78dbb8; }
.gf-lighthouse__timeline { display: grid; grid-template-columns: 12px 1fr; gap: 13px 8px; margin: 18px 10px; color: #aaa1b1; font-size: 10px; }
.gf-lighthouse__timeline i { position: relative; width: 7px; height: 7px; margin-top: 3px; border: 2px solid #ffc24d; border-radius: 50%; }
.gf-lighthouse__timeline i:not(:last-of-type)::after { content: ''; position: absolute; top: 8px; left: 1px; width: 1px; height: 22px; background: #51405d; }
.gf-lighthouse__panel p { display: flex; align-items: center; gap: 8px; margin: 9px 0 0; color: #aaa1b1; font-size: 10px; }
.gf-lighthouse__panel p span:last-child { margin-left: auto; color: #84798c; }
.gf-lighthouse__panel code { overflow: hidden; color: #d8d0dd; font-size: 10px; text-overflow: ellipsis; white-space: nowrap; }
.gf-lighthouse figcaption { padding: 10px 14px; border-top: 1px solid #2b2431; color: #8f8596; font-size: 10px; line-height: 1.5; }
@media (max-width: 700px) {
.gf-lighthouse__body { grid-template-columns: 1fr; }
.gf-lighthouse__rail { flex-direction: row; overflow-x: auto; border-right: 0; border-bottom: 1px solid #2b2431; }
.gf-lighthouse__rail span, .gf-lighthouse__rail b { white-space: nowrap; }
.gf-lighthouse__identity { grid-template-columns: 1fr 1fr; }
.gf-lighthouse__grid { grid-template-columns: 1fr; }
.gf-lighthouse__panel--wide { grid-row: auto; }
}
</style>
12 changes: 12 additions & 0 deletions docs/.vitepress/theme/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -7358,3 +7358,15 @@ html:not(.dark) .gf-blog-visual {
--gf-ink: #FFFFFF;
--gf-ink-2: #A9A1B3;
}
/* Atlas uses its source-owned product banner while the framework guide remains the preferred user path. */
.vp-doc .gf-atlas-banner {
margin: 1.5rem 0 1.75rem;
}

.vp-doc .gf-atlas-banner img {
display: block;
width: 100%;
border: 1px solid var(--vp-c-divider);
border-radius: 16px;
box-shadow: 0 18px 48px rgb(0 0 0 / 18%);
}
Loading