From 5b49149facb646a3fffac742a06825db720cd96b Mon Sep 17 00:00:00 2001 From: Simone Zhang Date: Fri, 26 Jun 2026 12:21:54 +0000 Subject: [PATCH 01/12] refactor(docs): per-block docs folders, merge index.md into docs/README.md, export ./docs/* --- packages/blocks/README.md | 17 +++- packages/blocks/package.json | 3 +- .../create-blocks-app/resources/AGENTS.md | 29 ++++++- scripts/sync-block-docs.mjs | 87 +++++++++++++------ 4 files changed, 100 insertions(+), 36 deletions(-) diff --git a/packages/blocks/README.md b/packages/blocks/README.md index 810e498f..a47bba22 100644 --- a/packages/blocks/README.md +++ b/packages/blocks/README.md @@ -109,7 +109,7 @@ onAuthChange(authApi, (user) => { ## Building Blocks -Each block is its own package; full per-block docs ship in this package under **`docs/.md`**, and **`docs/index.md`** has a decision tree to help you pick. +Each block is its own package; full per-block docs ship in this package under **`docs//`** (start with `README.md`, then `API.md` / `DESIGN.md`), and the catalog + decision tree in **`docs/README.md`** helps you pick. | Building Block | Import | Use it for | |---|---|---| @@ -133,7 +133,18 @@ Each block is its own package; full per-block docs ship in this package under ** | `Logger` / `Metrics` / `Tracer` / `Dashboard` | `@aws-blocks/blocks` | Observability — structured logs, metrics, traces, CloudWatch dashboard | | `Hosting` | `@aws-blocks/blocks` | Deploy a frontend (SPA / static / Next.js SSR) on CloudFront + S3 | -> **Not sure which data block?** Start with `DistributedTable` (DynamoDB). Reach for SQL only when you need joins across records, many-dimensional filtering, large transactions, or an existing Postgres database — `DistributedDatabase` for serverless Postgres, `Database` for full Aurora Postgres (FKs, RLS, triggers; carries idle cost / cold starts the other two don't). The full rationale is in `docs/index.md`. +> **Not sure which data block?** Start with `DistributedTable` (DynamoDB). Reach for SQL only when you need joins across records, many-dimensional filtering, large transactions, or an existing Postgres database — `DistributedDatabase` for serverless Postgres, `Database` for full Aurora Postgres (FKs, RLS, triggers; carries idle cost / cold starts the other two don't). The full rationale is in `docs/README.md`. + +### Per-block documentation + +Every Building Block ships its full docs under `docs//` in this package. Read them in this order: + +- **`/README.md`** — overview + common usage. **Start here.** +- **`/API.md`** — full API reference. Read when you need exact method signatures or option types. +- **`/DESIGN.md`** — architecture & rationale. Read when extending a block, debugging unexpected behavior, or making a non-trivial design decision. +- **block-specific guides** (e.g. `CUSTOMIZING-AUTH-UI.md`) — read when doing that specific task. + +The catalog + decision tree in `docs/README.md` help you pick a block in the first place. ## Local development and deploying @@ -183,7 +194,7 @@ Run with `npm run test:e2e`. Write the test first, iterate against mocks until i ## Reference -- **Per-block documentation:** `docs/.md` (e.g. `docs/bb-distributed-table.md`); `docs/index.md` for the catalog + decision tree. +- **Per-block documentation:** `docs//README.md` (overview), `docs//API.md` (full API reference), `docs//DESIGN.md` (architecture & rationale) — e.g. `docs/bb-distributed-table/README.md`. The catalog + decision tree live in `docs/README.md`. - **UI components** (`@aws-blocks/blocks/ui`): `Authenticator`, `AuthenticatedContent`, `AccountMenuBar`, `onAuthChange`, `broadcastAuthChange` — framework-agnostic, return DOM nodes. See the `@aws-blocks/auth-common` README. - **SSR** (`@aws-blocks/blocks/server`): `withAuth` forwards browser cookies to API calls during server rendering. See the `@aws-blocks/core` README. - **Wire protocol & debugging:** the client is JSON-RPC 2.0 over a single endpoint — you should never call it directly. For `curl`-level troubleshooting, see [TROUBLESHOOTING.md](./TROUBLESHOOTING.md). diff --git a/packages/blocks/package.json b/packages/blocks/package.json index 15b83566..85966fe0 100644 --- a/packages/blocks/package.json +++ b/packages/blocks/package.json @@ -45,7 +45,8 @@ "./utils": { "types": "./dist/utils.d.ts", "default": "./dist/utils.js" - } + }, + "./docs/*": "./docs/*" }, "scripts": { "prebuild": "node ../../scripts/sync-block-docs.mjs", diff --git a/packages/create-blocks-app/resources/AGENTS.md b/packages/create-blocks-app/resources/AGENTS.md index 8e0275f3..b1d15b32 100644 --- a/packages/create-blocks-app/resources/AGENTS.md +++ b/packages/create-blocks-app/resources/AGENTS.md @@ -5,9 +5,30 @@ - **Backend:** `aws-blocks/index.ts` — APIs, auth, data models - **Frontend:** `src/` — imports backend APIs via `import { api } from 'aws-blocks'` - **Tests:** `test/e2e.test.ts` — run with `npm run test:e2e` -- **Full guide:** `node_modules/@aws-blocks/blocks/README.md` — architecture, workflow, best practices, common mistakes -- **Block catalog + decision tree:** `node_modules/@aws-blocks/blocks/docs/index.md` -- **Per-block docs:** `node_modules/@aws-blocks/blocks/docs/.md` +- **Docs (dev guide + block catalog + decision tree, then per-block API/DESIGN):** bundled in `@aws-blocks/blocks` — see [Reading the Building Block docs](#reading-the-building-block-docs). + +## Reading the Building Block docs + +The `@aws-blocks/blocks` package ships its full documentation under `docs/`. Locate it by **resolution first** (version-correct, independent of where the package is installed), and fall back to a hard-coded `node_modules` path only if resolution fails. + +**Primary — resolve, then read.** The dev guide (architecture, workflow, best practices, common mistakes) plus the block catalog and decision tree all live in one file: + +```bash +node -e "console.log(require.resolve('@aws-blocks/blocks/docs/README.md'))" +``` + +Read the path it prints. For a specific block, resolve its docs the same way and read them in order — `README.md` (overview, start here), then `API.md` (exact method signatures / option types) and `DESIGN.md` (architecture & rationale) as needed: + +```bash +node -e "console.log(require.resolve('@aws-blocks/blocks/docs/bb-distributed-table/README.md'))" +``` + +Swap `bb-distributed-table` for the block you need; the catalog in `docs/README.md` lists every block. + +**Fallback — if resolution fails**, read the files directly: + +- Dev guide + catalog + decision tree: `node_modules/@aws-blocks/blocks/docs/README.md` +- Per-block: `node_modules/@aws-blocks/blocks/docs//{README,API,DESIGN}.md` ## Workflow @@ -19,7 +40,7 @@ ## Rules - **Use Building Blocks** for all persistence and cloud abstractions — never local files, in-memory arrays, or local databases. -- **Read block docs** at `node_modules/@aws-blocks/blocks/docs/.md` before using a block. +- **Read block docs** before using a block — resolve `@aws-blocks/blocks/docs//README.md` (see [Reading the Building Block docs](#reading-the-building-block-docs)), then `API.md` / `DESIGN.md` as needed. - **The JSON-RPC transport is invisible** — do not construct RPC payloads manually. Import and call the typed API directly. ## Deploying (requires AWS credentials) diff --git a/scripts/sync-block-docs.mjs b/scripts/sync-block-docs.mjs index 770c801b..6a7cddf6 100644 --- a/scripts/sync-block-docs.mjs +++ b/scripts/sync-block-docs.mjs @@ -3,10 +3,18 @@ // SPDX-License-Identifier: Apache-2.0 /** - * Assembles `packages/blocks/docs/` from all Building Block READMEs. + * Assembles `packages/blocks/docs/` from every Building Block's root markdown. * Run at build/publish time (not customer-side). Produces: - * packages/blocks/docs/index.md — decision tree + catalog - * packages/blocks/docs/.md — one per block + * packages/blocks/docs/README.md — dev guide + decision tree + catalog + * packages/blocks/docs//README.md — per-block overview + * packages/blocks/docs//API.md — per-block API reference (when present) + * packages/blocks/docs//DESIGN.md — per-block design notes (when present) + * packages/blocks/docs//.md — any other block-specific doc (when present) + * + * Inclusion rule: every package under packages/ that has a README.md and is not + * in EXCLUDED. For each included package, mirror every root-level *.md EXCEPT the + * ones in SKIP_MARKDOWN (CHANGELOG.md) — so block-specific docs are picked up + * automatically without listing them here. */ import { readFileSync, writeFileSync, mkdirSync, readdirSync, existsSync, rmSync } from 'node:fs'; @@ -16,36 +24,40 @@ import { fileURLToPath } from 'node:url'; const __dirname = dirname(fileURLToPath(import.meta.url)); const packagesDir = join(__dirname, '..', 'packages'); const outDir = join(packagesDir, 'blocks', 'docs'); +const devGuidePath = join(packagesDir, 'blocks', 'README.md'); const EXCLUDED = new Set(['blocks', 'data-common', 'foundations', 'create-blocks-app']); +// Root-level markdown that should NOT be mirrored into the per-block doc folder. +const SKIP_MARKDOWN = new Set(['CHANGELOG.md']); + const DECISION_TREE = `# AWS Blocks — Building Block Catalog Start from what you need: - **Store data** - - Simple key → value (caches, flags, user prefs) → \`KVStore\` ([bb-kv-store](./bb-kv-store.md)) - - Structured records with indexes and queries → \`DistributedTable\` ([bb-distributed-table](./bb-distributed-table.md)) — **default for most data** + - Simple key → value (caches, flags, user prefs) → \`KVStore\` ([bb-kv-store](./bb-kv-store/README.md)) + - Structured records with indexes and queries → \`DistributedTable\` ([bb-distributed-table](./bb-distributed-table/README.md)) — **default for most data** - Relational / SQL (joins, transactions) → see [Choosing a data block](#choosing-a-data-block) below - - Files, blobs, uploads, static assets → \`FileBucket\` ([bb-file-bucket](./bb-file-bucket.md)) - - A single config value or secret → \`AppSetting\` ([bb-app-setting](./bb-app-setting.md)) + - Files, blobs, uploads, static assets → \`FileBucket\` ([bb-file-bucket](./bb-file-bucket/README.md)) + - A single config value or secret → \`AppSetting\` ([bb-app-setting](./bb-app-setting/README.md)) - **Authenticate users** - - Username/password, prototypes/MVPs → \`AuthBasic\` ([bb-auth-basic](./bb-auth-basic.md)) - - Cognito user pools, MFA, groups → \`AuthCognito\` ([bb-auth-cognito](./bb-auth-cognito.md)) - - External identity provider (OIDC) → \`AuthOIDC\` ([bb-auth-oidc](./bb-auth-oidc.md)) + - Username/password, prototypes/MVPs → \`AuthBasic\` ([bb-auth-basic](./bb-auth-basic/README.md)) + - Cognito user pools, MFA, groups → \`AuthCognito\` ([bb-auth-cognito](./bb-auth-cognito/README.md)) + - External identity provider (OIDC) → \`AuthOIDC\` ([bb-auth-oidc](./bb-auth-oidc/README.md)) - **Run work outside the request/response** - - Fire-and-forget background jobs → \`AsyncJob\` ([bb-async-job](./bb-async-job.md)) - - Scheduled / recurring tasks → \`CronJob\` ([bb-cron-job](./bb-cron-job.md)) -- **Push live updates to browsers** (chat, presence, dashboards) → \`Realtime\` ([bb-realtime](./bb-realtime.md)) + - Fire-and-forget background jobs → \`AsyncJob\` ([bb-async-job](./bb-async-job/README.md)) + - Scheduled / recurring tasks → \`CronJob\` ([bb-cron-job](./bb-cron-job/README.md)) +- **Push live updates to browsers** (chat, presence, dashboards) → \`Realtime\` ([bb-realtime](./bb-realtime/README.md)) - **Build AI features** - - Agent with tool use + conversation → \`Agent\` ([bb-agent](./bb-agent.md)) - - Semantic document retrieval (RAG) → \`KnowledgeBase\` ([bb-knowledge-base](./bb-knowledge-base.md)) -- **Send transactional email** → \`EmailClient\` ([bb-email-client](./bb-email-client.md)) + - Agent with tool use + conversation → \`Agent\` ([bb-agent](./bb-agent/README.md)) + - Semantic document retrieval (RAG) → \`KnowledgeBase\` ([bb-knowledge-base](./bb-knowledge-base/README.md)) +- **Send transactional email** → \`EmailClient\` ([bb-email-client](./bb-email-client/README.md)) - **Observe and operate** - - Structured logs → \`Logger\` ([bb-logger](./bb-logger.md)) - - Custom metrics → \`Metrics\` ([bb-metrics](./bb-metrics.md)) - - Distributed traces → \`Tracer\` ([bb-tracer](./bb-tracer.md)) - - Auto CloudWatch dashboard → \`Dashboard\` ([bb-dashboard](./bb-dashboard.md)) + - Structured logs → \`Logger\` ([bb-logger](./bb-logger/README.md)) + - Custom metrics → \`Metrics\` ([bb-metrics](./bb-metrics/README.md)) + - Distributed traces → \`Tracer\` ([bb-tracer](./bb-tracer/README.md)) + - Auto CloudWatch dashboard → \`Dashboard\` ([bb-dashboard](./bb-dashboard/README.md)) ### Choosing a data block @@ -67,13 +79,27 @@ const packages = readdirSync(packagesDir).filter( const catalog = []; for (const pkg of packages) { - const content = readFileSync(join(packagesDir, pkg, 'README.md'), 'utf-8'); - writeFileSync(join(outDir, `${pkg}.md`), content); - catalog.push({ pkg, blurb: extractBlurb(content), keywords: extractKeywords(content) }); + const pkgDir = join(packagesDir, pkg); + const blockOutDir = join(outDir, pkg); + mkdirSync(blockOutDir, { recursive: true }); + + // Mirror every root-level markdown file (README.md, API.md, DESIGN.md, and any + // block-specific docs) except the skipped ones, so new docs ship automatically. + const mdFiles = readdirSync(pkgDir, { withFileTypes: true }).filter( + (entry) => entry.isFile() && entry.name.endsWith('.md') && !SKIP_MARKDOWN.has(entry.name), + ); + for (const entry of mdFiles) { + writeFileSync(join(blockOutDir, entry.name), readFileSync(join(pkgDir, entry.name), 'utf-8')); + } + + const readme = readFileSync(join(pkgDir, 'README.md'), 'utf-8'); + catalog.push({ pkg, blurb: extractBlurb(readme), keywords: extractKeywords(readme) }); } catalog.sort((a, b) => a.pkg.localeCompare(b.pkg)); -writeFileSync(join(outDir, 'index.md'), renderIndex(catalog)); + +const devGuide = readFileSync(devGuidePath, 'utf-8'); +writeFileSync(join(outDir, 'README.md'), renderReadme(devGuide, catalog)); console.log(`Synced ${catalog.length} block docs → packages/blocks/docs/`); @@ -98,20 +124,25 @@ function extractKeywords(content) { return match ? match[1].trim() : ''; } -function renderIndex(catalog) { +function renderReadme(devGuide, catalog) { + // docs/README.md is generated — the authored dev guide (packages/blocks/README.md) + // followed by the catalog + decision tree. Edit those sources, not docs/README.md. + return [devGuide.trimEnd(), '', '', renderCatalog(catalog), ''].join('\n'); +} + +function renderCatalog(catalog) { const rows = catalog.map( - (e) => `| [${e.pkg}](./${e.pkg}.md) | ${e.blurb || '—'} | ${e.keywords || '—'} |`, + (e) => `| [${e.pkg}](./${e.pkg}/README.md) | ${e.blurb || '—'} | ${e.keywords || '—'} |`, ); return [ DECISION_TREE, '', '## Catalog', '', - 'One page per Building Block. Read the linked doc before using a block.', + 'One folder per Building Block under `docs//`: start with its `README.md`, then read `API.md` for exact signatures and `DESIGN.md` for architecture & rationale.', '', '| Block | What it does | Keywords |', '|-------|--------------|----------|', ...rows, - '', ].join('\n'); } From 52d8c9d6529c65ca2b81940c4e9ab695febbfcca Mon Sep 17 00:00:00 2001 From: Simone Zhang Date: Sun, 28 Jun 2026 16:14:16 +0000 Subject: [PATCH 02/12] refactor(docs): commit BB catalog into README via markers + sync-docs --check CI gate --- .github/workflows/block-catalog-check.yml | 22 ++ package.json | 2 + packages/blocks/README.md | 101 ++++++--- packages/blocks/package.json | 2 +- scripts/sync-block-docs.mjs | 259 +++++++++++++--------- 5 files changed, 248 insertions(+), 138 deletions(-) create mode 100644 .github/workflows/block-catalog-check.yml diff --git a/.github/workflows/block-catalog-check.yml b/.github/workflows/block-catalog-check.yml new file mode 100644 index 00000000..810fa6c4 --- /dev/null +++ b/.github/workflows/block-catalog-check.yml @@ -0,0 +1,22 @@ +name: Block Catalog Check + +# Fails the PR if the generated Building Block catalog table in +# packages/blocks/README.md is out of date. The script only reads the committed +# package READMEs + root README (Node builtins, zero deps), so no `npm ci` needed. +# On failure its stderr tells the engineer to run `npm run sync-docs` and commit. + +on: + pull_request: + branches: [main] + +jobs: + check: + name: Catalog in sync + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-node@v5 + with: + node-version-file: '.nvmrc' + - name: Verify Building Block catalog is in sync + run: node scripts/sync-block-docs.mjs --check diff --git a/package.json b/package.json index 1cb91a68..56c9391a 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,8 @@ "create-demo": "node packages/create-blocks-app/dist/index.js --template demo", "update-template": "tsx scripts/update-template-from-demo.ts", "build:tech-design-pdf": "node scripts/build-tech-design-pdf.mjs", + "sync-docs": "node scripts/sync-block-docs.mjs --write", + "sync-docs:check": "node scripts/sync-block-docs.mjs --check", "review-pr": "bash scripts/review-pr.sh", "publish:dry-run": "tsx scripts/publish/publish.ts", "publish:npm": "tsx scripts/publish/publish-npm.ts", diff --git a/packages/blocks/README.md b/packages/blocks/README.md index a47bba22..2dd63943 100644 --- a/packages/blocks/README.md +++ b/packages/blocks/README.md @@ -109,42 +109,71 @@ onAuthChange(authApi, (user) => { ## Building Blocks -Each block is its own package; full per-block docs ship in this package under **`docs//`** (start with `README.md`, then `API.md` / `DESIGN.md`), and the catalog + decision tree in **`docs/README.md`** helps you pick. - -| Building Block | Import | Use it for | -|---|---|---| -| `Scope` | `@aws-blocks/blocks` | Resource boundaries / grouping for your backend | -| `ApiNamespace` | `@aws-blocks/blocks` | Type-safe APIs wired to the frontend automatically | -| `KVStore` | `@aws-blocks/blocks` | Simple key-value get/put/delete (prefs, flags, caches) | -| `DistributedTable` | `@aws-blocks/blocks` | Structured data with indexes and queries — **default for most data** | -| `DistributedDatabase` | `@aws-blocks/blocks` | Serverless SQL (Aurora DSQL) — zero-ops, scales to zero | -| `Database` | `@aws-blocks/blocks` | Full PostgreSQL (Aurora) — FKs, RLS, triggers, or an existing DB | -| `AuthBasic` | `@aws-blocks/blocks` | Username/password auth for prototypes, internal tools, MVPs | -| `AuthCognito` | `@aws-blocks/blocks` | Cognito User Pools — MFA, groups, hosted identity | -| `AuthOIDC` | `@aws-blocks/blocks` | Sign-in gated by an external OIDC identity provider | -| `Realtime` | `@aws-blocks/blocks` | Push to browsers — chat, presence, live dashboards | -| `AsyncJob` | `@aws-blocks/blocks` | Fire-and-forget background work (emails, uploads, reports) | -| `CronJob` | `@aws-blocks/blocks` | Scheduled / recurring tasks | -| `FileBucket` | `@aws-blocks/blocks` | File storage — uploads, downloads, presigned URLs | -| `AppSetting` | `@aws-blocks/blocks` | A single config value or secret (flags, API keys) | -| `KnowledgeBase` | `@aws-blocks/blocks` | Semantic document retrieval / RAG (Bedrock + S3 Vectors) | -| `Agent` | `@aws-blocks/blocks` | AI agent — tool use, streaming, conversation persistence | -| `EmailClient` | `@aws-blocks/blocks` | Transactional email (SES) | -| `Logger` / `Metrics` / `Tracer` / `Dashboard` | `@aws-blocks/blocks` | Observability — structured logs, metrics, traces, CloudWatch dashboard | -| `Hosting` | `@aws-blocks/blocks` | Deploy a frontend (SPA / static / Next.js SSR) on CloudFront + S3 | - -> **Not sure which data block?** Start with `DistributedTable` (DynamoDB). Reach for SQL only when you need joins across records, many-dimensional filtering, large transactions, or an existing Postgres database — `DistributedDatabase` for serverless Postgres, `Database` for full Aurora Postgres (FKs, RLS, triggers; carries idle cost / cold starts the other two don't). The full rationale is in `docs/README.md`. - -### Per-block documentation - -Every Building Block ships its full docs under `docs//` in this package. Read them in this order: - -- **`/README.md`** — overview + common usage. **Start here.** -- **`/API.md`** — full API reference. Read when you need exact method signatures or option types. -- **`/DESIGN.md`** — architecture & rationale. Read when extending a block, debugging unexpected behavior, or making a non-trivial design decision. -- **block-specific guides** (e.g. `CUSTOMIZING-AUTH-UI.md`) — read when doing that specific task. - -The catalog + decision tree in `docs/README.md` help you pick a block in the first place. +Start from what you need: + +- **Store data** + - Simple key → value (caches, flags, user prefs) → `KVStore` ([bb-kv-store](./docs/bb-kv-store/README.md)) + - Structured records with indexes and queries → `DistributedTable` ([bb-distributed-table](./docs/bb-distributed-table/README.md)) — **default for most data** + - Relational / SQL (joins, transactions) → see [Choosing a data block](#choosing-a-data-block) below + - Files, blobs, uploads, static assets → `FileBucket` ([bb-file-bucket](./docs/bb-file-bucket/README.md)) + - A single config value or secret → `AppSetting` ([bb-app-setting](./docs/bb-app-setting/README.md)) +- **Authenticate users** + - Username/password, prototypes/MVPs → `AuthBasic` ([bb-auth-basic](./docs/bb-auth-basic/README.md)) + - Cognito user pools, MFA, groups → `AuthCognito` ([bb-auth-cognito](./docs/bb-auth-cognito/README.md)) + - External identity provider (OIDC) → `AuthOIDC` ([bb-auth-oidc](./docs/bb-auth-oidc/README.md)) +- **Run work outside the request/response** + - Fire-and-forget background jobs → `AsyncJob` ([bb-async-job](./docs/bb-async-job/README.md)) + - Scheduled / recurring tasks → `CronJob` ([bb-cron-job](./docs/bb-cron-job/README.md)) +- **Push live updates to browsers** (chat, presence, dashboards) → `Realtime` ([bb-realtime](./docs/bb-realtime/README.md)) +- **Build AI features** + - Agent with tool use + conversation → `Agent` ([bb-agent](./docs/bb-agent/README.md)) + - Semantic document retrieval (RAG) → `KnowledgeBase` ([bb-knowledge-base](./docs/bb-knowledge-base/README.md)) +- **Send transactional email** → `EmailClient` ([bb-email-client](./docs/bb-email-client/README.md)) +- **Observe and operate** + - Structured logs → `Logger` ([bb-logger](./docs/bb-logger/README.md)) + - Custom metrics → `Metrics` ([bb-metrics](./docs/bb-metrics/README.md)) + - Distributed traces → `Tracer` ([bb-tracer](./docs/bb-tracer/README.md)) + - Auto CloudWatch dashboard → `Dashboard` ([bb-dashboard](./docs/bb-dashboard/README.md)) + +### Choosing a data block + +Default to `DistributedTable` for your data models unless your domain specifically requires SQL engine capabilities. + +Reach for one of the SQL blocks when you need to filter or join results across more than one related record, filter models on many dimensions with no preset hierarchy, store large objects, require transactions, or otherwise need the flexibility or familiarity of SQL that NoSQL does not offer. + +If you need SQL, prefer `DistributedDatabase` for basic Postgres-compatible querying. Use `Database` specifically when you need a full (more expensive) Postgres implementation where the engine itself provides and enforces foreign keys, row level security, triggers, views, large transactions (more than 3,000 rows), or integration with an existing Postgres database. Note it carries an idle cost at minimum 0.5 ACU, or a cold start when scaling from zero, unlike the other two blocks. + +### Catalog + +One folder per Building Block under `docs//`: start with its `README.md`, then read `API.md` for exact signatures and `DESIGN.md` for architecture & rationale. The catalog below is generated — run `npm run sync-docs` after adding or removing a block. + + +| Block | What it does | Keywords | +|-------|--------------|----------| +| [auth-common](./docs/auth-common/README.md) | Shared interfaces and UI components for all AWS Blocks auth Building Blocks. | — | +| [bb-agent](./docs/bb-agent/README.md) | AI agent with streaming, tool calling, and conversation persistence. | — | +| [bb-app-setting](./docs/bb-app-setting/README.md) | A single application configuration value backed by SSM Parameter Store. | — | +| [bb-async-job](./docs/bb-async-job/README.md) | Background job processing backed by SQS and Lambda. | queue, job, background, async, worker, submit, batch, retry, SQS | +| [bb-auth-basic](./docs/bb-auth-basic/README.md) | Simple username/password authentication with JWT sessions, password policy, and optional code-confirmed signup and password reset. | — | +| [bb-auth-cognito](./docs/bb-auth-cognito/README.md) | Authentication backed by Amazon Cognito User Pools. | — | +| [bb-auth-oidc](./docs/bb-auth-oidc/README.md) | OIDC sign-in gate for AWS Blocks applications. | — | +| [bb-cron-job](./docs/bb-cron-job/README.md) | Scheduled task execution backed by EventBridge Scheduler and Lambda. | cron, schedule, timer, periodic, recurring, rate, EventBridge, background, interval | +| [bb-dashboard](./docs/bb-dashboard/README.md) | Auto-generated CloudWatch Dashboard for application observability. | — | +| [bb-data](./docs/bb-data/README.md) | Full PostgreSQL database — provisions Aurora Serverless v2 by default, or connects to an existing PostgreSQL database (Supabase, Neon, etc.) via `fromExisting()`. | — | +| [bb-distributed-data](./docs/bb-distributed-data/README.md) | Serverless SQL database backed by Amazon Aurora DSQL. | — | +| [bb-distributed-table](./docs/bb-distributed-table/README.md) | Structured data storage backed by DynamoDB with secondary indexes and rich query capabilities. | — | +| [bb-email-client](./docs/bb-email-client/README.md) | Transactional email sending via Amazon SES. | — | +| [bb-file-bucket](./docs/bb-file-bucket/README.md) | File storage backed by Amazon S3. | — | +| [bb-knowledge-base](./docs/bb-knowledge-base/README.md) | Semantic document retrieval backed by Amazon Bedrock Knowledge Bases. | — | +| [bb-kv-store](./docs/bb-kv-store/README.md) | Simple key-value storage backed by DynamoDB. | — | +| [bb-logger](./docs/bb-logger/README.md) | Structured logging with consistent JSON format, log levels, and contextual metadata. | — | +| [bb-metrics](./docs/bb-metrics/README.md) | Custom application metrics backed by Amazon CloudWatch (via Embedded Metric Format). | — | +| [bb-realtime](./docs/bb-realtime/README.md) | Real-time pub/sub messaging backed by API Gateway WebSocket + DynamoDB. | — | +| [bb-tracer](./docs/bb-tracer/README.md) | Distributed tracing backed by AWS X-Ray. | — | +| [core](./docs/core/README.md) | Core primitives for building full-stack applications with the AWS Blocks. | — | +| [hosting](./docs/hosting/README.md) | Low-level CDK L3 constructs for deploying web applications on AWS | — | +| [pipeline](./docs/pipeline/README.md) | CDK Pipelines-based CI/CD construct for AWS Blocks applications. | — | + ## Local development and deploying diff --git a/packages/blocks/package.json b/packages/blocks/package.json index 85966fe0..9923fceb 100644 --- a/packages/blocks/package.json +++ b/packages/blocks/package.json @@ -49,7 +49,7 @@ "./docs/*": "./docs/*" }, "scripts": { - "prebuild": "node ../../scripts/sync-block-docs.mjs", + "prebuild": "node ../../scripts/sync-block-docs.mjs --docs-only", "build": "tsc --build", "test": "node --test dist/conditional-exports.test.js dist/vendorize-map.test.js" }, diff --git a/scripts/sync-block-docs.mjs b/scripts/sync-block-docs.mjs index 6a7cddf6..84ebc37f 100644 --- a/scripts/sync-block-docs.mjs +++ b/scripts/sync-block-docs.mjs @@ -3,18 +3,33 @@ // SPDX-License-Identifier: Apache-2.0 /** - * Assembles `packages/blocks/docs/` from every Building Block's root markdown. - * Run at build/publish time (not customer-side). Produces: - * packages/blocks/docs/README.md — dev guide + decision tree + catalog - * packages/blocks/docs//README.md — per-block overview - * packages/blocks/docs//API.md — per-block API reference (when present) - * packages/blocks/docs//DESIGN.md — per-block design notes (when present) - * packages/blocks/docs//.md — any other block-specific doc (when present) + * Keeps the Building Block catalog and the shipped `docs/` artifact in sync with + * the per-block READMEs. Three modes: * - * Inclusion rule: every package under packages/ that has a README.md and is not - * in EXCLUDED. For each included package, mirror every root-level *.md EXCEPT the - * ones in SKIP_MARKDOWN (CHANGELOG.md) — so block-specific docs are picked up - * automatically without listing them here. + * --write (default; `npm run sync-docs`, run MANUALLY when adding/removing a block) + * 1. Regenerate ONLY the catalog table between the + * `` / `` markers in + * packages/blocks/README.md (everything else, incl. the static decision-tree + * prose, is preserved). + * 2. Generate the per-block docs folders under packages/blocks/docs// + * (mirror every root *.md except CHANGELOG.md). + * 3. Write packages/blocks/docs/README.md as a copy of the (now catalog-containing) + * packages/blocks/README.md. + * + * --check (CI / PR gate) + * Regenerate the catalog table in memory and compare it to what is committed + * between the markers in packages/blocks/README.md. Exit 1 with an actionable + * message if they differ or the markers are missing; exit 0 if in sync. Writes + * nothing. + * + * --docs-only (build/publish `prebuild` hook) + * Generate ONLY the gitignored docs/ artifact — per-block folders + docs/README.md + * (a verbatim copy of the committed packages/blocks/README.md). Never modifies the + * committed packages/blocks/README.md, so builds don't dirty a tracked file. + * + * Inclusion rule: every package under packages/ that has a README.md and is not in + * EXCLUDED. For each included package, mirror every root-level *.md EXCEPT the ones in + * SKIP_MARKDOWN (CHANGELOG.md) — so block-specific docs are picked up automatically. */ import { readFileSync, writeFileSync, mkdirSync, readdirSync, existsSync, rmSync } from 'node:fs'; @@ -24,86 +39,151 @@ import { fileURLToPath } from 'node:url'; const __dirname = dirname(fileURLToPath(import.meta.url)); const packagesDir = join(__dirname, '..', 'packages'); const outDir = join(packagesDir, 'blocks', 'docs'); -const devGuidePath = join(packagesDir, 'blocks', 'README.md'); +const readmePath = join(packagesDir, 'blocks', 'README.md'); const EXCLUDED = new Set(['blocks', 'data-common', 'foundations', 'create-blocks-app']); // Root-level markdown that should NOT be mirrored into the per-block doc folder. const SKIP_MARKDOWN = new Set(['CHANGELOG.md']); -const DECISION_TREE = `# AWS Blocks — Building Block Catalog - -Start from what you need: - -- **Store data** - - Simple key → value (caches, flags, user prefs) → \`KVStore\` ([bb-kv-store](./bb-kv-store/README.md)) - - Structured records with indexes and queries → \`DistributedTable\` ([bb-distributed-table](./bb-distributed-table/README.md)) — **default for most data** - - Relational / SQL (joins, transactions) → see [Choosing a data block](#choosing-a-data-block) below - - Files, blobs, uploads, static assets → \`FileBucket\` ([bb-file-bucket](./bb-file-bucket/README.md)) - - A single config value or secret → \`AppSetting\` ([bb-app-setting](./bb-app-setting/README.md)) -- **Authenticate users** - - Username/password, prototypes/MVPs → \`AuthBasic\` ([bb-auth-basic](./bb-auth-basic/README.md)) - - Cognito user pools, MFA, groups → \`AuthCognito\` ([bb-auth-cognito](./bb-auth-cognito/README.md)) - - External identity provider (OIDC) → \`AuthOIDC\` ([bb-auth-oidc](./bb-auth-oidc/README.md)) -- **Run work outside the request/response** - - Fire-and-forget background jobs → \`AsyncJob\` ([bb-async-job](./bb-async-job/README.md)) - - Scheduled / recurring tasks → \`CronJob\` ([bb-cron-job](./bb-cron-job/README.md)) -- **Push live updates to browsers** (chat, presence, dashboards) → \`Realtime\` ([bb-realtime](./bb-realtime/README.md)) -- **Build AI features** - - Agent with tool use + conversation → \`Agent\` ([bb-agent](./bb-agent/README.md)) - - Semantic document retrieval (RAG) → \`KnowledgeBase\` ([bb-knowledge-base](./bb-knowledge-base/README.md)) -- **Send transactional email** → \`EmailClient\` ([bb-email-client](./bb-email-client/README.md)) -- **Observe and operate** - - Structured logs → \`Logger\` ([bb-logger](./bb-logger/README.md)) - - Custom metrics → \`Metrics\` ([bb-metrics](./bb-metrics/README.md)) - - Distributed traces → \`Tracer\` ([bb-tracer](./bb-tracer/README.md)) - - Auto CloudWatch dashboard → \`Dashboard\` ([bb-dashboard](./bb-dashboard/README.md)) - -### Choosing a data block - -Default to \`DistributedTable\` for your data models unless your domain specifically requires SQL engine capabilities. - -Reach for one of the SQL blocks when you need to filter or join results across more than one related record, filter models on many dimensions with no preset hierarchy, store large objects, require transactions, or otherwise need the flexibility or familiarity of SQL that NoSQL does not offer. - -If you need SQL, prefer \`DistributedDatabase\` for basic Postgres-compatible querying. Use \`Database\` specifically when you need a full (more expensive) Postgres implementation where the engine itself provides and enforces foreign keys, row level security, triggers, views, large transactions (more than 3,000 rows), or integration with an existing Postgres database. Note it carries an idle cost at minimum 0.5 ACU, or a cold start when scaling from zero, unlike the other two blocks.`; - -// Clean and recreate -rmSync(outDir, { recursive: true, force: true }); -mkdirSync(outDir, { recursive: true }); - -// Gather all @aws-blocks packages with READMEs -const packages = readdirSync(packagesDir).filter( - (name) => !name.startsWith('.') && !EXCLUDED.has(name) && existsSync(join(packagesDir, name, 'README.md')), -); - -const catalog = []; - -for (const pkg of packages) { - const pkgDir = join(packagesDir, pkg); - const blockOutDir = join(outDir, pkg); - mkdirSync(blockOutDir, { recursive: true }); - - // Mirror every root-level markdown file (README.md, API.md, DESIGN.md, and any - // block-specific docs) except the skipped ones, so new docs ship automatically. - const mdFiles = readdirSync(pkgDir, { withFileTypes: true }).filter( - (entry) => entry.isFile() && entry.name.endsWith('.md') && !SKIP_MARKDOWN.has(entry.name), - ); - for (const entry of mdFiles) { - writeFileSync(join(blockOutDir, entry.name), readFileSync(join(pkgDir, entry.name), 'utf-8')); +const BEGIN_MARKER = ''; +const END_MARKER = ''; + +const SYNC_HINT = 'Run `npm run sync-docs` and commit the result.'; + +const mode = process.argv.includes('--check') + ? 'check' + : process.argv.includes('--docs-only') + ? 'docs-only' + : 'write'; + +const packages = getPackages(); +const catalog = buildCatalog(packages); +const table = renderCatalogTable(catalog); + +if (mode === 'check') { + runCheck(); +} else { + runGenerate(mode); +} + +// ─── Modes ─────────────────────────────────────────────────────────────────── + +function runCheck() { + const readme = readFileSync(readmePath, 'utf-8'); + const current = extractBetweenMarkers(readme); + + if (current === null) { + process.stderr.write( + `❌ Building Block catalog markers (${BEGIN_MARKER} / ${END_MARKER}) not found in packages/blocks/README.md. ${SYNC_HINT}\n`, + ); + process.exit(1); + } + + if (current.trim() !== table.trim()) { + process.stderr.write( + `❌ Building Block catalog in packages/blocks/README.md is out of date. ${SYNC_HINT}\n`, + ); + process.exit(1); + } + + console.log('✅ Building Block catalog in packages/blocks/README.md is up to date.'); + process.exit(0); +} + +function runGenerate(currentMode) { + const readme = readFileSync(readmePath, 'utf-8'); + + // --write injects the fresh catalog into the committed README; --docs-only must + // never touch the committed README, so it copies it verbatim into docs/. + let docsReadme = readme; + if (currentMode === 'write') { + const updated = injectCatalog(readme, table); + if (updated !== readme) writeFileSync(readmePath, updated); + docsReadme = updated; } - const readme = readFileSync(join(pkgDir, 'README.md'), 'utf-8'); - catalog.push({ pkg, blurb: extractBlurb(readme), keywords: extractKeywords(readme) }); + generatePerBlockDocs(); + writeFileSync(join(outDir, 'README.md'), docsReadme); + + const where = + currentMode === 'write' + ? 'packages/blocks/README.md catalog + packages/blocks/docs/' + : 'packages/blocks/docs/'; + console.log(`Synced ${catalog.length} block docs → ${where}`); } -catalog.sort((a, b) => a.pkg.localeCompare(b.pkg)); +// ─── Catalog ───────────────────────────────────────────────────────────────── -const devGuide = readFileSync(devGuidePath, 'utf-8'); -writeFileSync(join(outDir, 'README.md'), renderReadme(devGuide, catalog)); +function getPackages() { + return readdirSync(packagesDir).filter( + (name) => !name.startsWith('.') && !EXCLUDED.has(name) && existsSync(join(packagesDir, name, 'README.md')), + ); +} + +function buildCatalog(pkgs) { + const entries = pkgs.map((pkg) => { + const readme = readFileSync(join(packagesDir, pkg, 'README.md'), 'utf-8'); + return { pkg, blurb: extractBlurb(readme), keywords: extractKeywords(readme) }; + }); + entries.sort((a, b) => a.pkg.localeCompare(b.pkg)); + return entries; +} + +function renderCatalogTable(entries) { + const rows = entries.map( + (e) => `| [${e.pkg}](./docs/${e.pkg}/README.md) | ${e.blurb || '—'} | ${e.keywords || '—'} |`, + ); + return ['| Block | What it does | Keywords |', '|-------|--------------|----------|', ...rows].join('\n'); +} + +// ─── docs/ artifact ────────────────────────────────────────────────────────── + +function generatePerBlockDocs() { + // Clean and recreate so removed blocks/files don't linger. + rmSync(outDir, { recursive: true, force: true }); + mkdirSync(outDir, { recursive: true }); + + for (const pkg of packages) { + const pkgDir = join(packagesDir, pkg); + const blockOutDir = join(outDir, pkg); + mkdirSync(blockOutDir, { recursive: true }); + + // Mirror every root-level markdown file (README.md, API.md, DESIGN.md, and any + // block-specific docs) except the skipped ones, so new docs ship automatically. + const mdFiles = readdirSync(pkgDir, { withFileTypes: true }).filter( + (entry) => entry.isFile() && entry.name.endsWith('.md') && !SKIP_MARKDOWN.has(entry.name), + ); + for (const entry of mdFiles) { + writeFileSync(join(blockOutDir, entry.name), readFileSync(join(pkgDir, entry.name), 'utf-8')); + } + } +} -console.log(`Synced ${catalog.length} block docs → packages/blocks/docs/`); +// ─── Marker helpers ────────────────────────────────────────────────────────── -// ─── Helpers ───────────────────────────────────────────────────────────────── +function injectCatalog(readme, catalogTable) { + const begin = readme.indexOf(BEGIN_MARKER); + const end = readme.indexOf(END_MARKER); + if (begin === -1 || end === -1 || end < begin) { + throw new Error( + `Building Block catalog markers (${BEGIN_MARKER} / ${END_MARKER}) not found in packages/blocks/README.md. ` + + 'Add them where the catalog table should live, then re-run.', + ); + } + const before = readme.slice(0, begin + BEGIN_MARKER.length); + const after = readme.slice(end); + return `${before}\n${catalogTable}\n${after}`; +} + +function extractBetweenMarkers(readme) { + const begin = readme.indexOf(BEGIN_MARKER); + const end = readme.indexOf(END_MARKER); + if (begin === -1 || end === -1 || end < begin) return null; + return readme.slice(begin + BEGIN_MARKER.length, end); +} + +// ─── README parsing ────────────────────────────────────────────────────────── function extractBlurb(content) { const lines = content.split('\n'); @@ -123,26 +203,3 @@ function extractKeywords(content) { const match = content.match(/\*\*Keywords?:\*\*\s*(.+)/i); return match ? match[1].trim() : ''; } - -function renderReadme(devGuide, catalog) { - // docs/README.md is generated — the authored dev guide (packages/blocks/README.md) - // followed by the catalog + decision tree. Edit those sources, not docs/README.md. - return [devGuide.trimEnd(), '', '', renderCatalog(catalog), ''].join('\n'); -} - -function renderCatalog(catalog) { - const rows = catalog.map( - (e) => `| [${e.pkg}](./${e.pkg}/README.md) | ${e.blurb || '—'} | ${e.keywords || '—'} |`, - ); - return [ - DECISION_TREE, - '', - '## Catalog', - '', - 'One folder per Building Block under `docs//`: start with its `README.md`, then read `API.md` for exact signatures and `DESIGN.md` for architecture & rationale.', - '', - '| Block | What it does | Keywords |', - '|-------|--------------|----------|', - ...rows, - ].join('\n'); -} From 8aeefc724e210a529178b1786e7251a1800ce825 Mon Sep 17 00:00:00 2001 From: Simone Zhang Date: Sun, 28 Jun 2026 16:38:11 +0000 Subject: [PATCH 03/12] docs: add Security Considerations to README, require.resolve locator note, changeset --- .changeset/docs-restructure-require-resolve.md | 16 ++++++++++++++++ packages/blocks/README.md | 15 +++++++++++++++ packages/create-blocks-app/resources/AGENTS.md | 2 ++ 3 files changed, 33 insertions(+) create mode 100644 .changeset/docs-restructure-require-resolve.md diff --git a/.changeset/docs-restructure-require-resolve.md b/.changeset/docs-restructure-require-resolve.md new file mode 100644 index 00000000..398cd798 --- /dev/null +++ b/.changeset/docs-restructure-require-resolve.md @@ -0,0 +1,16 @@ +--- +"@aws-blocks/blocks": patch +"@aws-blocks/create-blocks-app": patch +--- + +docs: per-block docs folders + committed BB catalog with CI sync check; CLAUDE/agents docs resolved via require.resolve + +`@aws-blocks/blocks` now ships one docs folder per Building Block under `docs//` +(`README.md` / `API.md` / `DESIGN.md`), plus a committed, marker-delimited Building Block +catalog in the package README that a `sync-docs --check` CI gate keeps in sync. The README's +catalog section and the scaffolded `AGENTS.md` (`@aws-blocks/create-blocks-app`) now direct +tools and agents to locate docs programmatically via +`require.resolve('@aws-blocks/blocks/docs//README.md')` (and +`require.resolve('@aws-blocks/blocks/docs/README.md')` for the catalog) rather than assuming a +`node_modules/` path or following the human-facing relative links. Also adds a Security +Considerations section to the package README. diff --git a/packages/blocks/README.md b/packages/blocks/README.md index 2dd63943..2562b647 100644 --- a/packages/blocks/README.md +++ b/packages/blocks/README.md @@ -147,6 +147,8 @@ If you need SQL, prefer `DistributedDatabase` for basic Postgres-compatible quer One folder per Building Block under `docs//`: start with its `README.md`, then read `API.md` for exact signatures and `DESIGN.md` for architecture & rationale. The catalog below is generated — run `npm run sync-docs` after adding or removing a block. +> **Tools & agents:** locate a doc programmatically with `require.resolve('@aws-blocks/blocks/docs//README.md')` (or `require.resolve('@aws-blocks/blocks/docs/README.md')` for this catalog) rather than assuming a `node_modules/` path. The relative links below are for humans browsing on GitHub/npm. + | Block | What it does | Keywords | |-------|--------------|----------| @@ -221,6 +223,19 @@ Run with `npm run test:e2e`. Write the test first, iterate against mocks until i - **`Database` when `DistributedTable` would do** — Aurora costs more and has cold starts; reach for SQL only when you need it. - **Curling REST-style paths** — there is no `GET /api/getData`. All calls are JSON-RPC to a single `POST /aws-blocks/api`; use the typed import instead. +## Security Considerations + +- Use `await auth.requireAuth(context)` in every method that shouldn't be public — ApiNamespace methods are **unauthenticated by default** +- Use `new AppSetting(scope, id, { secret: true })` for API keys and credentials — never hardcode or use `.env` files +- Always attach a schema to KVStore/AppSetting that accepts user data — the RPC layer validates structure but not business logic +- Do not add broad `*` IAM policies — each Building Block already grants least-privilege scoped to its own resources +- Never change `blockPublicAccess` on FileBucket — serve public files through CloudFront instead +- Configure `CORS_ALLOWED_ORIGINS` explicitly for production — avoid wildcards +- For cross-domain deployments, pass `crossDomain: true` to auth constructors (enables `SameSite=None; Secure; Partitioned`) +- Enable `monitoring: { enabled: true, snsTopicArn: '...' }` on Hosting for production alerts +- Add WAF and API Gateway throttling via CDK for public-facing apps — not included by default +- Logger provides serialization safety (circular refs, type coercion) but does NOT redact sensitive content — never pass raw credentials, tokens, or secrets to Logger methods; sanitize context objects before logging + ## Reference - **Per-block documentation:** `docs//README.md` (overview), `docs//API.md` (full API reference), `docs//DESIGN.md` (architecture & rationale) — e.g. `docs/bb-distributed-table/README.md`. The catalog + decision tree live in `docs/README.md`. diff --git a/packages/create-blocks-app/resources/AGENTS.md b/packages/create-blocks-app/resources/AGENTS.md index b1d15b32..9d8dcde5 100644 --- a/packages/create-blocks-app/resources/AGENTS.md +++ b/packages/create-blocks-app/resources/AGENTS.md @@ -25,6 +25,8 @@ node -e "console.log(require.resolve('@aws-blocks/blocks/docs/bb-distributed-tab Swap `bb-distributed-table` for the block you need; the catalog in `docs/README.md` lists every block. +> **Resolve, don't follow links.** The relative markdown links inside the docs (e.g. `./docs//README.md`) are for humans browsing on GitHub/npm — always locate a doc with `require.resolve('@aws-blocks/blocks/docs//README.md')` rather than treating those links as filesystem paths. + **Fallback — if resolution fails**, read the files directly: - Dev guide + catalog + decision tree: `node_modules/@aws-blocks/blocks/docs/README.md` From 2bcd05efe40403f48556554b9200d429a2aad7cf Mon Sep 17 00:00:00 2001 From: Simone Zhang Date: Sun, 28 Jun 2026 16:41:33 +0000 Subject: [PATCH 04/12] docs: note AWS credentials + least-privilege for sandbox/deploy --- packages/blocks/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/blocks/README.md b/packages/blocks/README.md index 2562b647..e8ef4bb0 100644 --- a/packages/blocks/README.md +++ b/packages/blocks/README.md @@ -186,6 +186,8 @@ One folder per Building Block under `docs//`: start with its `README.md`, | Data | persists to `.bb-data/` (delete to reset) | lives in AWS | | Use for | rapid iteration, tests | pre-production validation against real services | +> **Deploying needs AWS credentials.** `npm run dev` is fully local (no creds). `npm run sandbox` and `npm run deploy` provision real AWS resources, so configure credentials first — e.g. `aws configure sso` + `aws sso login`, or `aws configure` (verify with `aws sts get-caller-identity`). Use **least-privilege** credentials scoped to the services your blocks deploy — not broad `Administrator` access. + `npm run deploy` does a full production deploy; `npm run sandbox:destroy` tears the sandbox down. The same backend code runs in all three — blocks switch implementations automatically. ## Testing From a1e92ab3e3fe05ef430f764f6627f43c603b4b24 Mon Sep 17 00:00:00 2001 From: Simone Zhang Date: Sun, 28 Jun 2026 17:11:43 +0000 Subject: [PATCH 05/12] refactor(docs): split catalog (sync-catalog) from docs-artifact (gen-block-docs); --write is catalog-only --- .github/workflows/block-catalog-check.yml | 2 +- package.json | 4 +- packages/blocks/package.json | 2 +- scripts/gen-block-docs.mjs | 79 +++++++++++++++ .../{sync-block-docs.mjs => sync-catalog.mjs} | 99 +++++-------------- 5 files changed, 107 insertions(+), 79 deletions(-) create mode 100644 scripts/gen-block-docs.mjs rename scripts/{sync-block-docs.mjs => sync-catalog.mjs} (56%) diff --git a/.github/workflows/block-catalog-check.yml b/.github/workflows/block-catalog-check.yml index 810fa6c4..90f2babc 100644 --- a/.github/workflows/block-catalog-check.yml +++ b/.github/workflows/block-catalog-check.yml @@ -19,4 +19,4 @@ jobs: with: node-version-file: '.nvmrc' - name: Verify Building Block catalog is in sync - run: node scripts/sync-block-docs.mjs --check + run: node scripts/sync-catalog.mjs --check diff --git a/package.json b/package.json index 56c9391a..908cba9e 100644 --- a/package.json +++ b/package.json @@ -79,8 +79,8 @@ "create-demo": "node packages/create-blocks-app/dist/index.js --template demo", "update-template": "tsx scripts/update-template-from-demo.ts", "build:tech-design-pdf": "node scripts/build-tech-design-pdf.mjs", - "sync-docs": "node scripts/sync-block-docs.mjs --write", - "sync-docs:check": "node scripts/sync-block-docs.mjs --check", + "sync-docs": "node scripts/sync-catalog.mjs --write", + "sync-docs:check": "node scripts/sync-catalog.mjs --check", "review-pr": "bash scripts/review-pr.sh", "publish:dry-run": "tsx scripts/publish/publish.ts", "publish:npm": "tsx scripts/publish/publish-npm.ts", diff --git a/packages/blocks/package.json b/packages/blocks/package.json index 9923fceb..ec344773 100644 --- a/packages/blocks/package.json +++ b/packages/blocks/package.json @@ -49,7 +49,7 @@ "./docs/*": "./docs/*" }, "scripts": { - "prebuild": "node ../../scripts/sync-block-docs.mjs --docs-only", + "prebuild": "node ../../scripts/gen-block-docs.mjs", "build": "tsc --build", "test": "node --test dist/conditional-exports.test.js dist/vendorize-map.test.js" }, diff --git a/scripts/gen-block-docs.mjs b/scripts/gen-block-docs.mjs new file mode 100644 index 00000000..61e4ad1b --- /dev/null +++ b/scripts/gen-block-docs.mjs @@ -0,0 +1,79 @@ +#!/usr/bin/env node +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/** + * Generates the gitignored, shipped `docs/` artifact for @aws-blocks/blocks. Runs + * as the packages/blocks `prebuild` hook so the published package always carries + * fresh docs without dirtying any tracked file. + * + * It produces two things under packages/blocks/docs/: + * 1. Per-block folders docs// — mirror every root-level *.md of each + * included package EXCEPT the ones in SKIP_MARKDOWN (CHANGELOG.md), so + * block-specific docs (README.md, API.md, DESIGN.md, ...) ship automatically. + * 2. docs/README.md — a verbatim copy of the committed packages/blocks/README.md. + * + * This script NEVER modifies packages/blocks/README.md. The committed catalog + * table inside that README is managed separately by scripts/sync-catalog.mjs + * (`npm run sync-docs`); run that and commit before building if you added or + * removed a block. + * + * No flag is required — the default run generates docs/. `--docs-only` is accepted + * as a harmless alias for the same behavior. + * + * Inclusion rule: every package under packages/ that has a README.md and is not in + * EXCLUDED. (The package-discovery logic is intentionally duplicated from + * sync-catalog.mjs — the two scripts are kept independent on purpose.) + */ + +import { readFileSync, writeFileSync, mkdirSync, readdirSync, existsSync, rmSync } from 'node:fs'; +import { join, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const packagesDir = join(__dirname, '..', 'packages'); +const outDir = join(packagesDir, 'blocks', 'docs'); +const readmePath = join(packagesDir, 'blocks', 'README.md'); + +const EXCLUDED = new Set(['blocks', 'data-common', 'foundations', 'create-blocks-app']); + +// Root-level markdown that should NOT be mirrored into the per-block doc folder. +const SKIP_MARKDOWN = new Set(['CHANGELOG.md']); + +const packages = getPackages(); + +generatePerBlockDocs(); +writeFileSync(join(outDir, 'README.md'), readFileSync(readmePath, 'utf-8')); + +console.log(`Generated ${packages.length} block docs → packages/blocks/docs/`); + +// ─── docs/ artifact ────────────────────────────────────────────────────────── + +function generatePerBlockDocs() { + // Clean and recreate so removed blocks/files don't linger. + rmSync(outDir, { recursive: true, force: true }); + mkdirSync(outDir, { recursive: true }); + + for (const pkg of packages) { + const pkgDir = join(packagesDir, pkg); + const blockOutDir = join(outDir, pkg); + mkdirSync(blockOutDir, { recursive: true }); + + // Mirror every root-level markdown file (README.md, API.md, DESIGN.md, and any + // block-specific docs) except the skipped ones, so new docs ship automatically. + const mdFiles = readdirSync(pkgDir, { withFileTypes: true }).filter( + (entry) => entry.isFile() && entry.name.endsWith('.md') && !SKIP_MARKDOWN.has(entry.name), + ); + for (const entry of mdFiles) { + writeFileSync(join(blockOutDir, entry.name), readFileSync(join(pkgDir, entry.name), 'utf-8')); + } + } +} + +// ─── Package discovery (duplicated from sync-catalog.mjs) ────────────────────── + +function getPackages() { + return readdirSync(packagesDir).filter( + (name) => !name.startsWith('.') && !EXCLUDED.has(name) && existsSync(join(packagesDir, name, 'README.md')), + ); +} diff --git a/scripts/sync-block-docs.mjs b/scripts/sync-catalog.mjs similarity index 56% rename from scripts/sync-block-docs.mjs rename to scripts/sync-catalog.mjs index 84ebc37f..df50f370 100644 --- a/scripts/sync-block-docs.mjs +++ b/scripts/sync-catalog.mjs @@ -3,59 +3,46 @@ // SPDX-License-Identifier: Apache-2.0 /** - * Keeps the Building Block catalog and the shipped `docs/` artifact in sync with - * the per-block READMEs. Three modes: + * Keeps the committed Building Block *catalog table* in packages/blocks/README.md + * in sync with the per-block READMEs. This script ONLY manages the table between + * the `` / `` markers — it + * leaves the rest of the README (the static decision-tree prose, etc.) untouched + * and does NOT generate the shipped `docs/` artifact. That artifact (per-block + * docs// folders + docs/README.md copy) is produced by + * scripts/gen-block-docs.mjs, run as the packages/blocks `prebuild` hook. * - * --write (default; `npm run sync-docs`, run MANUALLY when adding/removing a block) - * 1. Regenerate ONLY the catalog table between the - * `` / `` markers in - * packages/blocks/README.md (everything else, incl. the static decision-tree - * prose, is preserved). - * 2. Generate the per-block docs folders under packages/blocks/docs// - * (mirror every root *.md except CHANGELOG.md). - * 3. Write packages/blocks/docs/README.md as a copy of the (now catalog-containing) - * packages/blocks/README.md. + * Two modes: * - * --check (CI / PR gate) - * Regenerate the catalog table in memory and compare it to what is committed - * between the markers in packages/blocks/README.md. Exit 1 with an actionable - * message if they differ or the markers are missing; exit 0 if in sync. Writes - * nothing. + * --write (default; `npm run sync-docs`, run MANUALLY when adding/removing a block) + * Render the catalog table from the discovered blocks and inject it between + * the markers in packages/blocks/README.md. Nothing else in the README changes. * - * --docs-only (build/publish `prebuild` hook) - * Generate ONLY the gitignored docs/ artifact — per-block folders + docs/README.md - * (a verbatim copy of the committed packages/blocks/README.md). Never modifies the - * committed packages/blocks/README.md, so builds don't dirty a tracked file. + * --check (CI / PR gate; `npm run sync-docs:check`) + * Render the catalog table in memory and compare it to what is committed + * between the markers. Exit 1 with an actionable message if they differ or the + * markers are missing; exit 0 if in sync. Writes nothing. * * Inclusion rule: every package under packages/ that has a README.md and is not in - * EXCLUDED. For each included package, mirror every root-level *.md EXCEPT the ones in - * SKIP_MARKDOWN (CHANGELOG.md) — so block-specific docs are picked up automatically. + * EXCLUDED. (The package-discovery logic is intentionally duplicated in + * gen-block-docs.mjs — the two scripts are kept independent on purpose.) */ -import { readFileSync, writeFileSync, mkdirSync, readdirSync, existsSync, rmSync } from 'node:fs'; +import { readFileSync, writeFileSync, readdirSync, existsSync } from 'node:fs'; import { join, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; const __dirname = dirname(fileURLToPath(import.meta.url)); const packagesDir = join(__dirname, '..', 'packages'); -const outDir = join(packagesDir, 'blocks', 'docs'); const readmePath = join(packagesDir, 'blocks', 'README.md'); const EXCLUDED = new Set(['blocks', 'data-common', 'foundations', 'create-blocks-app']); -// Root-level markdown that should NOT be mirrored into the per-block doc folder. -const SKIP_MARKDOWN = new Set(['CHANGELOG.md']); - const BEGIN_MARKER = ''; const END_MARKER = ''; const SYNC_HINT = 'Run `npm run sync-docs` and commit the result.'; -const mode = process.argv.includes('--check') - ? 'check' - : process.argv.includes('--docs-only') - ? 'docs-only' - : 'write'; +const mode = process.argv.includes('--check') ? 'check' : 'write'; const packages = getPackages(); const catalog = buildCatalog(packages); @@ -64,7 +51,7 @@ const table = renderCatalogTable(catalog); if (mode === 'check') { runCheck(); } else { - runGenerate(mode); + runWrite(); } // ─── Modes ─────────────────────────────────────────────────────────────────── @@ -91,26 +78,11 @@ function runCheck() { process.exit(0); } -function runGenerate(currentMode) { +function runWrite() { const readme = readFileSync(readmePath, 'utf-8'); - - // --write injects the fresh catalog into the committed README; --docs-only must - // never touch the committed README, so it copies it verbatim into docs/. - let docsReadme = readme; - if (currentMode === 'write') { - const updated = injectCatalog(readme, table); - if (updated !== readme) writeFileSync(readmePath, updated); - docsReadme = updated; - } - - generatePerBlockDocs(); - writeFileSync(join(outDir, 'README.md'), docsReadme); - - const where = - currentMode === 'write' - ? 'packages/blocks/README.md catalog + packages/blocks/docs/' - : 'packages/blocks/docs/'; - console.log(`Synced ${catalog.length} block docs → ${where}`); + const updated = injectCatalog(readme, table); + if (updated !== readme) writeFileSync(readmePath, updated); + console.log(`Synced ${catalog.length} blocks → packages/blocks/README.md catalog`); } // ─── Catalog ───────────────────────────────────────────────────────────────── @@ -137,29 +109,6 @@ function renderCatalogTable(entries) { return ['| Block | What it does | Keywords |', '|-------|--------------|----------|', ...rows].join('\n'); } -// ─── docs/ artifact ────────────────────────────────────────────────────────── - -function generatePerBlockDocs() { - // Clean and recreate so removed blocks/files don't linger. - rmSync(outDir, { recursive: true, force: true }); - mkdirSync(outDir, { recursive: true }); - - for (const pkg of packages) { - const pkgDir = join(packagesDir, pkg); - const blockOutDir = join(outDir, pkg); - mkdirSync(blockOutDir, { recursive: true }); - - // Mirror every root-level markdown file (README.md, API.md, DESIGN.md, and any - // block-specific docs) except the skipped ones, so new docs ship automatically. - const mdFiles = readdirSync(pkgDir, { withFileTypes: true }).filter( - (entry) => entry.isFile() && entry.name.endsWith('.md') && !SKIP_MARKDOWN.has(entry.name), - ); - for (const entry of mdFiles) { - writeFileSync(join(blockOutDir, entry.name), readFileSync(join(pkgDir, entry.name), 'utf-8')); - } - } -} - // ─── Marker helpers ────────────────────────────────────────────────────────── function injectCatalog(readme, catalogTable) { From cc82f2db35dfcfe7d1a04afb090297b596ed79cf Mon Sep 17 00:00:00 2001 From: Simone Zhang Date: Sun, 28 Jun 2026 21:58:18 +0000 Subject: [PATCH 06/12] docs: drop per-block links from catalog/prose; locate docs via resolve (no curl/broken-link confusion) --- packages/blocks/README.md | 90 ++++++++++--------- .../create-blocks-app/resources/AGENTS.md | 29 +----- scripts/sync-catalog.mjs | 4 +- 3 files changed, 50 insertions(+), 73 deletions(-) diff --git a/packages/blocks/README.md b/packages/blocks/README.md index e8ef4bb0..161e188d 100644 --- a/packages/blocks/README.md +++ b/packages/blocks/README.md @@ -112,28 +112,28 @@ onAuthChange(authApi, (user) => { Start from what you need: - **Store data** - - Simple key → value (caches, flags, user prefs) → `KVStore` ([bb-kv-store](./docs/bb-kv-store/README.md)) - - Structured records with indexes and queries → `DistributedTable` ([bb-distributed-table](./docs/bb-distributed-table/README.md)) — **default for most data** + - Simple key → value (caches, flags, user prefs) → `KVStore` (bb-kv-store) + - Structured records with indexes and queries → `DistributedTable` (bb-distributed-table) — **default for most data** - Relational / SQL (joins, transactions) → see [Choosing a data block](#choosing-a-data-block) below - - Files, blobs, uploads, static assets → `FileBucket` ([bb-file-bucket](./docs/bb-file-bucket/README.md)) - - A single config value or secret → `AppSetting` ([bb-app-setting](./docs/bb-app-setting/README.md)) + - Files, blobs, uploads, static assets → `FileBucket` (bb-file-bucket) + - A single config value or secret → `AppSetting` (bb-app-setting) - **Authenticate users** - - Username/password, prototypes/MVPs → `AuthBasic` ([bb-auth-basic](./docs/bb-auth-basic/README.md)) - - Cognito user pools, MFA, groups → `AuthCognito` ([bb-auth-cognito](./docs/bb-auth-cognito/README.md)) - - External identity provider (OIDC) → `AuthOIDC` ([bb-auth-oidc](./docs/bb-auth-oidc/README.md)) + - Username/password, prototypes/MVPs → `AuthBasic` (bb-auth-basic) + - Cognito user pools, MFA, groups → `AuthCognito` (bb-auth-cognito) + - External identity provider (OIDC) → `AuthOIDC` (bb-auth-oidc) - **Run work outside the request/response** - - Fire-and-forget background jobs → `AsyncJob` ([bb-async-job](./docs/bb-async-job/README.md)) - - Scheduled / recurring tasks → `CronJob` ([bb-cron-job](./docs/bb-cron-job/README.md)) -- **Push live updates to browsers** (chat, presence, dashboards) → `Realtime` ([bb-realtime](./docs/bb-realtime/README.md)) + - Fire-and-forget background jobs → `AsyncJob` (bb-async-job) + - Scheduled / recurring tasks → `CronJob` (bb-cron-job) +- **Push live updates to browsers** (chat, presence, dashboards) → `Realtime` (bb-realtime) - **Build AI features** - - Agent with tool use + conversation → `Agent` ([bb-agent](./docs/bb-agent/README.md)) - - Semantic document retrieval (RAG) → `KnowledgeBase` ([bb-knowledge-base](./docs/bb-knowledge-base/README.md)) -- **Send transactional email** → `EmailClient` ([bb-email-client](./docs/bb-email-client/README.md)) + - Agent with tool use + conversation → `Agent` (bb-agent) + - Semantic document retrieval (RAG) → `KnowledgeBase` (bb-knowledge-base) +- **Send transactional email** → `EmailClient` (bb-email-client) - **Observe and operate** - - Structured logs → `Logger` ([bb-logger](./docs/bb-logger/README.md)) - - Custom metrics → `Metrics` ([bb-metrics](./docs/bb-metrics/README.md)) - - Distributed traces → `Tracer` ([bb-tracer](./docs/bb-tracer/README.md)) - - Auto CloudWatch dashboard → `Dashboard` ([bb-dashboard](./docs/bb-dashboard/README.md)) + - Structured logs → `Logger` (bb-logger) + - Custom metrics → `Metrics` (bb-metrics) + - Distributed traces → `Tracer` (bb-tracer) + - Auto CloudWatch dashboard → `Dashboard` (bb-dashboard) ### Choosing a data block @@ -143,38 +143,42 @@ Reach for one of the SQL blocks when you need to filter or join results across m If you need SQL, prefer `DistributedDatabase` for basic Postgres-compatible querying. Use `Database` specifically when you need a full (more expensive) Postgres implementation where the engine itself provides and enforces foreign keys, row level security, triggers, views, large transactions (more than 3,000 rows), or integration with an existing Postgres database. Note it carries an idle cost at minimum 0.5 ACU, or a cold start when scaling from zero, unlike the other two blocks. -### Catalog +## Building Block documentation -One folder per Building Block under `docs//`: start with its `README.md`, then read `API.md` for exact signatures and `DESIGN.md` for architecture & rationale. The catalog below is generated — run `npm run sync-docs` after adding or removing a block. +Every Building Block ships its full docs — `README.md`, `API.md`, and `DESIGN.md` — inside the `@aws-blocks/blocks` package under `docs//`. To read them, locate the bundled folder: -> **Tools & agents:** locate a doc programmatically with `require.resolve('@aws-blocks/blocks/docs//README.md')` (or `require.resolve('@aws-blocks/blocks/docs/README.md')` for this catalog) rather than assuming a `node_modules/` path. The relative links below are for humans browsing on GitHub/npm. +```bash +node -p "require('path').dirname(require.resolve('@aws-blocks/blocks/docs/README.md'))" +``` + +If resolution fails, fall back to `node_modules/@aws-blocks/blocks/docs`. That folder holds this guide (`README.md`) plus one subfolder per block; the catalog below lists every block. | Block | What it does | Keywords | |-------|--------------|----------| -| [auth-common](./docs/auth-common/README.md) | Shared interfaces and UI components for all AWS Blocks auth Building Blocks. | — | -| [bb-agent](./docs/bb-agent/README.md) | AI agent with streaming, tool calling, and conversation persistence. | — | -| [bb-app-setting](./docs/bb-app-setting/README.md) | A single application configuration value backed by SSM Parameter Store. | — | -| [bb-async-job](./docs/bb-async-job/README.md) | Background job processing backed by SQS and Lambda. | queue, job, background, async, worker, submit, batch, retry, SQS | -| [bb-auth-basic](./docs/bb-auth-basic/README.md) | Simple username/password authentication with JWT sessions, password policy, and optional code-confirmed signup and password reset. | — | -| [bb-auth-cognito](./docs/bb-auth-cognito/README.md) | Authentication backed by Amazon Cognito User Pools. | — | -| [bb-auth-oidc](./docs/bb-auth-oidc/README.md) | OIDC sign-in gate for AWS Blocks applications. | — | -| [bb-cron-job](./docs/bb-cron-job/README.md) | Scheduled task execution backed by EventBridge Scheduler and Lambda. | cron, schedule, timer, periodic, recurring, rate, EventBridge, background, interval | -| [bb-dashboard](./docs/bb-dashboard/README.md) | Auto-generated CloudWatch Dashboard for application observability. | — | -| [bb-data](./docs/bb-data/README.md) | Full PostgreSQL database — provisions Aurora Serverless v2 by default, or connects to an existing PostgreSQL database (Supabase, Neon, etc.) via `fromExisting()`. | — | -| [bb-distributed-data](./docs/bb-distributed-data/README.md) | Serverless SQL database backed by Amazon Aurora DSQL. | — | -| [bb-distributed-table](./docs/bb-distributed-table/README.md) | Structured data storage backed by DynamoDB with secondary indexes and rich query capabilities. | — | -| [bb-email-client](./docs/bb-email-client/README.md) | Transactional email sending via Amazon SES. | — | -| [bb-file-bucket](./docs/bb-file-bucket/README.md) | File storage backed by Amazon S3. | — | -| [bb-knowledge-base](./docs/bb-knowledge-base/README.md) | Semantic document retrieval backed by Amazon Bedrock Knowledge Bases. | — | -| [bb-kv-store](./docs/bb-kv-store/README.md) | Simple key-value storage backed by DynamoDB. | — | -| [bb-logger](./docs/bb-logger/README.md) | Structured logging with consistent JSON format, log levels, and contextual metadata. | — | -| [bb-metrics](./docs/bb-metrics/README.md) | Custom application metrics backed by Amazon CloudWatch (via Embedded Metric Format). | — | -| [bb-realtime](./docs/bb-realtime/README.md) | Real-time pub/sub messaging backed by API Gateway WebSocket + DynamoDB. | — | -| [bb-tracer](./docs/bb-tracer/README.md) | Distributed tracing backed by AWS X-Ray. | — | -| [core](./docs/core/README.md) | Core primitives for building full-stack applications with the AWS Blocks. | — | -| [hosting](./docs/hosting/README.md) | Low-level CDK L3 constructs for deploying web applications on AWS | — | -| [pipeline](./docs/pipeline/README.md) | CDK Pipelines-based CI/CD construct for AWS Blocks applications. | — | +| auth-common | Shared interfaces and UI components for all AWS Blocks auth Building Blocks. | — | +| bb-agent | AI agent with streaming, tool calling, and conversation persistence. | — | +| bb-app-setting | A single application configuration value backed by SSM Parameter Store. | — | +| bb-async-job | Background job processing backed by SQS and Lambda. | queue, job, background, async, worker, submit, batch, retry, SQS | +| bb-auth-basic | Simple username/password authentication with JWT sessions, password policy, and optional code-confirmed signup and password reset. | — | +| bb-auth-cognito | Authentication backed by Amazon Cognito User Pools. | — | +| bb-auth-oidc | OIDC sign-in gate for AWS Blocks applications. | — | +| bb-cron-job | Scheduled task execution backed by EventBridge Scheduler and Lambda. | cron, schedule, timer, periodic, recurring, rate, EventBridge, background, interval | +| bb-dashboard | Auto-generated CloudWatch Dashboard for application observability. | — | +| bb-data | Full PostgreSQL database — provisions Aurora Serverless v2 by default, or connects to an existing PostgreSQL database (Supabase, Neon, etc.) via `fromExisting()`. | — | +| bb-distributed-data | Serverless SQL database backed by Amazon Aurora DSQL. | — | +| bb-distributed-table | Structured data storage backed by DynamoDB with secondary indexes and rich query capabilities. | — | +| bb-email-client | Transactional email sending via Amazon SES. | — | +| bb-file-bucket | File storage backed by Amazon S3. | — | +| bb-knowledge-base | Semantic document retrieval backed by Amazon Bedrock Knowledge Bases. | — | +| bb-kv-store | Simple key-value storage backed by DynamoDB. | — | +| bb-logger | Structured logging with consistent JSON format, log levels, and contextual metadata. | — | +| bb-metrics | Custom application metrics backed by Amazon CloudWatch (via Embedded Metric Format). | — | +| bb-realtime | Real-time pub/sub messaging backed by API Gateway WebSocket + DynamoDB. | — | +| bb-tracer | Distributed tracing backed by AWS X-Ray. | — | +| core | Core primitives for building full-stack applications with the AWS Blocks. | — | +| hosting | Low-level CDK L3 constructs for deploying web applications on AWS | — | +| pipeline | CDK Pipelines-based CI/CD construct for AWS Blocks applications. | — | ## Local development and deploying diff --git a/packages/create-blocks-app/resources/AGENTS.md b/packages/create-blocks-app/resources/AGENTS.md index 9d8dcde5..6a21d6ef 100644 --- a/packages/create-blocks-app/resources/AGENTS.md +++ b/packages/create-blocks-app/resources/AGENTS.md @@ -5,32 +5,7 @@ - **Backend:** `aws-blocks/index.ts` — APIs, auth, data models - **Frontend:** `src/` — imports backend APIs via `import { api } from 'aws-blocks'` - **Tests:** `test/e2e.test.ts` — run with `npm run test:e2e` -- **Docs (dev guide + block catalog + decision tree, then per-block API/DESIGN):** bundled in `@aws-blocks/blocks` — see [Reading the Building Block docs](#reading-the-building-block-docs). - -## Reading the Building Block docs - -The `@aws-blocks/blocks` package ships its full documentation under `docs/`. Locate it by **resolution first** (version-correct, independent of where the package is installed), and fall back to a hard-coded `node_modules` path only if resolution fails. - -**Primary — resolve, then read.** The dev guide (architecture, workflow, best practices, common mistakes) plus the block catalog and decision tree all live in one file: - -```bash -node -e "console.log(require.resolve('@aws-blocks/blocks/docs/README.md'))" -``` - -Read the path it prints. For a specific block, resolve its docs the same way and read them in order — `README.md` (overview, start here), then `API.md` (exact method signatures / option types) and `DESIGN.md` (architecture & rationale) as needed: - -```bash -node -e "console.log(require.resolve('@aws-blocks/blocks/docs/bb-distributed-table/README.md'))" -``` - -Swap `bb-distributed-table` for the block you need; the catalog in `docs/README.md` lists every block. - -> **Resolve, don't follow links.** The relative markdown links inside the docs (e.g. `./docs//README.md`) are for humans browsing on GitHub/npm — always locate a doc with `require.resolve('@aws-blocks/blocks/docs//README.md')` rather than treating those links as filesystem paths. - -**Fallback — if resolution fails**, read the files directly: - -- Dev guide + catalog + decision tree: `node_modules/@aws-blocks/blocks/docs/README.md` -- Per-block: `node_modules/@aws-blocks/blocks/docs//{README,API,DESIGN}.md` +- **AWS Blocks docs** ship inside the `@aws-blocks/blocks` package. Find the docs folder once: `node -p "require('path').dirname(require.resolve('@aws-blocks/blocks/docs/README.md'))"` (fallback: `node_modules/@aws-blocks/blocks/docs`). Read everything relative to it: `README.md` (dev guide + catalog + decision tree — start here), then `/README.md`, `/API.md`, `/DESIGN.md`. ## Workflow @@ -42,7 +17,7 @@ Swap `bb-distributed-table` for the block you need; the catalog in `docs/README. ## Rules - **Use Building Blocks** for all persistence and cloud abstractions — never local files, in-memory arrays, or local databases. -- **Read block docs** before using a block — resolve `@aws-blocks/blocks/docs//README.md` (see [Reading the Building Block docs](#reading-the-building-block-docs)), then `API.md` / `DESIGN.md` as needed. +- **Read block docs** before using a block — start with its `README.md`, then `API.md` / `DESIGN.md` as needed (see the **AWS Blocks docs** bullet above for where the docs folder lives). - **The JSON-RPC transport is invisible** — do not construct RPC payloads manually. Import and call the typed API directly. ## Deploying (requires AWS credentials) diff --git a/scripts/sync-catalog.mjs b/scripts/sync-catalog.mjs index df50f370..b12074f2 100644 --- a/scripts/sync-catalog.mjs +++ b/scripts/sync-catalog.mjs @@ -103,9 +103,7 @@ function buildCatalog(pkgs) { } function renderCatalogTable(entries) { - const rows = entries.map( - (e) => `| [${e.pkg}](./docs/${e.pkg}/README.md) | ${e.blurb || '—'} | ${e.keywords || '—'} |`, - ); + const rows = entries.map((e) => `| ${e.pkg} | ${e.blurb || '—'} | ${e.keywords || '—'} |`); return ['| Block | What it does | Keywords |', '|-------|--------------|----------|', ...rows].join('\n'); } From 96248e53beb1b5978d786accba64277ff2695cb3 Mon Sep 17 00:00:00 2001 From: Simone Zhang Date: Mon, 29 Jun 2026 12:30:52 +0000 Subject: [PATCH 07/12] ci: scope workflow GITHUB_TOKEN to contents:read; deterministic (code-point) catalog sort --- .github/workflows/block-catalog-check.yml | 3 +++ scripts/sync-catalog.mjs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/block-catalog-check.yml b/.github/workflows/block-catalog-check.yml index 90f2babc..9127f0ef 100644 --- a/.github/workflows/block-catalog-check.yml +++ b/.github/workflows/block-catalog-check.yml @@ -9,6 +9,9 @@ on: pull_request: branches: [main] +permissions: + contents: read + jobs: check: name: Catalog in sync diff --git a/scripts/sync-catalog.mjs b/scripts/sync-catalog.mjs index b12074f2..c4bdbec3 100644 --- a/scripts/sync-catalog.mjs +++ b/scripts/sync-catalog.mjs @@ -98,7 +98,7 @@ function buildCatalog(pkgs) { const readme = readFileSync(join(packagesDir, pkg, 'README.md'), 'utf-8'); return { pkg, blurb: extractBlurb(readme), keywords: extractKeywords(readme) }; }); - entries.sort((a, b) => a.pkg.localeCompare(b.pkg)); + entries.sort((a, b) => (a.pkg < b.pkg ? -1 : a.pkg > b.pkg ? 1 : 0)); return entries; } From 08f65c36697b13809a50b72627505a6b013ea6c1 Mon Sep 17 00:00:00 2001 From: Simone Zhang Date: Tue, 4 Aug 2026 09:23:29 +0000 Subject: [PATCH 08/12] ci(pr-checks): repoint blocks-integrity to gen-block-docs.mjs (base-pack tolerates pre/post-rename) --- .github/workflows/pr-checks.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index 80b093e6..2dfb5b0a 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -72,7 +72,7 @@ jobs: - run: npm ci - name: Sync block docs (prebuild step) - run: node scripts/sync-block-docs.mjs + run: node scripts/gen-block-docs.mjs - name: Check blocks docs-sync integrity env: @@ -80,14 +80,16 @@ jobs: run: | set -euo pipefail - # sync-block-docs.mjs regenerates packages/blocks/docs/ from sibling READMEs + # gen-block-docs.mjs regenerates packages/blocks/docs/ from sibling READMEs # (docs/ is gitignored, built at publish time). We compare the packed # @aws-blocks/blocks tarball at THIS PR's head against the PR base branch — # like-for-like (same algo, same build state), so only a change introduced by # THIS PR trips the check. A PR that doesn't alter what ships needs no bump. + # The base pack falls back to the pre-rename sync-block-docs.mjs so this job works + # against bases from either side of the gen-block-docs.mjs rename. pack_blocks() { local dest="$1" - node scripts/sync-block-docs.mjs >/dev/null + node scripts/gen-block-docs.mjs >/dev/null # --ignore-scripts intentionally packs source + synced docs, NOT a freshly built dist/ — this diffs shipped content, not the published build. ( cd packages/blocks && npm pack --ignore-scripts --pack-destination "$dest" >/dev/null ) set -- "$dest"/*.tgz @@ -103,7 +105,7 @@ jobs: mkdir -p /tmp/base-pack BASE_INTEGRITY=$( cd /tmp/blocks-base - node scripts/sync-block-docs.mjs >/dev/null + node scripts/gen-block-docs.mjs >/dev/null 2>/dev/null || node scripts/sync-block-docs.mjs >/dev/null ( cd packages/blocks && npm pack --ignore-scripts --pack-destination /tmp/base-pack >/dev/null ) set -- /tmp/base-pack/*.tgz [ $# -eq 1 ] || { echo "expected exactly one .tgz in /tmp/base-pack, found $#" >&2; exit 1; } From 1398575392e44bf8867963c5a7099ba439e970c0 Mon Sep 17 00:00:00 2001 From: Simone Zhang Date: Thu, 6 Aug 2026 13:26:14 +0000 Subject: [PATCH 09/12] docs(gen-block-docs): include CHANGELOG + umbrella root docs + per-package docs/ folders (PR #124 feedback) --- packages/blocks/README.md | 4 +-- scripts/gen-block-docs.mjs | 56 +++++++++++++++++++++++++------------- 2 files changed, 39 insertions(+), 21 deletions(-) diff --git a/packages/blocks/README.md b/packages/blocks/README.md index acdd2bd2..5606aa0d 100644 --- a/packages/blocks/README.md +++ b/packages/blocks/README.md @@ -146,13 +146,13 @@ If you need SQL, prefer `DistributedDatabase` for basic Postgres-compatible quer ## Building Block documentation -Every Building Block ships its full docs — `README.md`, `API.md`, and `DESIGN.md` — inside the `@aws-blocks/blocks` package under `docs//`. To read them, locate the bundled folder: +Every Building Block ships its full docs — `README.md`, `API.md`, `DESIGN.md`, and `CHANGELOG.md` — inside the `@aws-blocks/blocks` package under `docs//`. To read them, locate the bundled folder: ```bash node -p "require('path').dirname(require.resolve('@aws-blocks/blocks/docs/README.md'))" ``` -If resolution fails, fall back to `node_modules/@aws-blocks/blocks/docs`. That folder holds this guide (`README.md`) plus one subfolder per block; the catalog below lists every block. +If resolution fails, fall back to `node_modules/@aws-blocks/blocks/docs`. That folder holds this guide (`README.md`), the framework's own `API.md`, `TROUBLESHOOTING.md`, and `CHANGELOG.md` (version history — read when troubleshooting), plus one subfolder per block; the catalog below lists every block. | Block | What it does | Keywords | diff --git a/scripts/gen-block-docs.mjs b/scripts/gen-block-docs.mjs index 61e4ad1b..f6d10d03 100644 --- a/scripts/gen-block-docs.mjs +++ b/scripts/gen-block-docs.mjs @@ -7,11 +7,18 @@ * as the packages/blocks `prebuild` hook so the published package always carries * fresh docs without dirtying any tracked file. * - * It produces two things under packages/blocks/docs/: + * It produces three things under packages/blocks/docs/: * 1. Per-block folders docs// — mirror every root-level *.md of each - * included package EXCEPT the ones in SKIP_MARKDOWN (CHANGELOG.md), so - * block-specific docs (README.md, API.md, DESIGN.md, ...) ship automatically. - * 2. docs/README.md — a verbatim copy of the committed packages/blocks/README.md. + * included package, so block-specific docs (README.md, API.md, DESIGN.md, + * CHANGELOG.md, ...) ship automatically. + * 2. docs//docs/ — if an included package has its own `docs/` folder + * (code samples, mock data, any extension), its entire contents are + * mirrored there verbatim, namespaced under the package so it can never + * collide with the root *.md copied into docs//. + * 3. docs/ root — verbatim copies of every root-level *.md of the umbrella + * packages/blocks package (README.md, API.md, TROUBLESHOOTING.md, + * CHANGELOG.md). `blocks` stays in EXCLUDED so these land at the docs/ root + * only, with no redundant docs/blocks/ subfolder. * * This script NEVER modifies packages/blocks/README.md. The committed catalog * table inside that README is managed separately by scripts/sync-catalog.mjs @@ -26,24 +33,21 @@ * sync-catalog.mjs — the two scripts are kept independent on purpose.) */ -import { readFileSync, writeFileSync, mkdirSync, readdirSync, existsSync, rmSync } from 'node:fs'; -import { join, dirname } from 'node:path'; +import { readFileSync, writeFileSync, mkdirSync, readdirSync, existsSync, statSync, rmSync, cpSync } from 'node:fs'; +import { join, dirname, resolve, sep } from 'node:path'; import { fileURLToPath } from 'node:url'; const __dirname = dirname(fileURLToPath(import.meta.url)); const packagesDir = join(__dirname, '..', 'packages'); -const outDir = join(packagesDir, 'blocks', 'docs'); -const readmePath = join(packagesDir, 'blocks', 'README.md'); +const blocksDir = join(packagesDir, 'blocks'); +const outDir = join(blocksDir, 'docs'); const EXCLUDED = new Set(['blocks', 'data-common', 'foundations', 'create-blocks-app']); -// Root-level markdown that should NOT be mirrored into the per-block doc folder. -const SKIP_MARKDOWN = new Set(['CHANGELOG.md']); - const packages = getPackages(); generatePerBlockDocs(); -writeFileSync(join(outDir, 'README.md'), readFileSync(readmePath, 'utf-8')); +copyMarkdown(blocksDir, outDir); console.log(`Generated ${packages.length} block docs → packages/blocks/docs/`); @@ -59,17 +63,31 @@ function generatePerBlockDocs() { const blockOutDir = join(outDir, pkg); mkdirSync(blockOutDir, { recursive: true }); - // Mirror every root-level markdown file (README.md, API.md, DESIGN.md, and any - // block-specific docs) except the skipped ones, so new docs ship automatically. - const mdFiles = readdirSync(pkgDir, { withFileTypes: true }).filter( - (entry) => entry.isFile() && entry.name.endsWith('.md') && !SKIP_MARKDOWN.has(entry.name), - ); - for (const entry of mdFiles) { - writeFileSync(join(blockOutDir, entry.name), readFileSync(join(pkgDir, entry.name), 'utf-8')); + copyMarkdown(pkgDir, blockOutDir); + + const pkgDocsDir = join(pkgDir, 'docs'); + if (existsSync(pkgDocsDir) && statSync(pkgDocsDir).isDirectory()) { + const resolvedPkgDocsDir = resolve(pkgDocsDir); + const resolvedOutDir = resolve(outDir); + const isSelfReferential = + resolvedPkgDocsDir === resolvedOutDir || resolvedPkgDocsDir.startsWith(resolvedOutDir + sep); + if (!isSelfReferential) { + cpSync(pkgDocsDir, join(blockOutDir, 'docs'), { recursive: true }); + } } } } +/** Mirrors every root-level *.md file of `srcDir` into `destDir` verbatim. */ +function copyMarkdown(srcDir, destDir) { + const mdFiles = readdirSync(srcDir, { withFileTypes: true }).filter( + (entry) => entry.isFile() && entry.name.endsWith('.md'), + ); + for (const entry of mdFiles) { + writeFileSync(join(destDir, entry.name), readFileSync(join(srcDir, entry.name), 'utf-8')); + } +} + // ─── Package discovery (duplicated from sync-catalog.mjs) ────────────────────── function getPackages() { From 42627b388c3e11beca680c920570abfafca351a1 Mon Sep 17 00:00:00 2001 From: Simone Zhang Date: Thu, 6 Aug 2026 13:56:04 +0000 Subject: [PATCH 10/12] docs: re-sync BB catalog after merging main (bb-async-job keywords) --- packages/blocks/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/blocks/README.md b/packages/blocks/README.md index 5606aa0d..acb8f2a4 100644 --- a/packages/blocks/README.md +++ b/packages/blocks/README.md @@ -160,7 +160,7 @@ If resolution fails, fall back to `node_modules/@aws-blocks/blocks/docs`. That f | auth-common | Shared interfaces and UI components for all AWS Blocks auth Building Blocks. | — | | bb-agent | AI agent with streaming, tool calling, and conversation persistence. | — | | bb-app-setting | A single application configuration value backed by SSM Parameter Store. | — | -| bb-async-job | Background job processing backed by SQS and Lambda. | queue, job, background, async, worker, submit, batch, retry, SQS | +| bb-async-job | Background job processing backed by SQS and Lambda. | queue, job, background, async, worker, submit, batch, retry, status, transitions, SQS | | bb-auth-basic | Simple username/password authentication with JWT sessions, password policy, and optional code-confirmed signup and password reset. | — | | bb-auth-cognito | Authentication backed by Amazon Cognito User Pools. | — | | bb-auth-oidc | OIDC sign-in gate for AWS Blocks applications. | — | From 7d2dbf1d1e452f91cdf9b4bc9578155c246d7d3f Mon Sep 17 00:00:00 2001 From: Simone Zhang Date: Thu, 6 Aug 2026 14:52:12 +0000 Subject: [PATCH 11/12] =?UTF-8?q?docs:=20address=20review=20=E2=80=94=20pr?= =?UTF-8?q?epack=20docs=20gen,=20soften=20docs-availability=20wording,=20e?= =?UTF-8?q?scape=20catalog=20pipes,=20clarify=20duplicated-discovery=20rat?= =?UTF-8?q?ionale=20(PR=20#124)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/blocks/README.md | 4 ++-- packages/blocks/package.json | 1 + .../create-blocks-app/resources/AGENTS.md | 4 ++-- scripts/gen-block-docs.mjs | 14 ++++++++++---- scripts/sync-catalog.mjs | 19 +++++++++++++++---- 5 files changed, 30 insertions(+), 12 deletions(-) diff --git a/packages/blocks/README.md b/packages/blocks/README.md index acb8f2a4..c3000b34 100644 --- a/packages/blocks/README.md +++ b/packages/blocks/README.md @@ -146,7 +146,7 @@ If you need SQL, prefer `DistributedDatabase` for basic Postgres-compatible quer ## Building Block documentation -Every Building Block ships its full docs — `README.md`, `API.md`, `DESIGN.md`, and `CHANGELOG.md` — inside the `@aws-blocks/blocks` package under `docs//`. To read them, locate the bundled folder: +Every Building Block ships its docs inside the `@aws-blocks/blocks` package under `docs//`: each `docs//` folder contains that block's `README.md`, plus `API.md`, `DESIGN.md`, and `CHANGELOG.md` where present. To read them, locate the bundled folder: ```bash node -p "require('path').dirname(require.resolve('@aws-blocks/blocks/docs/README.md'))" @@ -247,7 +247,7 @@ Run with `npm run test:e2e`. Write the test first, iterate against mocks until i ## Reference -- **Per-block documentation:** `docs//README.md` (overview), `docs//API.md` (full API reference), `docs//DESIGN.md` (architecture & rationale) — e.g. `docs/bb-distributed-table/README.md`. The catalog + decision tree live in `docs/README.md`. +- **Per-block documentation:** `docs//README.md` (overview), plus `docs//API.md` (full API reference) and `docs//DESIGN.md` (architecture & rationale) where present — e.g. `docs/bb-distributed-table/README.md`. The catalog + decision tree live in `docs/README.md`. - **UI components** (`@aws-blocks/blocks/ui`): `Authenticator`, `AuthenticatedContent`, `AccountMenuBar`, `onAuthChange`, `broadcastAuthChange` — framework-agnostic, return DOM nodes. See the `@aws-blocks/auth-common` README. - **SSR** (`@aws-blocks/blocks/server`): `withAuth` forwards browser cookies to API calls during server rendering. See the `@aws-blocks/core` README. - **Wire protocol & debugging:** the client is JSON-RPC 2.0 over a single endpoint — you should never call it directly. For `curl`-level troubleshooting, see [TROUBLESHOOTING.md](./TROUBLESHOOTING.md). diff --git a/packages/blocks/package.json b/packages/blocks/package.json index c6d60e28..8be26c03 100644 --- a/packages/blocks/package.json +++ b/packages/blocks/package.json @@ -50,6 +50,7 @@ }, "scripts": { "prebuild": "node ../../scripts/gen-block-docs.mjs", + "prepack": "node ../../scripts/gen-block-docs.mjs", "build": "tsc --build", "test": "node --test dist/conditional-exports.test.js dist/vendorize-map.test.js" }, diff --git a/packages/create-blocks-app/resources/AGENTS.md b/packages/create-blocks-app/resources/AGENTS.md index 6a21d6ef..1a72240d 100644 --- a/packages/create-blocks-app/resources/AGENTS.md +++ b/packages/create-blocks-app/resources/AGENTS.md @@ -5,7 +5,7 @@ - **Backend:** `aws-blocks/index.ts` — APIs, auth, data models - **Frontend:** `src/` — imports backend APIs via `import { api } from 'aws-blocks'` - **Tests:** `test/e2e.test.ts` — run with `npm run test:e2e` -- **AWS Blocks docs** ship inside the `@aws-blocks/blocks` package. Find the docs folder once: `node -p "require('path').dirname(require.resolve('@aws-blocks/blocks/docs/README.md'))"` (fallback: `node_modules/@aws-blocks/blocks/docs`). Read everything relative to it: `README.md` (dev guide + catalog + decision tree — start here), then `/README.md`, `/API.md`, `/DESIGN.md`. +- **AWS Blocks docs** ship inside the `@aws-blocks/blocks` package. Find the docs folder once: `node -p "require('path').dirname(require.resolve('@aws-blocks/blocks/docs/README.md'))"` (fallback: `node_modules/@aws-blocks/blocks/docs`). Read everything relative to it: `README.md` (dev guide + catalog + decision tree — start here), then `/README.md`, plus `/API.md` and `/DESIGN.md` where present. ## Workflow @@ -17,7 +17,7 @@ ## Rules - **Use Building Blocks** for all persistence and cloud abstractions — never local files, in-memory arrays, or local databases. -- **Read block docs** before using a block — start with its `README.md`, then `API.md` / `DESIGN.md` as needed (see the **AWS Blocks docs** bullet above for where the docs folder lives). +- **Read block docs** before using a block — start with its `README.md`, then `API.md` / `DESIGN.md` where present — not every block has them, so a missing file is not an error (see the **AWS Blocks docs** bullet above for where the docs folder lives). - **The JSON-RPC transport is invisible** — do not construct RPC payloads manually. Import and call the typed API directly. ## Deploying (requires AWS credentials) diff --git a/scripts/gen-block-docs.mjs b/scripts/gen-block-docs.mjs index f6d10d03..b717ed02 100644 --- a/scripts/gen-block-docs.mjs +++ b/scripts/gen-block-docs.mjs @@ -4,8 +4,9 @@ /** * Generates the gitignored, shipped `docs/` artifact for @aws-blocks/blocks. Runs - * as the packages/blocks `prebuild` hook so the published package always carries - * fresh docs without dirtying any tracked file. + * as the packages/blocks `prebuild` AND `prepack` hooks so the published package + * always carries fresh docs — even when packing without a preceding build — + * without dirtying any tracked file. * * It produces three things under packages/blocks/docs/: * 1. Per-block folders docs// — mirror every root-level *.md of each @@ -29,8 +30,13 @@ * as a harmless alias for the same behavior. * * Inclusion rule: every package under packages/ that has a README.md and is not in - * EXCLUDED. (The package-discovery logic is intentionally duplicated from - * sync-catalog.mjs — the two scripts are kept independent on purpose.) + * EXCLUDED. + * + * NOTE: EXCLUDED + getPackages() are intentionally duplicated in this file and in + * sync-catalog.mjs so each script stays dependency-free and independently + * runnable (no shared module to resolve, no build step). They MUST agree on the + * block set — keep the two in sync when editing. If this pair grows further, + * extract a shared module instead. */ import { readFileSync, writeFileSync, mkdirSync, readdirSync, existsSync, statSync, rmSync, cpSync } from 'node:fs'; diff --git a/scripts/sync-catalog.mjs b/scripts/sync-catalog.mjs index c4bdbec3..0e28e8b4 100644 --- a/scripts/sync-catalog.mjs +++ b/scripts/sync-catalog.mjs @@ -9,7 +9,7 @@ * leaves the rest of the README (the static decision-tree prose, etc.) untouched * and does NOT generate the shipped `docs/` artifact. That artifact (per-block * docs// folders + docs/README.md copy) is produced by - * scripts/gen-block-docs.mjs, run as the packages/blocks `prebuild` hook. + * scripts/gen-block-docs.mjs, run as the packages/blocks `prebuild` and `prepack` hooks. * * Two modes: * @@ -23,8 +23,13 @@ * markers are missing; exit 0 if in sync. Writes nothing. * * Inclusion rule: every package under packages/ that has a README.md and is not in - * EXCLUDED. (The package-discovery logic is intentionally duplicated in - * gen-block-docs.mjs — the two scripts are kept independent on purpose.) + * EXCLUDED. + * + * NOTE: EXCLUDED + getPackages() are intentionally duplicated in this file and in + * gen-block-docs.mjs so each script stays dependency-free and independently + * runnable (no shared module to resolve, no build step). They MUST agree on the + * block set — keep the two in sync when editing. If this pair grows further, + * extract a shared module instead. */ import { readFileSync, writeFileSync, readdirSync, existsSync } from 'node:fs'; @@ -103,10 +108,16 @@ function buildCatalog(pkgs) { } function renderCatalogTable(entries) { - const rows = entries.map((e) => `| ${e.pkg} | ${e.blurb || '—'} | ${e.keywords || '—'} |`); + const rows = entries.map( + (e) => `| ${e.pkg} | ${escapeCell(e.blurb) || '—'} | ${escapeCell(e.keywords) || '—'} |`, + ); return ['| Block | What it does | Keywords |', '|-------|--------------|----------|', ...rows].join('\n'); } +function escapeCell(value) { + return (value || '').replace(/\|/g, '\\|'); +} + // ─── Marker helpers ────────────────────────────────────────────────────────── function injectCatalog(readme, catalogTable) { From 2148aec46705d5a452f19e76712db8153035d895 Mon Sep 17 00:00:00 2001 From: Simone Zhang Date: Thu, 6 Aug 2026 16:04:05 +0000 Subject: [PATCH 12/12] fix(publish): discover pack tarball via filesystem, not npm-pack stdout (unbreak pack under npm 10 prepack) --- scripts/publish/publish.ts | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/scripts/publish/publish.ts b/scripts/publish/publish.ts index 5b3b8cc9..bebfeb4a 100644 --- a/scripts/publish/publish.ts +++ b/scripts/publish/publish.ts @@ -2,7 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 import { createHash } from "node:crypto"; -import { readdir, readFile, unlink, writeFile, mkdir, stat, access } from "node:fs/promises"; +import { readdir, readFile, unlink, writeFile, mkdir, stat, access, mkdtemp } from "node:fs/promises"; +import { readdirSync } from "node:fs"; +import { tmpdir } from "node:os"; import { join, resolve, relative, } from "node:path"; import { execSync } from "node:child_process"; import { LocalPublisher } from "./publishers/local.ts"; @@ -97,18 +99,28 @@ function topoSort(packages: PackageInfo[]): PackageInfo[] { // ── Pack + hash ──────────────────────────────────────────────────── async function packPackage(pkg: PackageInfo): Promise { - // npm pack outputs the tarball filename to stdout - const tarballName = execSync("npm pack --pack-destination .", { + // npm >= 10 forwards lifecycle-script (prepack) stdout into `npm pack`'s stdout, + // so the filename is discovered from the filesystem instead of stdout. + const packDir = await mkdtemp(join(tmpdir(), "aws-blocks-pack-")); + + execSync(`npm pack --pack-destination ${JSON.stringify(packDir)}`, { cwd: pkg.dirPath, encoding: "utf-8", - }).trim(); + }); + + const tarballs = readdirSync(packDir).filter((name) => name.endsWith(".tgz")); + if (tarballs.length !== 1) { + throw new Error( + `expected exactly one .tgz in ${packDir} for ${pkg.name}, found ${tarballs.length}: ${tarballs.join(", ")}`, + ); + } - const tarballPath = join(pkg.dirPath, tarballName); + const tarballPath = join(packDir, tarballs[0]!); - // Validate tarball path stays within the package directory + // Validate tarball path stays within the pack destination directory const resolvedTarball = resolve(tarballPath); - if (!resolvedTarball.startsWith(resolve(pkg.dirPath))) { - throw new Error(`Tarball path escapes package directory: ${resolvedTarball}`); + if (!resolvedTarball.startsWith(resolve(packDir))) { + throw new Error(`Tarball path escapes pack destination directory: ${resolvedTarball}`); } const tarballBuf = await readFile(tarballPath);