Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- A `docker-compose` repo type for `/new-repo`: a minimal `compose.yaml` (named volumes for data,
committed config, `.env` secrets), `.env.example`, `.dockerignore`, a README with a workflow
diagram, and a `docker compose config -q` validation CI (Public tier).
- A `power-platform-connectors` repo type for `/new-repo`: turns a committed Postman collection
into **OpenAPI 2.0** custom-connector definitions for Microsoft Power Platform. A pinned-Docker
generator (`postman-to-openapi` + `api-spec-converter`, normalised to valid Swagger 2.0) splits
per top-level folder **only when a single definition would exceed the 1 MB limit**, self-validates
every output, and a scheduled sync workflow opens a PR when the upstream collection changes. The
collection is committed, so the repo builds with just Docker — no Postman account.
- The `repo-standard` skill now tells agents to check the remote after a push/PR — CI/Actions status and GitHub Copilot / reviewer feedback — before calling work done.

### Changed
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,9 @@ make the first commit. Nothing is pushed or published — you do that when you'r

## Profiles

- **Type — what the repo *is*:** `powershell-module` and `docker-compose` (built out), plus `skill-plugin`,
`collection`, `mcp-server`, `app-ts`, `app-python`, `script-collection` (stubs, filled as needed).
- **Type — what the repo *is*:** `powershell-module`, `docker-compose`, and
`power-platform-connectors` (built out), plus `skill-plugin`, `collection`, `mcp-server`,
`app-ts`, `app-python`, `script-collection` (stubs, filled as needed).
- **Tier — ceremony, set by visibility:** **Core** (every repo), **+Public**, **+Published**.

See [`the standard`](plugins/repokit/skills/repo-standard/standard/the-standard.md) for the full
Expand Down
55 changes: 55 additions & 0 deletions docs/adr/0003-power-platform-connectors-type.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# ADR-0003: `power-platform-connectors` repo type

- **Status:** accepted
- **Date:** 2026-07-04

## Context

We needed a repo **type** that turns a Postman collection into OpenAPI definitions ready to import
as **Microsoft Power Platform custom connectors**. Verified hard constraints from Microsoft Learn
(*Create a custom connector from an OpenAPI definition*): the definition must be **OpenAPI 2.0 /
Swagger** ("OpenAPI 3.0 format is not supported") and **< 1 MB**, `.json` or `.yaml`, with a single
top security definition (client-credentials OAuth is rejected).

## Decision

- **Converter (free, pinned in Docker):** `postman-to-openapi@3.0.1` outputs OpenAPI **3.0** only,
so we downconvert with `api-spec-converter@2.12.0` (`--from=openapi_3 --to=swagger_2`). Both are
unmaintained (2023 / 2021; the latter depends on the deprecated `request`) and do **not** run on
current Node — the image is pinned to **Node 18**. Do not bump without re-testing. APIMatic (paid,
direct Postman→2.0) is the documented escape hatch, not built.
- **The downconvert is lossy, so `generate.mjs` normalises the output to valid Swagger 2.0** — this
was proven empirically (a real public collection failed validation four different ways before the
fixups): (1) **pre-resolve collection `{{variables}}`** so `host`/`basePath`/`schemes` are real
instead of `%7B%7Bbaseurl%7D%7D`; (2) **derive `securityDefinitions` from the Postman `auth`**
block (p2o mis-maps apikey to an invalid `type:http` and drops the header name); (3) **backfill a
`description` on every response** (required in 2.0; p2o only sets it from the Postman `status`);
(4) **add missing path parameters and leading `/`** on path keys. The generator then
**self-validates** each output with `swagger-cli` and asserts **< 1 MB**, exiting non-zero on any
failure.
- **Split policy:** convert the whole collection to one definition; ship one file if it's < 1 MB;
otherwise split **per top-level folder** (each carrying the collection `variable`/`auth`); if a
single folder is still ≥ 1 MB after stripping example fields, **flag it** rather than ship an
un-importable file.
- **Self-containment / source:** the collection is **committed** (`source/collection.json`), so the
repo builds with just Docker — no Postman account. **Sync** fetches a **configurable `sourceUrl`**:
a public URL (GitHub raw / vendor site) is **account-free**; a Postman-platform-only collection
needs the maintainer's optional `POSTMAN_API_KEY` secret (never needed by cloners). Postman has no
reliable anonymous fetch for a collection you don't own (public JSON links are deprecated).
- **Change detection = scheduled workflow + hash:** `sync.yml` (daily cron + dispatch) canonicalises
the fetched collection, SHA-256s it, compares to `.postman/manifest.json`, and on a change updates
the snapshot, regenerates, **validates inline**, and opens a PR. Validation is inline because a PR
opened by the default `GITHUB_TOKEN` does not trigger `ci.yml`. This needs
`permissions: pull-requests: write` and the repo setting *Allow GitHub Actions to create and
approve pull requests*.
- **Tier: Core + Public, no Published** (like `docker-compose`) — "publishing" is manually importing
a definition into Power Platform; there is no registry step to automate.

## Consequences

- The conversion is lossy — the pipeline validates and opens a **PR to review**; it never
auto-imports. Richer collections (saved example responses, one clear auth scheme) produce better
connectors.
- The toolchain is stale but pinned in Docker; if a future Node breaks it, the pin holds.
- Auto-sync is account-free for public-URL sources; Postman-platform-only collections need the
maintainer's optional key.
6 changes: 3 additions & 3 deletions plugins/repokit/skills/new-repo/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ Gather these from the user's arguments / message, else ask — keep it to load-b

- **name** — the repo / directory name (kebab-case).
- **description** — one line.
- **type** — one of: `powershell-module`, `docker-compose`, `skill-plugin`, `collection`, `mcp-server`, `app-ts`,
`app-python`, `script-collection`.
- **type** — one of: `powershell-module`, `docker-compose`, `power-platform-connectors`,
`skill-plugin`, `collection`, `mcp-server`, `app-ts`, `app-python`, `script-collection`.
- **visibility** — `private` (= Core tier), `public` (= +Public), or `published` (= +Published).
- **author** — default `Peter Braun` (`PBNZ`).
- **license** — default `Apache-2.0`.
- For `powershell-module` only: **ModuleName** (PascalCase, e.g. `MyModule`).

If the chosen type is a **stub** (anything other than `powershell-module` or `docker-compose`), tell the user so: the
If the chosen type is a **stub** (anything other than `powershell-module`, `docker-compose`, or `power-platform-connectors`), tell the user so: the
Core/Public/Published files get stamped, but there's no type-specific structure yet. Confirm they
want to continue.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"sourceUrl": "",
"sha256": ""
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Pinned toolchain for generating Power Platform custom-connector definitions.
#
# Node is pinned to 18 on purpose: the converters are unmaintained
# (postman-to-openapi 2023, api-spec-converter 2021 — the latter depends on the
# deprecated `request` module) and do NOT run on current Node. Do not bump the
# Node line or the tool versions without re-testing the whole pipeline.
FROM node:18-bullseye-slim

# --ignore-scripts blocks install-time lifecycle scripts (defence-in-depth for this stale,
# transitive-heavy dependency tree). These three tools are pure JS and run fine without them.
RUN npm install -g --ignore-scripts postman-to-openapi@3.0.1 api-spec-converter@2.12.0 @apidevtools/swagger-cli@4.0.4

WORKDIR /work
# The repo is mounted at /work at run time; run: node scripts/generate.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# {{name}}

{{description}}

OpenAPI **2.0** connector definitions for **Microsoft Power Platform** custom connectors,
generated from a Postman collection. Each definition is valid Swagger 2.0 and kept **under 1 MB**
(Power Platform's limits — 3.0 is not supported). The collection is **committed to this repo**, so
anyone can regenerate the connectors with just Docker — no Postman account needed.

## How it works

```mermaid
flowchart LR
Src["source/collection.json<br/>(committed Postman collection)"] --> Gen["generate.mjs, in Docker"]
Gen --> Q{"whole def under 1 MB?"}
Q -->|yes| One["one connectors/*.swagger.json"]
Q -->|no| Split["split per top-level folder"]
One --> Val["validate: Swagger 2.0 + under 1 MB"]
Split --> Val
Val --> Imp["import into Power Platform"]
```

## Use it

1. Export your Postman collection (v2.1) and save it as **`source/collection.json`**.
2. Set `sourceUrl` in **`connectors.config.json`** — a public URL to the collection JSON (a GitHub
raw file or the vendor's site), or the Postman API endpoint if the collection lives only in
Postman.
3. Generate the definitions (one Docker command — no local Node needed):

```sh
docker build -t {{name}}-gen .
docker run --rm -v "${PWD}:/work" {{name}}-gen node scripts/generate.mjs

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Running the Docker container as root locally can result in generated files in the workspace being owned by root, which prevents local developers from editing or deleting them without using sudo. Passing --user "$(id -u):$(id -g)" ensures files are created with the correct host user ownership.

   docker run --rm --user "$(id -u):$(id -g)" -v "${PWD}:/work" {{name}}-gen node scripts/generate.mjs

```

*(On Linux/macOS, add `--user "$(id -u):$(id -g)"` to the `docker run` so the generated files
aren't owned by root.)*

It writes `connectors/*.swagger.json` — **one file if the whole collection fits under 1 MB,
otherwise one per top-level folder**. Every file is validated as Swagger 2.0 and checked < 1 MB;
the run fails if any definition is invalid or oversize.
4. In Power Platform (Power Automate / Power Apps / Logic Apps): **New custom connector → Import an
OpenAPI file** → pick a `connectors/*.swagger.json`.

## Keeping it up to date

- **CI** (`.github/workflows/ci.yml`) rebuilds + regenerates + validates on every change to
`source/`, `scripts/`, the `Dockerfile`, or the config.
- **Auto-sync** (`.github/workflows/sync.yml`) runs daily: it fetches `sourceUrl`, and if the
upstream collection changed, it updates `source/collection.json`, regenerates, validates, and
opens a **PR** for you to review. Account-free when `sourceUrl` is a public URL. If the collection
lives only in Postman, add a `POSTMAN_API_KEY` repo secret (only the maintainer needs it — never
cloners), and enable *Settings → Actions → Allow GitHub Actions to create and approve pull requests*.

## Heads-up — review before importing

The Postman → Swagger 2.0 conversion is **lossy** (it drops `oneOf/anyOf`, `nullable`, and derives
response schemas from saved example responses). The pipeline fixes the common breakages
(host/basePath, security definitions, response descriptions) and validates every output, but
**review the connector in the PR before importing**. Richer collections — with saved example
responses and a single clear auth scheme — produce better connectors. Power Platform also picks the
single top security definition and rejects OAuth client-credentials.

See [`AGENTS.md`](AGENTS.md) for the START-HERE map. Follows the
[RepoKit](https://github.com/PBNZ/repo-kit) standard.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"sourceUrl": "<fill me: a public URL to the Postman collection JSON, e.g. a GitHub raw file; or the Postman API endpoint if it is Postman-only>",
"sizeLimitBytes": 1048576,
"output": "connectors"
}
Loading
Loading