Skip to content

Release 2.1.0-beta.1 - #464

Open
mrafnadeem-apimatic wants to merge 9 commits into
betafrom
dev
Open

mrafnadeem-apimatic wants to merge 9 commits into
betafrom
dev

Conversation

@mrafnadeem-apimatic

Copy link
Copy Markdown
Contributor

Promotes dev to beta, expected to publish 2.1.0-beta.1 to the npm beta dist-tag as a GitHub prerelease. The run commits nothing back to beta; the tag goes on this merge commit. dev already contains main's 2.0.0 merge, so the version continues from v2.0.0.

Merge with Create a merge commit.

What releases

#453, #455 and #461 are chore and docs commits and stay out of the notes.

After the merge

  • This is the first release since chore: retire CHANGELOG.md and the committed version #453 set package.json's version to 0.0.0-semantically-released. Check that npm view @apimatic/cli dist-tags shows beta: 2.1.0-beta.1 and that npx @apimatic/cli@beta --version reports 2.1.0-beta.1, not the placeholder.

🤖 Generated with Claude Code

saeedjamshaid and others added 9 commits October 26, 2026 13:27
Closes #420. 2.0.0 has shipped and `main` is back in `dev` (52b9c39),
so this merges without conflicts.

## The change

Nothing commits a version or a changelog back to a branch since #409, so
both files would go stale:

- **`CHANGELOG.md`** opens with a line pointing at GitHub Releases. It
keeps the history up to 1.5.0 and drops the 2.0.0-beta.1 and
2.0.0-beta.2 sections, which are in GitHub Releases.
- **`package.json`'s `version`** becomes `0.0.0-semantically-released`,
the placeholder semantic-release's FAQ suggests. Otherwise it stays at
`2.0.0-beta.2`.

## Checked

- Everything in `CHANGELOG.md` from 1.5.0 down is unchanged.
- `@semantic-release/npm` runs `npm version <next> --no-git-tag-version
--allow-same-version` before `npm publish`. On a copy of this
`package.json`, that writes the real version. `oclif.manifest.json` is
generated after that, by `prepack`.
- A build from source: `apimatic --version` prints
`@apimatic/cli/0.0.0-semantically-released`, and the User-Agent and
telemetry report the same version. `npm pack` names the tarball
`apimatic-cli-0.0.0-semantically-released.tgz`, and Portal automation
tests find it with a `*.tgz` glob.

## Not in this PR

`oclif readme` takes the version from `package.json` for the README's
"See code" links and the `--version` example. Run without its
`--version` flag, it now writes `blob/v0.0.0-semantically-released/…`
links, which 404. Nothing runs it automatically; the README's command
section was last regenerated at 1.3.1.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
… files samples by spec (#448)

## The problem

When two merged specs declare the same method and path, such as `GET
/status` on two services, the platform's code samples hold one sample
for the pair, keyed by path and method. The portal cannot tell whose it
is, so since #435 it leaves the SDK sample off such an operation and
warns.

The platform is adding a grouping to its samples file. When the
operations come from two or more spec files,
`code-samples/<language>.json` gains `specs`: each spec's samples, keyed
by its main file relative to `src/spec/`. `paths` stays as it is, so
released CLIs are unaffected.

Tracked in apimatic-io issue 2298.

## What changes

- **Samples filed by spec are placed by spec.** When a samples file has
`specs`, each spec takes the samples filed under its own file, and only
those. The file is matched exactly: `orders/openapi.yaml` for
`src/spec/orders/openapi.yaml`. An operation two specs declare then
shows each spec's own call. A spec with nothing filed under it shows no
SDK sample, rather than calls from `paths` that may be another API's.
- **A file without `specs` is read as before.** Every spec takes
`paths`, less the operations two specs declare. Every samples file the
platform sends today is of this kind.
- **The shared-operation warning now depends on the samples file.** It
moves from before the server run to after it, and appears only for a
file without `specs`.
- **Samples no operation takes are checked within each spec** when they
are filed by spec, and the warning names the spec.
- **Samples filed under a file no spec is read from are named too,** as
samples no operation takes. That happens when the platform builds a
merged folder from another file than the OpenAPI document the portal
reads, such as a Postman collection that sorts first (apimatic-io issue
2307).
- **`code-samples.json` changes shape.** The CLI writes this file for
the template, and it becomes `{ paths, specs }`, with `specs` keyed by
section slug. Each section reads `paths` plus its own entry. A test
holds the CLI's and the template's declarations of the file to one
shape, as it does for `portal.config.json`.
- **Tidy-ups:**
- the samples warnings live together in
`src/prompts/portal/code-samples.ts`;
- `PortalSource.sharedEndpoints` goes, since the placed samples work out
the shared operations.

## What it prints

Without `specs`, the warning reads as before but prints after the
samples arrive:

```
▲  More than one API definition declares this operation, so the portal shows no SDK sample on it:
│    • 'GET /status': 'spec/alpha/alpha.json' and 'spec/beta/beta.json'
```

A sample filed under a spec that declares no such operation:

```
▲  No operation in 'spec/orders/openapi.yaml' takes the code samples for POST /refunds.
```

Samples filed under a file the portal reads no API definition from:

```
▲  No operation in 'spec/inventory/collection.json' takes the code samples for GET /status, GET /stock.
```

## Testing

- `pnpm test` with `APIMATIC_E2E=1`, as CI runs it: 2054 passing, ESLint
clean.
- **Unit:**
- placing samples with and without `specs`, and refusing a malformed
`specs`;
- exact matching: `./orders/openapi.yaml`, `orders\openapi.yaml`,
`Orders/openapi.yaml` and the folder `orders` match nothing, and a spec
in a nested folder matches;
- both warnings, the file no spec is read from among them, and the
template reading `paths` plus its section's entry, including a section
named `constructor`.
- **End to end:**
- A new build of the merged fixture with its samples filed by spec shows
each API's own call on both `GET /status` pages.
  - That build fails when the template is made to ignore `specs`.
- The existing build without `specs` still shows no SDK sample on either
`/status` page.
- **Against production:** a real `portal generate` of the two-spec
fixture. Production files have no `specs` yet, so the result matches
#435's:
  - the warning names `GET /status` with both specs;
  - neither status page carries an SDK sample;
  - the other two pages carry their own calls.

## Not in this PR

- **Nothing changes for users until the platform sends `specs`.** The
backend change is tracked in apimatic-io issue 2298.
- **An operation the platform folds into an identical one from an
earlier spec** is filed under the earlier spec only, so the later spec's
page shows no SDK sample. The fix is on the backend.
- **A merged folder holding another API format beside its OpenAPI
document,** such as a Postman collection that sorts first, gets its SDK
built from that other file. The warning above names the file; the CLI
should refuse such a folder (apimatic-io issue 2307).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…atic/ folder (#457)

`portal generate`, `portal serve` (and so `quickstart`), `sdk generate`
(and so `sdk publish`) and `plugin generate` no longer upload
`src/static/` with the build. Nothing on the server reads it, and the
portal build still copies it from disk. `content/` is still uploaded.

A project whose `static/` folder made the upload too large, such as one
with a few large GIFs, used to fail with "An unexpected error occurred".
It now generates.

## What changed

- **One build upload for all three commands.** `ProjectContext` makes
it: `buildZip` for the portal and the SDK, and `pluginBuildZip` for the
plugin, which also narrows the copy's `languages`. Each copies `src/` to
a temporary folder without `static/`, then zips the copy. So a large
`static/` is no longer copied to disk either.
- **`static/` is matched in any letter case.** On Windows and macOS the
portal build reads a `Static/` folder as `static/`, so that folder is
left out too.
- **The copy holds what links point to.** A linked folder in `src/` used
to fail every upload, and a relative file link broke in the copy that
`sdk generate` and `plugin generate` made. Both are now uploaded as what
they point to, the way the portal build reads them.
- **A build that can't be prepared says why.** All three commands now
report "'…/src' could not be zipped for upload: <reason>", for example
when a file is locked. Before, `portal generate` and `portal serve`
showed "An unexpected error occurred, please try again later", and `sdk
generate` and `plugin generate` crashed with a raw `EBUSY` error. No
partial copy is left behind in the temporary folder.
- **The portal's build is sent like the SDK and plugin builds:** as a
file named by a random ID, of type `application/octet-stream`, rather
than `build.zip` / `application/zip`. Production accepts it.

## How it was verified

- Tests pin each part: the folders left out (any case, top level only),
the links followed, a failed copy reported only once the copy has
stopped, and the upload contents of each command. Each was checked by
breaking the code and seeing a test fail.
- The full suite passes with `APIMATIC_E2E=1`.
- Real runs against production, against 2.0.0:

  | Project | 2.0.0 | This branch |
  |---|---|---|
| 23 MB in `static/`: `portal generate`, `sdk generate`, `plugin
generate` | "An unexpected error occurred" | all three succeed; `portal
serve` serves the files |
| 22 MB in `Static/`: `portal generate` | not run | succeeds, and the
files are published |
| A locked file in `src/` | portal: generic message; sdk and plugin: raw
`EBUSY` crash | all three name the file and the reason |
| A small project with `static/`: `plugin generate` | 69 files |
byte-identical, 69 files |

## Known edge

A link inside `src/` that points back at `src/` or a folder above it is
followed about 63 levels deep before the copy fails with `ELOOP`. That
failure is reported with the same message. Before, such a link failed
straight away.

A clearer message for an upload that is still too large follows
separately.

Refs apimatic/apimatic-io#2316

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
#455)

Follows up the `oclif readme` note in #453. Tracked in apimatic-io issue
2315.

## The problem

The README, which npm shows on the package page, still carries 1.3.1:

- The usage block shows `apimatic --version` printing
`@apimatic/cli/1.3.1`.
- The 13 "See code" links point at `blob/v1.3.1/`, the 1.x source of
each command.
- The License badge links `blob/master/package.json`, which is a 404:
`master` holds the old Python CLI.

`pnpm readme` couldn't repair it. Since #453 it writes
`blob/v0.0.0-semantically-released/` links, and on the CRLF README it
adds a second usage block and a second commands block instead of
replacing them.

## What changes

- **LF README.** `README.md` is stored with LF endings, and a new
`.gitattributes` keeps it LF on every checkout. The first commit is that
conversion alone, so the Files tab shows the whole README as changed;
the other commits have the real changes.
- **Links.** `oclif.repositoryPrefix` in `package.json` makes `pnpm
readme` link each command's source on `dev`, where every command exists.
`prepack` now runs `oclif readme` too, so the README packed for npm
links the release's own tag, which semantic-release pushes before it
publishes. The plugin-autocomplete and plugin-help links still point at
those plugins' own repositories.
- **Usage block.** The version line and the `<!-- usage -->` markers go,
so `pnpm readme` no longer rewrites the block.
- **License badge.** It links `blob/main/LICENSE`.
- The commands section is regenerated, which re-wraps two lines of the
`plugin generate` description.

A `docs:` change makes no release, so npm shows this README from the
next release.

## Checked

- `README.md` has no `1.3.1`, no `apimatic-cli/blob/v` and no
`blob/master`. All 13 linked files exist on `dev`, and `LICENSE` on
`main`.
- `pnpm build && pnpm readme` leaves the README unchanged, with one
commands block and no usage block.
- `npm version 2.1.0 --no-git-tag-version && npm pack` packs a README
whose 13 links point at `blob/v2.1.0/` and which otherwise matches the
committed one; `2.1.0-beta.1` gives `blob/v2.1.0-beta.1/`. At the
placeholder version, `pnpm pack` leaves the README unchanged.
- With the platform faked as Linux and `CI` set, as in the release job,
`oclif manifest && oclif readme` writes the committed README byte for
byte.
- A fresh clone checks the README out with LF under `core.autocrlf`
`true`, `false` and `input`.

## Open PRs that edit the README

- **#438** merges cleanly with `git merge -X renormalize origin/dev`; a
plain merge conflicts on the line endings. Its `portal copilot sync`
link points at `blob/v1.3.1/`, so run `pnpm readme` after the merge.
- **#445** still conflicts, even with `-X renormalize`, in the usage
block and the 13 links. Keep `dev`'s side in each conflict, then run
`pnpm readme`.
- **#454**, stacked on #445, gets the same conflict and takes #445's
resolution.

## Regenerating the README

Run `pnpm readme` after `pnpm build`, with colour on and `COLUMNS`
unset; `.ai/instructions.md` now says so. Our command examples carry
colour codes, so oclif leaves them as written. With colour off, every
example gains a `$ ` prefix. `pnpm build` also deletes any
`oclif.manifest.json`, whose examples `oclif readme` would otherwise
use.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
## Summary

The README's install line is now `npm install -g @apimatic/cli@2`. The
Requirements paragraph now explains why the major is pinned.

## Why

The README said npm "only warns" on a Node older than 24, so the install
succeeds and the CLI then refuses to run. That is true for `@2`, but not
for the bare command the README showed:

- 2.0.0 declares `engines.node >=24.0.0`, and 1.5.0 declares `>=20.0.0`.
- When `latest` fails the running Node's engines check, npm (10.8.2 and
later) skips it and installs the newest release that passes, which is
1.5.0.
- npm prints no warning, and the `init` hook's Node check never runs
because v2 was never installed.

I reproduced this with the official Node 22.23.2 binary and its bundled
npm 10.9.8, installing into a throwaway prefix:

| Command | Result on Node 22.23.2 |
|---|---|
| `npm install -g @apimatic/cli` | Installs **1.5.0** with no warning;
`apimatic --version` → `@apimatic/cli/1.5.0 win32-x64 node-v22.23.2` |
| `npm install -g @apimatic/cli@2` | `npm warn EBADENGINE Unsupported
engine … required: { node: '>=24.0.0' }`, installs 2.0.0, then every
command exits 1 with *"The APIMatic CLI needs Node >=24.0.0; this is
Node 22.23.2. Upgrade Node … and reinstall the CLI."* |

Deprecating 1.x wouldn't change npm's pick; it would only add a warning.
A 2026-10-28 signup on Node 22.23.2 generated SDKs and a portal with
1.5.0 this way.

## Not changed

- `apimatic.schema.json`'s `$id`, `APIMATIC_SCHEMA_URL` (the `$schema`
the scaffold writes) and the test fixtures already point at
`https://cdn.jsdelivr.net/npm/@apimatic/cli@2/…`.
- The `init` hook's comment ("npm and pnpm only warn…") describes a
pinned or local install, where it holds.

Companion PRs pin the same command in the platform app, the skills repo
and apimatic-docs. A separate PR lowers the engine floor to Node 22.12.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
## Summary

The CLI runs on **Node 22.14 or newer** again. `engines.node` goes from
`>=24.0.0` to `>=22.14.0`, the test matrix adds Node 22.14.0 and the
newest 22.x, and `@types/node` is pinned to exactly 22.14.1 to match the
floor.

## Why

- **The 24 floor is policy, not a requirement.** #347 raised it because
"Node 22 reaches end of life on 2027-04-30, inside the supported life of
the next major". It names no API or dependency that needs Node 24. The
CLI itself needs 22.12, which #343 set for the local Fumadocs/TanStack
Start portal build (Vite needs `^20.19.0 || >=22.12.0`). The floor is
22.14 because the repo's own toolchain needs it: pnpm 11 declares
`>=22.13` and semantic-release 25 `^22.14.0 || >=24.10.0`. 22.14 is the
oldest Node the CLI can also be installed, built, tested and released
on.
- **Node 22 is common among real users.** In the last 90 days, 13 of the
40 external users who ran the CLI locally did so on Node 22.
- **On Node 22, v2 is out of reach:**
- A bare `npm install -g @apimatic/cli` silently installs **CLI v1
(1.5.0)**, because npm skips a `latest` release whose `engines` the
running Node fails.
  - A pinned `@2` installs v2, which then refuses to run.
- A 2026-10-28 signup on Node 22.23.2 generated SDKs and a portal with
1.5.0 this way.
- **What this fixes:** with the floor at 22.14, both commands give v2 on
Node 22.14+. Node 20 and 22.0–22.13 still resolve a bare install to
1.5.0. That's why the companion PRs pin `@apimatic/cli@2` in every
install instruction (README #461, the platform app, skills and
apimatic-docs).

## Changes

| File | Change |
|---|---|
| `package.json` | `engines.node`: `>=24.0.0` → `>=22.14.0`. The `init`
hook reads it, so the runtime gate follows. |
| `package.json`, `pnpm-workspace.yaml`, `pnpm-lock.yaml` |
`@types/node` `^24.13.4` → exactly `22.14.1`, the types for the 22.14
floor itself, and an override `'@types/node': $@types/node` that
resolves every package to that one copy. Before, `@types/fs-extra`,
`@types/adm-zip`, `@types/unzipper`, `@types/yazl` and `@inquirer/core`
pulled in 24.13.4 and 22.19.18 as well. This follows #347's rule that
the types describe the floor runtime, so the compiler rejects any Node
API added after 22.14, including ones backported later in the 22 line
such as `process.execve` (22.15). The lockfile changes only
`@types/node` and `undici-types`. |
| `tsconfig.json` | `lib` drops `dom`. The source compiles without it.
It declared browser globals Node 22 lacks, which compiled and would then
throw `ReferenceError` on 22: `URLPattern` and `CloseEvent` (Node 24),
and `localStorage`, `sessionStorage`, `Storage` and `ErrorEvent` (Node
26). Every 22.x from 22.12 to 22.23.4 has the same set of these globals,
so no 22.x floor would make any of them safe. |
| `.github/workflows/test.yml` | Matrix `node: ['22.14.0', '22', '24',
'26']`. The first entry is the floor itself, so a dependency or API that
needs a later 22 release fails there. `'22'` is the newest 22.x, where
most Node 22 users are. Install runs with `--engine-strict`, so a
package whose `engines` excludes the job's Node fails the job instead of
printing a warning. A new step runs `node bin/run.js --version` after
the build; it goes through the `init` hook, so a floor that refuses the
job's Node fails CI. The jobs run with `APIMATIC_E2E=1`, so the real
portal build runs on Node 22.14.0 on Linux, Windows and macOS. |
| `README.md` | "Node.js 24 or newer" → "Node.js 22.14 or newer",
keeping #461's `@2` wording. |
| `test/infrastructure/services/plugin-service.test.ts` | The
hand-rolled `drain` helper is replaced by `text()` from
`node:stream/consumers`. `@types/node` 22.14.x has no `Buffer.from`
overload for the `string \| Buffer` chunk the helper passed, so it
stopped type-checking. |

`release.yml`, `pull-requests.yml` and `portal_tests.yml` stay on Node
24. Release needs npm ≥ 11.5.1 for trusted publishing, and the other two
are single-version jobs.

## Verification

The local runs used the official **Node 22.14.0** binary and the
standalone pnpm 11.0.8 that CI installs:

- [x] `tsc` build against `@types/node` 22.14.1 without the `dom` lib:
clean.
- [x] `tsc -p test/tsconfig.json`: clean.
- [x] Full suite with `APIMATIC_E2E=1`: **2080 passing**, 1 pending, 0
failing, including **78** end-to-end portal builds and serves. This
includes the guard that every portal-template dependency's `engines`
covers `>=22.14.0` (`portal-project-service.test.ts`, "supports only
Node versions every template dependency supports").
- [x] `eslint .`: clean.
- [x] Engines gate: `node bin/run.js --version` exits 0 on Node 22.14.0
and 22.23.4. On 22.13.0 it exits 1 with "The APIMatic CLI needs Node
>=22.14.0; this is Node 22.13.0."
- [x] `--engine-strict`: a fresh install of these manifests passes on
Node 22.14.0. On Node 22.13.0, with `engines` lowered to `>=22.12.0` so
only dependencies are checked, it fails on `semantic-release@25.0.3`
with the flag and passes without it. This shows the standalone pnpm
checks against the Node on `PATH`, not the one it bundles.
- [x] Every package in `pnpm-lock.yaml` accepts Node 22.14.0, 22.23.4,
24.22.2 and 26.13.0.
- [x] CI on Node 22.14.0, 22.23.4 (the newest 22.x), 24.22.1 and 26.13.0
for Linux, macOS and Windows, E2E included: **2076 passing** on Linux
and macOS (5 Windows-only tests pending) and **2081** on Windows, 0
failing. Every job installed with `--engine-strict`. Every job's gate
step printed that job's own Node, `node-v22.14.0` on the floor jobs.

## Trade-off

This is the cost #347 was avoiding: v2 now supports a runtime that
reaches end of life on 2027-04-30. When that happens, dropping Node 22
in a 2.x minor is low-risk once installs are pinned to `@2`. npm then
installs the newest 2.x that still supports Node 22, not v1.

## Follow-ups after release

- Replace "Requires Node.js 24.0.0 or later" in the platform app's setup
and transform steps with 22.14.
- Do the same for the "Node.js v24 or later" prerequisites in
apimatic-docs v4 and the skills' `node --version` checks.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@sonarqubecloud

sonarqubecloud Bot commented Nov 2, 2026

Copy link
Copy Markdown

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants