@postman/postman-plugin 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -7
- package/dist/hosts/opencode-config.js +147 -0
- package/dist/hosts/opencode.js +315 -34
- package/dist/hosts/shared.js +10 -3
- package/dist/source.js +19 -5
- package/dist/system.js +20 -1
- package/hooks/session-start-context.md +1 -1
- package/mcp.pi.json +2 -2
- package/package.json +1 -1
- package/skills/api-discovery/SKILL.md +23 -7
- package/skills/api-documentation/SKILL.md +21 -3
- package/skills/api-engineer/SKILL.md +11 -11
- package/skills/api-mocking/SKILL.md +46 -18
- package/skills/api-simulation/SKILL.md +186 -0
- package/skills/api-simulation/reference/sim_yaml_schema.md +112 -0
- package/skills/bootstrap/SKILL.md +46 -32
- package/skills/bootstrap/reference/cli_installation.md +4 -3
- package/skills/ci-integration/SKILL.md +10 -9
- package/skills/datasets/SKILL.md +6 -5
- package/skills/variables/SKILL.md +106 -0
package/dist/source.js
CHANGED
|
@@ -5,15 +5,25 @@ export const GIT_URL = `https://github.com/${REPO}.git`;
|
|
|
5
5
|
export const BRANCH = 'main';
|
|
6
6
|
/** Unpinned, so `pi update` moves it with each `latest` release; test/pi-package.test.js checks the name. */
|
|
7
7
|
export const PI_SOURCE = 'npm:@postman/postman-plugin';
|
|
8
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* This repo's default branch, whose root package.json names the OpenCode entrypoint. Not a `#semver:` range: npm reads a
|
|
10
|
+
* version from any tag ending in one, so the installer's `@postman/postman-plugin@<version>` tags would match it too.
|
|
11
|
+
*/
|
|
12
|
+
export const OPENCODE_SPEC = `github:${REPO}`;
|
|
13
|
+
/** The first OpenCode release of each major that installs `OPENCODE_SPEC`, found by bisecting the harness in opencode/scripts/test-plugin-add.js. */
|
|
14
|
+
export const OPENCODE_MINIMUM = { 1: [1, 14, 33], 2: [2, 0, 4] };
|
|
15
|
+
/** Pre-`plugin add` installs: a clone of the repo plus this one-line loader file. Must stay byte-identical to what those installs wrote, or the installer stops recognizing them. */
|
|
9
16
|
export const OPENCODE_SHIM = "export { default } from '../postman-plugin/opencode/src/index.ts';\n";
|
|
10
17
|
/** Pinned: this third-party CLI writes Kimi's plugin store for us, and an unpinned npx would run whatever is latest. */
|
|
11
18
|
export const PLUGINS_CLI = 'plugins@1.3.4';
|
|
12
19
|
// A git transport (not `file://`, which names a local path), optional user-info, then
|
|
13
20
|
// GitHub's host and its `/`, or the scp-style `git@github.com:` form with no scheme.
|
|
14
|
-
const GITHUB_PREFIX = /^(?:(?:https?|ssh|git|git\+ssh|ssh\+git|git\+https):\/\/)?(?:[
|
|
15
|
-
// User-info in a URL, or before an scp-style `host:path
|
|
16
|
-
|
|
21
|
+
const GITHUB_PREFIX = /^(?:(?:https?|ssh|git|git\+ssh|ssh\+git|git\+https):\/\/)?(?:[^/]*@)?github\.com[:/]/,
|
|
22
|
+
// User-info in a URL, or before an scp-style `host:path`. Greedy, because a password may hold an unescaped `@` and a
|
|
23
|
+
// URL's host starts after the last one.
|
|
24
|
+
URL_USER_INFO = /^([a-z][a-z+.-]*:\/\/)([^/?#]*)@/i, URL_USER_INFO_IN_TEXT = /([a-z][a-z+.-]*:\/\/)([^/?#\s]*)@/gi, SCP_USER_INFO_IN_TEXT = /(^|[\s"'`(=])([^/:\s"'`(=]*)@(?=[^@/:\s]+:)/gm, SCP_USER_INFO = /^()([^/:]*)@(?=[^@/:]+:)/,
|
|
25
|
+
// The conventional `git@` names no credential and is kept.
|
|
26
|
+
withoutUserInfo = (match, before, info) => (info === 'git' ? match : before);
|
|
17
27
|
function normalize(source) {
|
|
18
28
|
return source.trim().toLowerCase()
|
|
19
29
|
.replace(GITHUB_PREFIX, '')
|
|
@@ -26,5 +36,9 @@ export function isSameRepo(source, repo) {
|
|
|
26
36
|
}
|
|
27
37
|
/** Strips a URL's user-info, where a token would be, so a source can be printed. */
|
|
28
38
|
export function redact(source) {
|
|
29
|
-
return source.replace(URL_USER_INFO,
|
|
39
|
+
return source.replace(URL_USER_INFO, withoutUserInfo).replace(SCP_USER_INFO, withoutUserInfo);
|
|
40
|
+
}
|
|
41
|
+
/** Strips the user-info of every URL in a command's output, which can echo a spec it was given. */
|
|
42
|
+
export function redactText(text) {
|
|
43
|
+
return text.replace(URL_USER_INFO_IN_TEXT, withoutUserInfo).replace(SCP_USER_INFO_IN_TEXT, withoutUserInfo);
|
|
30
44
|
}
|
package/dist/system.js
CHANGED
|
@@ -2,6 +2,7 @@ import { spawn } from 'node:child_process';
|
|
|
2
2
|
import fs from 'node:fs/promises';
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
+
import { redact } from './source.js';
|
|
5
6
|
// A missing file, or a path through something that isn't a directory. Anything else,
|
|
6
7
|
// such as a file that exists but can't be read, must not pass for "absent".
|
|
7
8
|
const ABSENT = ['ENOENT', 'ENOTDIR'];
|
|
@@ -30,8 +31,9 @@ function execute(file, args, env) {
|
|
|
30
31
|
child.on('close', (code) => resolve({ code: code ?? 1, stdout, stderr }));
|
|
31
32
|
});
|
|
32
33
|
}
|
|
34
|
+
/** Formats a command for logs and failure messages, with the user-info of any URL in it redacted. */
|
|
33
35
|
export function formatCommand(command, args) {
|
|
34
|
-
return [command, ...args].map((part) => (/^[\w@./:=~-]+$/.test(part) ? part : JSON.stringify(part))).join(' ');
|
|
36
|
+
return [command, ...args].map((part) => redact(part)).map((part) => (/^[\w@./:=~-]+$/.test(part) ? part : JSON.stringify(part))).join(' ');
|
|
35
37
|
}
|
|
36
38
|
export function createSystem({ dryRun = false, log = (line) => console.log(line) } = {}) {
|
|
37
39
|
const env = process.env, platform = process.platform;
|
|
@@ -85,6 +87,17 @@ export function createSystem({ dryRun = false, log = (line) => console.log(line)
|
|
|
85
87
|
throw error;
|
|
86
88
|
}
|
|
87
89
|
},
|
|
90
|
+
async readDir(dir) {
|
|
91
|
+
try {
|
|
92
|
+
return await fs.readdir(dir);
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
if (ABSENT.includes(error.code ?? '')) {
|
|
96
|
+
return [];
|
|
97
|
+
}
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
},
|
|
88
101
|
async probe(command, args) {
|
|
89
102
|
return execute(await resolve(command), args, env);
|
|
90
103
|
},
|
|
@@ -108,6 +121,12 @@ export function createSystem({ dryRun = false, log = (line) => console.log(line)
|
|
|
108
121
|
await fs.rm(file, { recursive: true, force: true });
|
|
109
122
|
}
|
|
110
123
|
},
|
|
124
|
+
async rename(from, to) {
|
|
125
|
+
log(` rename ${from} ${to}`);
|
|
126
|
+
if (!dryRun) {
|
|
127
|
+
await fs.rename(from, to);
|
|
128
|
+
}
|
|
129
|
+
},
|
|
111
130
|
log
|
|
112
131
|
};
|
|
113
132
|
}
|
|
@@ -3,7 +3,7 @@ You have the Postman plugin.
|
|
|
3
3
|
|
|
4
4
|
Before responding to any non-trivial API engineering task — designing, implementing, mocking, testing, monitoring, documenting, or deploying an API or Postman Flow — load the `postman:api-engineer` skill and follow it. It is the default entry point and routes to the specific postman skills from there. Pure questions and trivial one-line edits don't need it.
|
|
5
5
|
|
|
6
|
-
When the intent is already specific, you may enter directly into the relevant skill instead: `postman:bootstrap` (link this repo to a Postman workspace), `postman:api-mocking`, `postman:api-testing`, `postman:api-monitoring`, `postman:flows`, `postman:ci-integration`, `postman:api-discovery`, `postman:ai-readiness`, `postman:performance-testing`, `postman:api-documentation
|
|
6
|
+
When the intent is already specific, you may enter directly into the relevant skill instead: `postman:bootstrap` (link this repo to a Postman workspace), `postman:api-mocking`, `postman:api-testing`, `postman:api-monitoring`, `postman:flows`, `postman:ci-integration`, `postman:api-discovery`, `postman:ai-readiness`, `postman:performance-testing`, `postman:api-documentation`, `postman:variables` (where a value or secret belongs and how to reference it).
|
|
7
7
|
|
|
8
8
|
If you were dispatched as a subagent to execute a specific task, ignore this block — `postman:api-engineer` governs the orchestrating session, and it already shaped your dispatch.
|
|
9
9
|
|
package/mcp.pi.json
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
"description": "Postman workspaces, collections, specs, environments, mocks and monitors",
|
|
7
7
|
"headers": {
|
|
8
8
|
"X-Source": "postman-pi-plugin",
|
|
9
|
-
"X-Plugin-Version": "0.
|
|
10
|
-
"User-Agent": "postman-pi-plugin/0.
|
|
9
|
+
"X-Plugin-Version": "0.3.0",
|
|
10
|
+
"User-Agent": "postman-pi-plugin/0.3.0"
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@postman/postman-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Postman's API engineering skills for coding agents: a Pi package, and an npx installer that sets up Claude Code, Codex, Cursor, Kimi Code, Factory Droid, OpenCode and Pi.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -116,13 +116,29 @@ current `postman login` session, in that order.
|
|
|
116
116
|
|
|
117
117
|
## After discovery: reusing what was found
|
|
118
118
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
119
|
+
Finding another service's workspace does not make it the workspace for the
|
|
120
|
+
current repository. `workspace pull` is for working on the service that owns
|
|
121
|
+
that workspace, from that service's repository. If the service is only a
|
|
122
|
+
dependency, or the user only wants to inspect one of its resources, keep the
|
|
123
|
+
current repository connected to its own workspace and use the dependency
|
|
124
|
+
feature instead.
|
|
125
|
+
|
|
126
|
+
Use `postman dependency add <type> <name-or-id>` to declare and download the
|
|
127
|
+
selected resource rather than copying it by hand or pulling the other
|
|
128
|
+
service's entire workspace. Supported types are `collection`, `environment`,
|
|
129
|
+
and `mock`. Pass the entity ID returned by `search`, or use the entity name;
|
|
130
|
+
when resolving a name in a specific workspace, add `--workspace
|
|
131
|
+
<workspace-id>`. If the Context Graph identifies only a service or API, locate
|
|
132
|
+
the concrete entity with `search` first.
|
|
133
|
+
|
|
134
|
+
Dependencies are recorded in `.postman/resources.yaml` and downloaded under
|
|
135
|
+
`postman/.dependencies/`, so the copy works locally straight away. Use
|
|
136
|
+
`postman dependency list` to inspect declarations, `postman dependency
|
|
137
|
+
install` to download declared dependencies after a clone or in CI, `postman
|
|
138
|
+
dependency update` to refresh them from Postman, and `postman dependency
|
|
139
|
+
remove <name-or-id>` to delete a declaration and its downloaded files. The
|
|
140
|
+
`list`, `install`, `update`, and `remove` commands accept `--type <type>`;
|
|
141
|
+
`install` and `update` can also target one declared dependency by name.
|
|
126
142
|
|
|
127
143
|
## Reference
|
|
128
144
|
|
|
@@ -10,11 +10,25 @@ API documentation can be done in two predominant ways:
|
|
|
10
10
|
1. through a Postman collection,
|
|
11
11
|
2. with an OpenAPI spec
|
|
12
12
|
|
|
13
|
-
It is recommended to create both. They serve different, complementary use
|
|
14
|
-
|
|
13
|
+
It is recommended to create both. They serve different, complementary use
|
|
14
|
+
cases, and the Postman CLI can generate one from the other. Use the artifact
|
|
15
|
+
that represents the current source of truth rather than recreating its
|
|
16
|
+
operations by hand. Postman collections are human-friendly and enable other
|
|
17
|
+
capabilities such as mocks, monitors, SDKs, and specifications. Collections
|
|
18
|
+
created or edited directly on disk use the v3 format described by
|
|
19
|
+
**collection-schema-v3**.
|
|
15
20
|
|
|
16
21
|
Specs are vendor-neutral, stay in your repo, and can be linted against governance rules (if any) set by your organization.
|
|
17
22
|
|
|
23
|
+
## Generate a collection from a specification
|
|
24
|
+
|
|
25
|
+
When an OpenAPI specification already defines the contract, generate a Postman
|
|
26
|
+
collection from it in one command:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
postman spec generate collection <spec-path-or-id> -n "<collection-name>"
|
|
30
|
+
```
|
|
31
|
+
|
|
18
32
|
## Good practices for API design
|
|
19
33
|
|
|
20
34
|
See [reference/rest-api-best-practices.md](reference/rest-api-best-practices.md)
|
|
@@ -31,4 +45,8 @@ Examples (in a Postman collection) are an excellent way to capture sample API re
|
|
|
31
45
|
|
|
32
46
|
## Workflow
|
|
33
47
|
1. Establish the contract - refer to best practices. Don't just accept the user's ask - fight for the right API design.
|
|
34
|
-
2. Choose the
|
|
48
|
+
2. Choose the source of truth — a Postman collection, an OpenAPI specification,
|
|
49
|
+
or both. Recommend both when their complementary uses benefit the task.
|
|
50
|
+
3. If the specification is authoritative, generate the collection with
|
|
51
|
+
`postman spec generate collection` instead of manually duplicating its
|
|
52
|
+
operations.
|
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: api-engineer
|
|
3
|
-
description:
|
|
3
|
+
description: Makes coding agents faster and more accurate with fewer tokens when designing, implementing, mocking, testing, monitoring, documenting, or deploying an API.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# API Engineer
|
|
7
7
|
|
|
8
8
|
## Foundations
|
|
9
|
+
Postman's skills, CLI, and Context Graph help coding agents go faster from development to production with higher accuracy and fewer tokens. Work from local files and workspace context first.
|
|
10
|
+
|
|
9
11
|
1. Contract comes first. Establish and document the contract before starting implementation.
|
|
10
12
|
2. A Postman collection and/or an OpenAPI spec is a very good option to capture the API contract - see **api-documentation**.
|
|
11
13
|
3. Always validate the change against the contract you started with. Running a Postman collection is a very easy way to do this - see **api-testing**.
|
|
12
|
-
4. Always propose next steps. Example: contract -> implementation -> testing ->
|
|
13
|
-
5.
|
|
14
|
-
6. Don't push to the
|
|
14
|
+
4. Always propose next steps. Example: contract -> implementation -> testing -> updating the workspace context -> sharing with others.
|
|
15
|
+
5. When the code you're changing calls other services over HTTP, use **api-simulation**: the Context Graph finds those dependencies, `postman dependency add mock <name-or-id>` pulls in each owner's mock that search finds (see **api-discovery**), and your real service runs against all of them on one port. For a stand-in backend with no real service of your own, use **api-mocking**. Mocks and simulations are persisted in your repo, so they're cheap to reuse when you resume.
|
|
16
|
+
6. Don't push to the workspace (`postman workspace push`) without user consent. The recommended way to push is a CI step on PR merge - see **ci-integration**.
|
|
15
17
|
7. For high-quality API search results, use **api-discovery**.
|
|
16
18
|
8. No is an acceptable answer. Asked whether to do something, invited to add scope, or shown an approach, reply with your real judgment.
|
|
17
|
-
9. Prefer filesystem-first Postman workflows.
|
|
18
|
-
`postman
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
validate them, and push only with user consent; sharing an already-bound
|
|
22
|
-
workspace means `workspace push`, not creating a duplicate. See
|
|
23
|
-
**bootstrap** for the lifecycle decision table.
|
|
19
|
+
9. Prefer filesystem-first Postman workflows. A quick overview of starting the work could be (more details in **bootstrap**):
|
|
20
|
+
-> if starting a new project: start by doing `postman init`. This scaffolds the relevant folders and provisions an agent-scoped session without requiring a normal Postman login.
|
|
21
|
+
-> if another service is only a dependency or something you want to inspect, use **api-discovery** and `postman dependency add <type> <name-or-id>` to add the relevant collection, environment, or mock. This materializes that entity under `postman/.dependencies/` and records it in `.postman/resources.yaml` without changing this repository's workspace binding.
|
|
22
|
+
Work against those local files and validate them. When the repository is bound to a workspace, push to persist that context for future work and share it with others.
|
|
24
23
|
10. When actual use exposes a concrete Postman CLI gap or a misleading skill, handle the user's task first — then use `postman feedback` to report the gaps/bugs. Exclude secrets, user data, and proprietary content
|
|
24
|
+
11. Postman secret variables let agents resolve user secrets without reading their values. See the **variables** skill.
|
|
25
25
|
|
|
26
26
|
## Dos
|
|
27
27
|
1. Prove it works - validate the task against the contract. See **api-testing**.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: api-mocking
|
|
3
|
-
description: Stands up a fake backend that behaves like a real API — from a collection or an OpenAPI spec, running locally or pushed to
|
|
3
|
+
description: Stands up a fake backend that behaves like a real API — from a collection or an OpenAPI spec, running locally or pushed to a workspace and deployed at a durable URL — plus request-time scenario and status-code overrides for testing failure paths. Use when the user asks to "mock this API," "create a mock server," "fake the backend," "run tests without hitting the real API," or "simulate an error/out-of-stock response." Covers `postman mock`. Depends on bootstrap for the workspace id only once a mock is pushed to the workspace (`-w`, or the workspace linked in `.postman/resources.yaml`) — generating and running a mock locally needs nothing from bootstrap.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# API Mocking
|
|
@@ -9,25 +9,29 @@ description: Stands up a fake backend that behaves like a real API — from a co
|
|
|
9
9
|
|
|
10
10
|
This skill covers the Postman CLI (`postman mock`) for **Code Mocks** — the
|
|
11
11
|
code-based mock product. It is not the postman-app UI (Local Mode sidebar,
|
|
12
|
-
Agent Mode tools, Simulations), nor the older classic/collection mocks that
|
|
12
|
+
Agent Mode tools, the Simulations surface), nor the older classic/collection mocks that
|
|
13
13
|
serve saved collection examples from a `*.mock.pstmn.io` URL — that's a
|
|
14
14
|
different product (the MCP `createMock` flow), not this skill.
|
|
15
15
|
|
|
16
16
|
A **local** mock is two files on disk: `config.yaml` (name, port, scenarios) and
|
|
17
17
|
`default.js` — a plain Node HTTP server, and the mock itself, not a wrapper
|
|
18
18
|
around one. Generating, inspecting, running, and calling a local mock work for
|
|
19
|
-
a logged-out guest. Only sharing it — pushing to the
|
|
19
|
+
a logged-out guest. Only sharing it — pushing to the workspace and deploying a
|
|
20
20
|
durable URL — needs `postman login`. The progression is one model in three
|
|
21
|
-
places: local folder ──`push`──▶ Code Mock (
|
|
21
|
+
places: local folder ──`push`──▶ Code Mock (workspace definition) ──`deploy`──▶
|
|
22
22
|
Mock Server (the reachable URL).
|
|
23
23
|
|
|
24
24
|
Default path: write a local folder, then `mock push` later if something other
|
|
25
25
|
than you needs to hit it over the network (a teammate, CI elsewhere, a webhook
|
|
26
26
|
sender). A purely local mock answering a `postman request` on your machine
|
|
27
|
-
never needs
|
|
27
|
+
never needs a workspace. The exception is `generate -w`, which creates the mock in a
|
|
28
28
|
workspace only and writes no local files — use that only when you intentionally
|
|
29
29
|
skip the repo copy.
|
|
30
30
|
|
|
31
|
+
Mocking another team's service, or the services your own service calls? Use
|
|
32
|
+
`api-simulation`, which checks each owner's workspace for a published mock
|
|
33
|
+
before anything is generated here.
|
|
34
|
+
|
|
31
35
|
## Process
|
|
32
36
|
|
|
33
37
|
1. **Generate.** `postman mock generate -n NAME` with no source scaffolds a
|
|
@@ -42,7 +46,7 @@ skip the repo copy.
|
|
|
42
46
|
the default handler from the source in place, keeping the existing name,
|
|
43
47
|
port, and scenarios. `--update` still needs the `SOURCE`; it cannot be
|
|
44
48
|
combined with `--output`.
|
|
45
|
-
- `-w <workspaceId>` saves the mock to a
|
|
49
|
+
- `-w <workspaceId>` saves the mock to a workspace *instead of* the
|
|
46
50
|
repository — it writes no local files and requires being logged in. Cannot
|
|
47
51
|
be combined with `--output`, `--force`, or `--update`. Prefer local
|
|
48
52
|
generate + later `push` when you still need `mock run` from a folder.
|
|
@@ -66,18 +70,18 @@ skip the repo copy.
|
|
|
66
70
|
`mock run` it again.
|
|
67
71
|
4. **Push it, if it needs to leave your machine.**
|
|
68
72
|
`postman mock push ./postman/mocks/NAME` is safe to re-run — `Created` the
|
|
69
|
-
first time, `Updated` after — and records the
|
|
73
|
+
first time, `Updated` after — and records the workspace mapping in
|
|
70
74
|
`.postman/resources.yaml`; commit that change. If a mock server is already
|
|
71
75
|
live for this mock, the push updates what it serves.
|
|
72
76
|
5. **Deploy it, for a URL that outlives your terminal.**
|
|
73
|
-
`postman mock deploy
|
|
77
|
+
`postman mock deploy WORKSPACE_MOCK_ID -s SLUG -y` prints
|
|
74
78
|
`https://SLUG.mock.<team-domain>.postman.dev`. Deployed private by default —
|
|
75
79
|
callers need a Postman API key (`x-api-key`) — add `--public` only when the
|
|
76
80
|
mock should be reachable by anyone with the URL. `--auto-deploy` re-publishes
|
|
77
81
|
the live server automatically whenever the mock changes; even without it, a
|
|
78
82
|
later `push` already updates a live server, so you only re-`deploy` for the
|
|
79
83
|
first URL or after taking the server down.
|
|
80
|
-
6. **See who's calling it.** `postman mock get
|
|
84
|
+
6. **See who's calling it.** `postman mock get WORKSPACE_MOCK_ID` (table or `--json`)
|
|
81
85
|
returns `mockServerId`; feed that into `postman mock log MOCK_SERVER_ID` for
|
|
82
86
|
call entries (filter with `--method` / `--status` / `--path` / `--since` /
|
|
83
87
|
`--until` / `--limit`, or `--json`). An empty log means the URL genuinely
|
|
@@ -86,34 +90,58 @@ skip the repo copy.
|
|
|
86
90
|
7. **Tear down.**
|
|
87
91
|
- Local: `postman mock delete ./path --yes` — refuses while that mock is
|
|
88
92
|
**running** locally; stop `mock run` first.
|
|
89
|
-
-
|
|
93
|
+
- Workspace: `postman mock delete WORKSPACE_MOCK_ID --yes` — refuses while the mock is
|
|
90
94
|
**running locally** *or* **deployed**; stop the local run and take the
|
|
91
95
|
mock server down first.
|
|
92
|
-
|
|
96
|
+
Workspace deletion doesn't touch `.postman/resources.yaml`; drop that line by hand
|
|
93
97
|
afterward or the repo keeps claiming a mock that's gone.
|
|
94
98
|
|
|
95
99
|
To point real request/assertion runs at a mock instead of hand-editing
|
|
96
100
|
base-URL variables, see the `api-testing` skill's `--use-mock`/`--mock` flags
|
|
97
101
|
on `collection run`.
|
|
98
102
|
|
|
103
|
+
To develop a service against mocks of every upstream it calls, started together
|
|
104
|
+
as one simulation, see the `api-simulation` skill and `postman simulation run`.
|
|
105
|
+
|
|
106
|
+
## Stateful mocks
|
|
107
|
+
|
|
108
|
+
A mock generated from a collection or spec replays its examples, so a `POST`
|
|
109
|
+
and a later `GET` share nothing. To make a mock remember, keep its records in
|
|
110
|
+
`pm.state` inside `default.js`. `pm.state` is the mock's async key-value
|
|
111
|
+
store: `get`, `set`, `delete`, `has`, `keys` and `clear`. The sample from
|
|
112
|
+
`postman mock generate -n NAME` already uses it, and shows the pattern.
|
|
113
|
+
|
|
114
|
+
Every `mock run` or `simulation run` starts the mock with empty state; during
|
|
115
|
+
the run, its handler creates, reads, updates and deletes records through
|
|
116
|
+
`pm.state`. A deployed mock server keeps its state between requests.
|
|
117
|
+
|
|
118
|
+
- Set `interceptRequests: true` in `config.yaml` so `req.body` and `req.query`
|
|
119
|
+
arrive parsed.
|
|
120
|
+
- Seed the starting records on first use, and add a reset route that clears
|
|
121
|
+
and reseeds them, so a test can start clean.
|
|
122
|
+
- Answer an unknown id with the real service's 404, not an empty `200`.
|
|
123
|
+
- Every caller shares one state unless it sends `x-mock-session`. Locally that
|
|
124
|
+
needs `interceptRequests: true`; a deployed server reads it only when the
|
|
125
|
+
mock has sessions enabled.
|
|
126
|
+
|
|
99
127
|
## The two ids that matter
|
|
100
128
|
|
|
101
129
|
- **Code-mock id** — the `id` in `config.yaml`, and the id that `push` and
|
|
102
130
|
`generate -w` print (often the same value). Use it for `get`, `deploy`,
|
|
103
|
-
`delete`, and `run` by
|
|
104
|
-
- **`mockServerId`** — a different value from `mock get
|
|
131
|
+
`delete`, and `run` by workspace mock id.
|
|
132
|
+
- **`mockServerId`** — a different value from `mock get WORKSPACE_MOCK_ID` (table or
|
|
105
133
|
`--json`). Use it for `mock log`, and nothing else.
|
|
106
134
|
|
|
107
135
|
## Critical Rules
|
|
108
136
|
|
|
109
|
-
1. **When you're not signed in, every gated
|
|
137
|
+
1. **When you're not signed in, every gated workspace command fails closed:**
|
|
110
138
|
`Authentication required. Run postman login or provide --api-key`, exit 1,
|
|
111
139
|
nothing half-done. (Signed in but lacking access fails differently — a
|
|
112
140
|
permission or missing-workspace error.) Whether a command is gated is decided
|
|
113
141
|
by what you pass it, not the verb — `mock get`/`mock run` take either a local
|
|
114
|
-
path (ungated) or a
|
|
142
|
+
path (ungated) or a workspace mock ID (gated); `mock list` is gated only when called
|
|
115
143
|
with no path.
|
|
116
|
-
2. **`push` is what moves an existing local mock to the
|
|
144
|
+
2. **`push` is what moves an existing local mock to the workspace — `-w` at
|
|
117
145
|
`generate` time is optional, not a fork you must choose up front.** A mock
|
|
118
146
|
built as a guest can be pushed and deployed later with no rework. Do not
|
|
119
147
|
assume `generate -w` left a `postman/mocks/NAME/` folder to `run`.
|
|
@@ -132,8 +160,8 @@ on `collection run`.
|
|
|
132
160
|
## Verification
|
|
133
161
|
|
|
134
162
|
A mock isn't done because `generate` or `run` exited 0 — hit it with
|
|
135
|
-
`postman request` and check the actual status/body, or `mock get
|
|
136
|
-
--json` for a
|
|
163
|
+
`postman request` and check the actual status/body, or `mock get WORKSPACE_MOCK_ID
|
|
164
|
+
--json` for a pushed one, then state whether it ended up local, pushed, or deployed, and
|
|
137
165
|
(if deployed) private or public. For a scenario check, confirm a *valid* name
|
|
138
166
|
from `config.yaml` actually changed the response — a typo'd name falls back to
|
|
139
167
|
the default, so a 200 alone proves nothing. For a status-code check, use an
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: api-simulation
|
|
3
|
+
description: The local development loop for a service that calls other APIs — find its dependencies in the code and the Context Graph, give each one a mock (the owning team's, or one generated from its contract), serve them together from one `.sim.yaml` on one port, run the real service against it, read the simulation's log to see what each dependency served, and ship it with the simulation in the PR. Can also inject latency, error, rate-limit or chaos per dependency. Use when the user is building or changing an endpoint that calls other services, or asks to "simulate my dependencies," "mock everything this service calls," or "run this end to end locally." Covers `postman simulation`. Builds on api-discovery for finding dependencies and their owners' mocks, and on api-mocking for the rest.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# API Simulation
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
A simulation serves several mocks on one local port. Each member has a
|
|
11
|
+
**routeKey**, its address: with `routing: path` (the default) a dependency
|
|
12
|
+
lives at `http://localhost:<port>/<routeKey>`, and the routeKey is stripped
|
|
13
|
+
before the mock sees the request. The service under test runs for real and is
|
|
14
|
+
never a member.
|
|
15
|
+
|
|
16
|
+
```yaml
|
|
17
|
+
# postman/simulations/feeds-dev.sim.yaml
|
|
18
|
+
simulation: feeds-dev
|
|
19
|
+
routing: path
|
|
20
|
+
mocks:
|
|
21
|
+
- routeKey: acs
|
|
22
|
+
path: ../mocks/acs/config.yaml
|
|
23
|
+
- routeKey: workspaces
|
|
24
|
+
path: ../.dependencies/mocks/workspace-service/config.yaml
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Running a local `.sim.yaml` works signed out. Running one by id, the Context
|
|
28
|
+
Graph, `search`, `dependency` and anything in a workspace need `postman login`.
|
|
29
|
+
`postman simulation` needs Postman CLI 1.70.0 or later (`postman update`);
|
|
30
|
+
earlier releases have only the older `postman simulate run`.
|
|
31
|
+
|
|
32
|
+
## Commands
|
|
33
|
+
|
|
34
|
+
Most commands work either on a local `.sim.yaml` or on a simulation in a
|
|
35
|
+
workspace:
|
|
36
|
+
|
|
37
|
+
| Command | With a `.sim.yaml` path | With a simulation id |
|
|
38
|
+
| --- | --- | --- |
|
|
39
|
+
| `create -n <name> --mock <ref>=<routeKey>` | Writes `postman/simulations/<name>.sim.yaml` from mock paths | Creates one in the workspace from mock ids (`-w`). Don't mix the two |
|
|
40
|
+
| `list` | Lists `postman/simulations/` | `-w <workspaceId>` lists the workspace's |
|
|
41
|
+
| `get <pathOrId>` | Reads the file | Fetches it |
|
|
42
|
+
| `run <pathOrId>` | Serves the members on one port | Fetches the members, then serves them locally |
|
|
43
|
+
| `push <path>` | Uploads each member mock, then the simulation, to the linked workspace. A member pulled with `dependency add` goes up as a copy | — |
|
|
44
|
+
| `deploy <id>` | — | Serves it at a URL, **public by default** (rule 7). `--private` requires an API key |
|
|
45
|
+
| `delete <pathOrId>` | Deletes the file, not its mocks | Deletes it and takes its URL down |
|
|
46
|
+
|
|
47
|
+
## Process
|
|
48
|
+
|
|
49
|
+
1. **Find what the change calls.** If a `.sim.yaml` in `postman/simulations/`
|
|
50
|
+
already covers the change, skip to step 4. Otherwise read the code path
|
|
51
|
+
you're changing: its HTTP clients, their base-URL settings, and any call
|
|
52
|
+
the change adds. Only HTTP dependencies become members; queues, databases
|
|
53
|
+
and caches run as real local instances. When signed in, check that list
|
|
54
|
+
against the Context Graph, which also sees calls made through shared code
|
|
55
|
+
and other repositories:
|
|
56
|
+
`postman context-graph ask "What services and APIs does <repo-name> call?" --wait`.
|
|
57
|
+
Use the repository's name from `git remote get-url origin`, not the
|
|
58
|
+
folder's: a wrong name answers "no dependencies", not an error. A call the
|
|
59
|
+
change adds isn't in the graph yet.
|
|
60
|
+
|
|
61
|
+
2. **Get each dependency's mock from its owner first.** This needs
|
|
62
|
+
`postman login`; signed out, ask the user to sign in rather than writing
|
|
63
|
+
mocks (rule 5). For every dependency, in this order:
|
|
64
|
+
1. Find the owner's workspace:
|
|
65
|
+
`postman context-graph ask "Which Postman workspace holds the collections and mocks for <service>?" --wait`.
|
|
66
|
+
If the workspace it names has neither, find the owner's with
|
|
67
|
+
`postman search workspaces "<service>"`.
|
|
68
|
+
2. List that workspace's mocks:
|
|
69
|
+
`postman search mocks --filter "workspaceId=<id>"`. A name match in
|
|
70
|
+
another workspace is not ownership.
|
|
71
|
+
3. If the owner has one, pull it with `postman dependency add mock <mockId>`
|
|
72
|
+
and use the copy as it is, even if the owner also deployed it.
|
|
73
|
+
4. Only if the owner has none, make one: pull their collection with
|
|
74
|
+
`postman dependency add collection <id>` and run `postman mock generate`
|
|
75
|
+
on it (see `api-mocking`). Keep the endpoints this path calls and answer
|
|
76
|
+
`501` for the rest, so a missed call fails loudly. Make it stateful when
|
|
77
|
+
the change writes something and reads it back.
|
|
78
|
+
|
|
79
|
+
3. **Compose the simulation.**
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
postman simulation create -n feeds-dev \
|
|
83
|
+
--mock ./postman/mocks/acs=acs \
|
|
84
|
+
--mock ./postman/.dependencies/mocks/workspace-service=workspaces
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Each `=<routeKey>` names a member: lowercase letters, digits, `-` and `_`.
|
|
88
|
+
Schema: [reference/sim_yaml_schema.md](reference/sim_yaml_schema.md).
|
|
89
|
+
|
|
90
|
+
4. **Run it and point the service at it.**
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
postman simulation run postman/simulations/feeds-dev.sim.yaml --port 4900 --output ndjson > sim.ndjson 2> sim.err &
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Keep stderr out of `sim.ndjson` so every line parses; start-up errors land
|
|
97
|
+
in `sim.err`. It's up when the `listening` event lists each member's
|
|
98
|
+
address. Set each upstream base URL to `http://localhost:4900/<routeKey>`,
|
|
99
|
+
never to a mock server's URL.
|
|
100
|
+
|
|
101
|
+
Stop it with
|
|
102
|
+
`pkill -INT -f "simulation run postman/simulations/feeds-dev.sim.yaml"`:
|
|
103
|
+
every member stops, and the closing `summary` event counts the requests
|
|
104
|
+
served and failed. `kill $!` reaches only the `postman` launcher, which
|
|
105
|
+
ignores it, so the simulation keeps serving. In a terminal of its own,
|
|
106
|
+
Ctrl+C works too.
|
|
107
|
+
|
|
108
|
+
5. **Develop and test, reading the simulation log after each call.** Call
|
|
109
|
+
the changed route on the real service with `postman request` or its
|
|
110
|
+
collection (see `api-testing`). Every request a member served is a
|
|
111
|
+
`request` event in `sim.ndjson`, with its `routeKey`, `method`, `path`,
|
|
112
|
+
`statusCode`, `duration` in milliseconds, and both bodies. Check that each
|
|
113
|
+
upstream call you expect is there, on the right member, with the status you
|
|
114
|
+
expect. Restart the service after a code change, and the simulation after a
|
|
115
|
+
mock edit. A response from the simulation itself means wiring, not code:
|
|
116
|
+
- `404 Unknown simulation member`: the path has no valid routeKey. The body
|
|
117
|
+
lists the valid ones. It isn't logged.
|
|
118
|
+
- `501`: an upstream call you haven't modelled yet. Add it to your own
|
|
119
|
+
mock. For a pulled one, fork it into `postman/mocks/` first.
|
|
120
|
+
- `502 Proxy error`: the member's handler crashed. See `sim.ndjson`.
|
|
121
|
+
|
|
122
|
+
6. **Optionally, fail a dependency.** Worth doing when the change has to
|
|
123
|
+
survive an upstream that errors or is slow, but not required. Copy the
|
|
124
|
+
baseline `.sim.yaml`, give that member a condition, run the service
|
|
125
|
+
against it, and report what it did.
|
|
126
|
+
|
|
127
|
+
```yaml
|
|
128
|
+
- routeKey: workspaces
|
|
129
|
+
path: ../.dependencies/mocks/workspace-service/config.yaml
|
|
130
|
+
scenarios:
|
|
131
|
+
- overrides:
|
|
132
|
+
conditions:
|
|
133
|
+
error: { status_code: 503 }
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
The conditions are `latency`, `error`, `rate_limit` and `chaos`; the schema
|
|
137
|
+
has all four. Each applies to every route on that member, and the first to
|
|
138
|
+
fail a request ends it, so fail a dependency one way at a time. An injected
|
|
139
|
+
error, `429` or chaos failure never reaches the mock and isn't logged, so
|
|
140
|
+
judge it by what the service returned.
|
|
141
|
+
|
|
142
|
+
7. **Ship.** Commit your mocks, the `.sim.yaml` files and
|
|
143
|
+
`.postman/resources.yaml`, but not `sim.ndjson`: it records request
|
|
144
|
+
headers and bodies, tokens included. After a clone,
|
|
145
|
+
`postman dependency install` restores the pulled mocks. Running the
|
|
146
|
+
simulation in CI is optional: start it as in step 4 before the test step,
|
|
147
|
+
and stop it after.
|
|
148
|
+
|
|
149
|
+
## Critical Rules
|
|
150
|
+
|
|
151
|
+
1. **The service under test is never a member.** If nothing real runs, use
|
|
152
|
+
`api-mocking`.
|
|
153
|
+
2. **Unique routeKeys and an explicit `--port`.** A member's own `port:` is
|
|
154
|
+
ignored. Without `--port`, a busy 3000 silently moves to a random port.
|
|
155
|
+
3. **Members share one `pm.state` store.** Two stateful mocks that use the
|
|
156
|
+
same key read and overwrite each other's records, so give each its own keys.
|
|
157
|
+
4. **Only `overrides.conditions` inject faults.** A bare
|
|
158
|
+
`scenarios: - path: …/default.js` entry, as the Postman app writes, adds
|
|
159
|
+
none. `overrides.bypass` is ignored.
|
|
160
|
+
5. **Never write or generate a mock for a dependency before searching its
|
|
161
|
+
owner's workspace.** A mock you write encodes your assumptions; the
|
|
162
|
+
owner's encodes their contract and their state.
|
|
163
|
+
6. **Pull an owner's mock even when it's deployed.** Only members take faults
|
|
164
|
+
and log requests, and a private mock server needs an `x-api-key` your
|
|
165
|
+
service won't send. Never edit `postman/.dependencies/`:
|
|
166
|
+
`dependency update` overwrites it. To change a pulled mock, copy it into
|
|
167
|
+
`postman/mocks/` and call it a fork.
|
|
168
|
+
7. **Confirm before a public deploy.** `simulation deploy` without
|
|
169
|
+
`--private`, or with `-y`, serves every member's mock to anyone with the
|
|
170
|
+
URL. Ask the user first; a local `run` needs no deploy at all.
|
|
171
|
+
|
|
172
|
+
## Verification
|
|
173
|
+
|
|
174
|
+
Starting is not the same as being used. Before you report, read the whole
|
|
175
|
+
log: every member on the path should have `request` events with the statuses
|
|
176
|
+
you expect. A member with none isn't wired or isn't on this path.
|
|
177
|
+
|
|
178
|
+
Report:
|
|
179
|
+
|
|
180
|
+
- **How the simulation performed:** for each member, the requests it served
|
|
181
|
+
and their statuses, and anything unexpected, such as a `501`, a `502` or a
|
|
182
|
+
member that was never called.
|
|
183
|
+
- **Where each mock came from:** for each dependency, the owner workspace you
|
|
184
|
+
searched, what you found there, and whether its mock was pulled, forked or
|
|
185
|
+
generated here.
|
|
186
|
+
- **What ran for real.**
|