Skip to content

feat(portal): build a portal from the spec folders APIMATIC-META.json merges - #435

Merged
saeedjamshaid merged 4 commits into
devfrom
saeedjamshaid/merged-spec-folders
Oct 14, 2026
Merged

saeedjamshaid merged 4 commits into
devfrom
saeedjamshaid/merged-spec-folders

Conversation

@saeedjamshaid

@saeedjamshaid saeedjamshaid commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

The problem

To build SDKs from several APIs, the platform wants a folder to each API in src/spec and an APIMATIC-META.json beside the folders whose MergeConfiguration turns on MergeApis. sdk generate works with that layout, but portal generate and portal serve read only the documents at the top of src/spec. With the metadata file the only file there, both stopped with:

■  No OpenAPI 3.x document found in '…/src/spec'.
│  If 'APIMATIC-META.json' is an API definition in another format, convert it with: …

So one input could not give both the SDKs and the portal. Flattening the specs into src/spec let the portal build, but then nothing merges them, and the SDKs and their code samples cover one of the specs only, without a word.

Tracked in apimatic-io issue 2297.

What changes

  • Merged folders are read as the platform reads them. When src/spec/APIMATIC-META.json turns on MergeApis (setting names in any case, and true or "true", as the platform accepts them), the portal reads the folders it merges:
    • the folders MergeOrderOfDirectories lists, in its order, or every folder by name when it lists none (an empty list included, as on the platform); a folder listed twice is read once;
    • the one API definition at the top of each folder, which has to be an OpenAPI 3.x document and may $ref files anywhere. Any file there with a version key counts as a candidate, in whatever format, as on the platform; files below the top, the folder's own metadata file and hidden files such as .gitkeep do not;
    • a section of the API reference per folder, named after the folder (orders.v2 becomes orders-v2). The sidebar lists sections by name; content/api/nav.json reorders them, as it already did.
  • Only the exact file name APIMATIC-META.json is read. Other names the platform also accepts are treated as any other file.
  • Refused, each with its own message:
    • a folder MergeOrderOfDirectories names that the platform would not find: missing, named in another case than on disk, outside src/spec, or src/spec itself (.);
    • a merged folder that holds no API definition, hidden files aside;
    • a merged folder with more than one API definition at its top, in any format. The platform builds the folder from the first file by name and drops the rest without a word, and that first file may be one of shared parts written as an OpenAPI document, which fails its build. The message says to keep the definition at the top and move shared parts into a folder inside it, which the platform builds;
    • an APIMATIC-META.json that is not JSON;
    • a metadata file that merges where src/spec has no folders.
  • Warned, and built:
    • several documents at the top of src/spec that nothing merges, since the SDKs are built from one of them;
    • an API definition in any format (a version key, and paths, webhooks or channels) left at the top of src/spec while its folders are merged, which the platform leaves out of the SDKs and the portal leaves out too; shared parts kept there are not named;
    • an operation (method and path) that more than one definition declares. The code samples hold one sample per method and path, so the one sample would be another API's call on all but one of the pages: the SDK sample is left off those operations, and the rest keep theirs. An operation one spec declares twice, as a path item's own method beside its $ref, counts once.
  • portal serve reloads on any change to a .json, .yaml or .yml file in src/spec, so parts a merged folder refers to in a folder of their own reload the preview too. portal.config.json carries specDir for it.
  • A conversion is pointed at the folder it is for. "No OpenAPI 3.x document found" names the document's own folder, and api transform is told to write beside it, in a merged folder as at the top of src/spec; the converted file is moved up in place of the original. An unreadable document is named by its path.
  • Help and docs. portal generate describes the layout, and portal serve, its Live preview note and the README say that changing which folders APIMATIC-META.json merges needs the preview restarted, as changing the documents in src/spec does.
  • Tidy-ups on the way: pages sharing an address and specs sharing an operation are one Shared type and one grouping; FileName.isEqual replaces three compare(...) === 0; FileName.slugified, FileName.isHidden, DirectoryPath.compare, DirectoryPath.isAncestorOf, Endpoint.isEqual, OpenApiDocument.namesApiFormat/declaresOperations and FileService.directorySpelledOnDisk are new; DirectoryPath.contains no longer takes a folder named ..v2 for a step up; the "list these files in a sentence" idiom in src/prompts/portal/source.ts is one listed() helper instead of seven copies; METADATA_FILE_NAME and SPEC_EXTENSIONS live in src/types/project-layout.ts.

What it prints

■  'spec/APIMATIC-META.json' merges './spec/Beta' and './spec', which are not folders inside '…/src/spec'.
│  Name each folder in 'MergeOrderOfDirectories' by its path from the file, spelt as on disk, case included.

■  'spec/APIMATIC-META.json' merges './spec/placeholder', which holds no API definition.
│  Add its OpenAPI 3.x document to the top of the folder, or list only the folders to merge in 'MergeOrderOfDirectories'.

■  'spec/alpha/a-legacy.json' and 'spec/alpha/openapi.json' are each an API definition, but a folder that 'APIMATIC-META.json' merges holds one at its top, and the SDKs are built from one of them only.
│  Keep the definition there and move each of the others: shared parts into a folder inside it, such as './spec/alpha/shared', and another API into a folder of its own, adding the new folders to 'MergeOrderOfDirectories' when the file lists folders.

■  No OpenAPI 3.x document found in '…/src/spec/beta'. 'swagger.json' is Swagger 2.0.
│  Convert it with:
│    apimatic api transform --format=openapi3yaml --file=…/src/spec/beta/swagger.json --destination=…/src/spec/beta
│  then move '…/src/spec/beta/transformations/swagger_OpenApi3Yaml.yaml' up into '…/src/spec/beta', in place of 'swagger.json'.

■  'spec/APIMATIC-META.json' merges the folders of '…/src/spec', which has none.
│  Put each API definition in a folder of its own there, or turn off 'MergeApis' in the file.

■  'spec/APIMATIC-META.json' could not be read as JSON.

■  '…/src/spec' has no API definition. Add your OpenAPI 3.x document to it as a '.json', '.yaml' or '.yml' file.
│  A document in a folder inside it, such as 'alpha' and 'beta', is not read. Move it up, or to keep one API to a folder, set 'MergeConfiguration.MergeApis' to true in an 'APIMATIC-META.json' beside the folders.

▲  'spec/alpha.json' and 'spec/beta.json' are separate API definitions, and the SDKs are generated from one of them only. To cover them all, give each its own folder and set 'MergeConfiguration.MergeApis' to true in an 'APIMATIC-META.json' beside the folders.

▲  'spec/old.json' is left out of the portal and the SDKs, as 'APIMATIC-META.json' merges only the folders beside it. To include it, move it into a folder of its own, adding the folder to 'MergeOrderOfDirectories' when the file lists folders.

▲  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'

How the platform reads a merged folder

Probed with apimatic sdk generate from this branch, with spec/alpha holding:

Files Result
components.yaml (an OpenAPI document of shared parts) and openapi.json fails: "No endpoint found for Code Generation", built from components.yaml
openapi.json and zparts.json, or Zparts.json builds from openapi.json
components.json with no version key, and openapi.json builds from openapi.json
a.json and b.json, both full APIs builds from a.json; b.json is dropped without a warning
common/components.json (shared parts, in a folder sorting first) and openapi.json builds from openapi.json
MergeOrderOfDirectories: ["Alpha"] with a folder named alpha fails: "Directory does not exist"
mergeConfiguration/mergeApis in lower case, or "MergeApis": "true" merges
MergeOrderOfDirectories: [] merges every folder
a folder listed twice in MergeOrderOfDirectories builds

Testing

  • pnpm test: 1853 passing, 67 pending, ESLint clean. New tests cover the metadata file, discovery of merged folders and every refusal and warning, the messages, the samples left off shared operations, the preview's reload, and the new value-object methods.
  • APIMATIC_E2E=1: 65 build and 2 serve cases passing. A new fixture merges two folders that both declare GET /status, one of them keeping a schema in a file of its own folder. It checks a section and a page per operation, each API's own sample and server, no SDK sample on either /status page (this case fails with the filter taken out), and the schema read from the other file.
  • Real portal generate runs against the platform, with the CLI built from this branch:
    • a 28-spec merged project, one spec to a folder: no warnings, 28 sections, and SDK samples on all 174 operation pages (on the first commit);
    • two merged specs sharing GET /status: the warning, no SDK call on either /status page, and the other operations' samples in place;
    • lower-case metadata settings with "true", shared parts in a folder inside a merged folder, and a single spec with a method beside its path item's $ref: each builds, with samples on every page and no warning about the specs;
    • an OpenAPI document of shared parts or a Swagger 2.0 file beside the definition, a Swagger-only merged folder, missing, hidden-only, misspelt and . folders, a metadata file merging no folders, a flat two-spec project and a spec left at the top beside merged folders: each prints what is shown above.
  • A real portal serve on a merged fixture whose spec refers to spec/common/pet.json: a .DS_Store written into spec/ sent the open preview nothing, and saving pet.json sent it a full reload.

Not in this PR

  • Samples for an operation two specs share. The platform's code samples are keyed by method and path alone, so this PR leaves the SDK sample off such an operation rather than show another API's call. Placing each spec's own sample needs a backend change and a follow-up here (apimatic-io issue 2298).
  • Whether the platform sorts a folder's files itself or follows the order in the uploaded zip is not settled; the refusal of a second definition holds either way. Nor is whether it reads a hidden file with a version key as a candidate, which the portal does not. Nested merges are not read.
  • Conflicts with fix(portal): keep the preview up and report a broken specification while serving #434, which rewrites the same discovery loop and messages; whichever of the two merges second gets the other merged in.

🤖 Generated with Claude Code

… merges

`portal generate` and `portal serve` read only the documents at the top of `src/spec`, so the
layout the platform merges several APIs from, a folder to each API and an `APIMATIC-META.json`
beside them, failed with "No OpenAPI 3.x document found". When `APIMATIC-META.json` turns on
`MergeApis`, the portal now reads the folders it merges, in the order `MergeOrderOfDirectories`
lists them or else every folder by name, one OpenAPI document to a folder and a section named
after the folder.

Refused, each with its own message: a merged folder with no document to read (empty, missing or
outside `src/spec`), a merged folder with two OpenAPI documents, and an `APIMATIC-META.json` that is
not JSON. Only the exact name `APIMATIC-META.json` is read.

Warned, and built:
- several documents at the top of `src/spec` that nothing merges, as the SDKs are built from one;
- an API definition left at the top of `src/spec` while its folders are merged, which the portal
  and the SDKs leave out;
- an operation that more than one definition declares: its SDK samples are left out, since the
  samples hold one per method and path and would show another API's call.

The `portal generate` and `portal serve` help, the README and the Live preview note describe the
layout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
saeedjamshaid and others added 3 commits October 14, 2026 13:08
Follows the review of the merged spec folders:

- An operation that one spec declares twice, as a path item's own method beside its `$ref` does,
  no longer counts as shared: a single-spec project kept losing its SDK sample. Pages sharing an
  address and specs sharing an operation are now one `Shared` type and one grouping.
- `APIMATIC-META.json` is read as the platform reads it: setting names in any case, and
  `MergeApis` as `true` or `"true"`.
- A merged folder with more than one OpenAPI document at its top is still refused, now with where
  to move shared parts: the platform builds the folder from the first file by name, which may be
  the shared parts. Shared parts at the top of `src/spec` are no longer warned as left out.
- Hidden files such as `.gitkeep` are never read as API definitions.
- A merged folder the platform would not find is refused: `.` (`src/spec` itself), and a name in
  another case than the folder's on disk.
- `portal serve` reloads on any change in `src/spec`, so shared parts in a folder of their own
  reload the preview too.
- A metadata file that merges where `src/spec` has no folders is its own problem, and the sentence
  about `MergeOrderOfDirectories` lives once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A merged folder counts every file with a version key as a candidate definition, as the platform
  does, so a Swagger or other definition beside the OpenAPI 3.x document is refused too. The
  conversion advice says to move the converted file up in place of the original, which would
  otherwise be such a second definition.
- Two ideas named apart on `OpenApiDocument`: `namesApiFormat` (what the platform may take as a
  folder's definition) and `declaresOperations` (paths, webhooks or channels). The left-out warning
  needs both, so an AsyncAPI or webhooks-only definition at the top of `src/spec` is named again.
- Folders `MergeOrderOfDirectories` names that the platform would not find (missing, in another
  case, outside `src/spec`, or `.`) are their own problem, checked where the order is read;
  `emptyMergedFolders` keeps the folders that are there and hold nothing.
- `APIMATIC-META.json` prefers a setting's exact name over one in another case.
- `DirectoryPath.contains` takes a folder named `..v2` for one inside, not a step up.
- An operation one spec declares twice is read once where its endpoints are collected, and
  `shared()` is a plain grouping again.
- The preview reloads only for `.json`, `.yaml` and `.yml` files in `src/spec`, by a list a test
  holds to the CLI's; `SPEC_EXTENSIONS` lives in `src/types/project-layout.ts`.
- `severalSpecsInFolder` carries its folder; a read document is a spec yet to be given its slug;
  one helper gives a page's slugs and address; no restating comments.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…docs

The advice to merge folders, in the error for a `src/spec` with only folders, the warning for
several specs nothing merges, and the `portal generate` help, no longer links the docs. It names
the setting by its place in `APIMATIC-META.json`, `MergeConfiguration.MergeApis`, which the link
was left to say: a `MergeApis` at the top of the file merges nothing. `MERGE_SETTINGS` gains
`configuration`, which the file reader uses too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@saeedjamshaid
saeedjamshaid merged commit 0faa568 into dev Oct 14, 2026
10 of 12 checks passed
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 2.0.0-beta.6 🎉

The release is available on:

Your semantic-release bot 📦🚀

saeedjamshaid added a commit that referenced this pull request Oct 16, 2026
…ec-errors

#435 rewrote the same spec reading. Its folder discovery is kept, and every file it reads goes
through this branch's reader, so the documents of a merged folder get the `$ref` checks and the
reasons. #435's check for an operation declared twice moves into `SpecBundle.endpoints()`, with a
test there, and the `specDirectory` both branches added is kept once.

Serve's warnings now hold when `APIMATIC-META.json` changes which folders merge: the documents are
named by their path from `spec/` and said to be "now read" or "no longer read", and a refused
specification says the preview "shows what it can still read".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants