Skip to main content

create-tutorial

Scaffold a new Membrane API Gateway tutorial in the api-gateway repo — the numbered self-teaching YAML under distribution/tutorials/<category>/, its support files and README links, and the matching auto-discovered integration test. Use whenever the user asks to create, add, write, or scaffold a tutorial (or a tutorial step), even if they only describe the gateway behavior they want to teach and don't say the word "skill" or name the files.

来源信息

仓库
membrane/api-gateway
最近来源活动
2026年9月9日 10:53
检测到的 SKILL.md 语言
英语
星标
645
分支
182

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
create-tutorial
description
Scaffold a new Membrane API Gateway tutorial in the api-gateway repo — the numbered self-teaching YAML under distribution/tutorials/<category>/, its support files and README links, and the matching auto-discovered integration test. Use whenever the user asks to create, add, write, or scaffold a tutorial (or a tutorial step), even if they only describe the gateway behavior they want to teach and don't say the word "skill" or name the files.
# Create a Tutorial In this repo a tutorial is usually a **runnable, self-teaching YAML config**. The leading comment block of the file is the lesson; the config below it is the working example the reader runs. A complete tutorial therefore has three parts, and they must stay in sync: 1. **The numbered YAML** in `distribution/tutorials/<category>/` — the lesson plus the config. 2. **Category support files** — `membrane.sh`, `membrane.cmd`, a `README.md` (and `run-docker.*` where the category offers Docker), so the reader can actually start it. 3. **An integration test** under `distribution/src/test/java/com/predic8/membrane/tutorials/…` so the tutorial can't silently rot. These tests unzip the built distribution and start Membrane against the tutorial's YAML, so a broken example fails CI. A tutorial step can also be a **plain `.md` file** — use this when the step needs no local Membrane config (e.g. a curl-only walkthrough against a hosted API). A `.md` step gets no integration test; it just lives alongside the numbered YAMLs and is linked from the README. Work through the steps below in order. The reader of a tutorial follows the file top-to-bottom with no other docs open, so the single most important thing is that the YAML teaches itself. ## Step 1 — Choose the category and filename Categories live under `distribution/tutorials/`: `getting-started`, `json`, `xml`, `advanced`, `transformation`, `security`, `jwt`, `api-keys`, `oauth2`, `soap`, `soap-rest-converter`, `wsdl-to-openapi`, `orchestration`, `sse`, `mcp`, `llm-gateway`. Pick the one that fits; only create a new category if nothing fits (see "New category" at the end). Files are numbered `NN-Title.yaml` (e.g. `10-PathParameters.yaml`). The number sets reading order. List the directory and pick the next gap — convention leaves room between numbers (10, 20, 30…) so steps can be inserted later. Match the existing `PascalCase`-with-hyphens title style. ## Step 2 — Write the YAML (the lesson lives here) Open a sibling file in the same category and mirror its shape. Every tutorial YAML has the same skeleton: ```yaml # yaml-language-server: $schema=https://www.membrane-api.io/v7.2.4.json # # Tutorial: <Human Readable Name> # # <One sentence on what this teaches.> # # 1.) Start Membrane: # ./membrane.sh -c NN-Title.yaml # # 2.) Call the API: # curl localhost:2000/... # # <Expected output — paste it verbatim.> # # 3.) Continue with file NN-Next.yaml # only in sequential tutorials, see below api: port: 2000 ... ``` Hold to these conventions — they're what makes the set feel like one coherent course: - **Pin the schema line** to whatever sibling files use (grep one: `grep -m1 schema= distribution/tutorials/<category>/*.yaml`). It tracks the current release version, so don't invent a number. - **Keep the comment block short.** Steps are a command and its expected output — nothing more. Don't explain what the config does; the reader can see the config. Don't repeat information the `name:` field already provides. - **No section-header comments** above each `api:` block. The `name:` field describes the API. - **No trailing `# ...` after a command** in a step. Readers copy the line into their shell, and Windows `cmd` doesn't treat `#` as a comment, so it would pass the text to the command. Put prompts and notes on their own line: ``` # curl -u alice localhost:2000 # enter password: secret ``` Show every prompt an interactive tool asks, in order (e.g. `htpasswd` also asks `Re-type new password:`), so the reader isn't surprised by one the step left out. - **`NOTE:` blocks go at the end of the file**, after the config. Caveats, alternatives and background (unsupported hash formats, other databases, "plaintext only for demos") would otherwise come between the steps and the config. The top block stays steps only. - **Inline comments only for the non-obvious WHY** — a surprising built-in, a hidden constraint. Never comment what the interceptor name already says. - **Keep the config minimal and focused** on the one concept the step teaches. Tutorials favor clarity over completeness; richer setups belong under `distribution/examples/`. - **Port 2000** is the house convention for tutorial listeners unless the lesson needs more. - Use `return: status: 200` not `return: {}`. - **"Continue with file NN-Title.yaml" only where the reader goes through the files in order:** `getting-started/`, `advanced/`, and numbered sub-step series within a topic (e.g. 20, 21, 22, 23). Every other category (routing, security, openapi, ...) is random access, so its files get no "Continue with" pointer. When adding to a sequential set, end the previous file's comment block with a pointer to the new one. ## Step 3 — Make the category runnable The reader runs `./membrane.sh -c <file>` from the category directory, so that directory needs the launcher scripts and a README. - If `membrane.sh` / `membrane.cmd` are missing from the category dir, copy them verbatim from a sibling category — they are identical generic launchers that walk up to find `MEMBRANE_HOME`. - Copy `run-docker.sh` / `run-docker.cmd` only if the category documents a Docker workflow. - **`README.md`**: the category README points at the first file in the chain ("open [NN-First.yaml](NN-First.yaml) and follow the instructions there"). If you added the first file of a new category, write this README modeled on a sibling's. ## Step 4 — Write the integration test (don't skip this) Without a test the example will drift out of sync with the code and break unnoticed — that's the whole reason these tests exist. The harness starts Membrane from the **built distribution** with your YAML and lets you assert against the live gateway. Test package mirrors the category but with **underscores**: dir `getting-started` → package `com.predic8.membrane.tutorials.getting_started`. The `ExampleTests` `@Suite` selects the whole `com.predic8.membrane.tutorials` package, so a test placed correctly is **auto-discovered — no registration needed.** Each category has an `Abstract<Category>TutorialTest` that fixes `getTutorialDir()` (this is where the hyphen dir name is restored). Your concrete test extends it, names the YAML, and asserts: ```java /* <copy the Apache 2.0 license header verbatim from a sibling test — keep its year> */ package com.predic8.membrane.tutorials.<category_with_underscores>; import org.junit.jupiter.api.Test; import static io.restassured.RestAssured.given; import static org.hamcrest.CoreMatchers.containsString; public class <Name>TutorialTest extends Abstract<Category>TutorialTest { @Override protected String getTutorialYaml() { return "NN-Title.yaml"; } @Test void <whatItVerifies>() { // @formatter:off given() .when() .get("http://localhost:2000/...") .then() .statusCode(200) .body(containsString("...")); // @formatter:on } } ``` Notes: - Copy the license header from an existing sibling test rather than retyping it, so the year and wording match the repo. - Assert the **observable behavior the YAML comments promise** in Step 2 — if the lesson says "you should see Customer: 7", assert exactly that. The test is the executable form of the lesson. - If you created a brand-new category, also create its `Abstract<Category>TutorialTest` modeled on an existing one — it just returns the hyphenated dir name from `getTutorialDir()`. - **Sub-categories** (e.g. `llm-gateway/google`) need their own abstract base returning the full relative path (`llm-gateway/google`), not just the top-level category abstract. Model on `AbstractAiTutorialTest` / `AbstractGoogleTutorialTest` in `distribution/src/test/java/com/predic8/membrane/tutorials/`. - **`.md` steps** have no integration test — skip this step for them. ## Step 5 — Verify Run the test through the existing `run-example-test` skill. Pass `-b` so the distribution is rebuilt first: the tutorial YAML ships **inside** `distribution/target/*.zip`, and the tests run from the unzipped distribution, so an un-rebuilt run would test the old YAML (or none). ```bash .claude/skills/run-example-test/run-example-test.sh -b <Name>TutorialTest ``` A green run means the example actually starts and behaves as the lesson claims. ## Common pitfalls ### Template JSON auto-quoting When a `template` interceptor has `contentType: application/json`, the engine **automatically adds quotes** around every `${...}` interpolation. Writing `"${expr}"` produces `""value""` and breaks JSON. Always write interpolations **unquoted**: ```yaml - template: contentType: application/json src: | { "sub": ${user()}, "aud": "demo-resource" } ``` String literals that are not interpolated keep their quotes as normal. ### Built-in functions in Groovy templates Prefer the built-ins over raw property access: - `user()` — authenticated username (from `basicAuthentication` security scheme) - `scopes()` — list of granted scopes - `hasScope("read")` — boolean scope check Avoid the verbose `${property.'membrane.security.schemes'[0].username}` — use `${user()}` instead. ## New category (only when nothing fits) 1. `distribution/tutorials/<new-category>/` with the first `NN-*.yaml`, `membrane.sh`, `membrane.cmd`, and a `README.md`. 2. Add a section linking the new category in `distribution/tutorials/README.md`. 3. Create `com.predic8.membrane.tutorials.<new_category>.Abstract<NewCategory>TutorialTest` extending `AbstractMembraneTutorialTest`, returning the hyphenated dir from `getTutorialDir()`. 4. Add concrete tests as in Step 4. ## Checklist - [ ] `NN-Title.yaml` (or `.md` for curl-only steps) created with schema line, short numbered comment block, minimal config - [ ] Comment block: steps = command + expected output only; no prose repeating the config - [ ] No trailing `# ...` on command lines (Windows `cmd` passes it to the command); prompts on their own line - [ ] `NOTE:` blocks placed after the config at the end of the file, not in the top step block - [ ] No section-header comments above `api:` blocks; `name:` describes them - [ ] `return: status: 200` used (not `return: {}`) - [ ] Template interpolations unquoted when `contentType: application/json` - [ ] Previous file points to it ("Continue with…") only in a sequential set (getting-started, advanced, NN/NN+1 sub-step series); none in random-access categories - [ ] Category has `membrane.sh`, `membrane.cmd`, `README.md` (and `run-docker.*` if used) - [ ] `<Name>TutorialTest` in the underscore package, extends the category abstract base, has the license header - [ ] Sub-category has its own abstract base returning the full relative path - [ ] `.md` steps have no integration test - [ ] Assertions match the behavior the YAML comments promise - [ ] `run-example-test.sh -b <Name>TutorialTest` is green
在 GitHub 查看