Skip to content

feat(portal): prefer port 23513 and auto-configure API Copilot in quickstart#290

Merged
saeedjamshaid merged 44 commits into
devfrom
feat/portal-port-23513-copilot-quickstart
Jun 24, 2026
Merged

feat(portal): prefer port 23513 and auto-configure API Copilot in quickstart#290
saeedjamshaid merged 44 commits into
devfrom
feat/portal-port-23513-copilot-quickstart

Conversation

@sohail2721

Copy link
Copy Markdown
Contributor

Summary

Three related portal changes:

1. Default serve port → 23513

  • apimatic portal serve now defaults to port 23513, falling back to 3000, 3001, 3002, then a random port. The serve action's preferred-port list already produces [23513, 3000, 3001, 3002] for the default.
  • The apimatic quickstart portal flow serves on 23513 too, so it matches the Copilot base URL.

2. Base-URL / serve-port mismatch warning

  • On portal serve, if the effective portal base URL (generatePortal.portalSettings.baseUrl preferred, else generatePortal.baseUrl — mirroring codegen resolution) is a localhost URL whose port differs from the actual serve port, the user is warned. Non-localhost base URLs are ignored.
  • Added isLocalhost() and port() to the UrlPath value object.

3. Quickstart auto-configures API Copilot

After clone → sample-build → build config, when the account has an API Copilot key:

  • sets generatePortal.baseUrl to http://localhost:23513,
  • adds apiCopilotConfig (matching the portal copilot command's shape, with a default welcome message — no editor popup),
  • enables aiIntegration (Cursor, Claude Code, VS Code) for every language in languageConfig.

Prompts for key selection when multiple keys exist; uses the single key automatically otherwise. When no Copilot key is available (or the check fails), it skips silently with no output.

Notes

  • Excludes the unrelated, pre-existing .vscode/launch.json change.
  • tsc -b passes; changed files lint clean. The repo's test suite is broken/stale independent of this PR.

🤖 Generated with Claude Code

…ckstart

- Default the portal serve port to 23513 (falls back to 3000, 3001, 3002,
  then a random port), and align the quickstart serve port to match.
- Warn on `portal serve` when the effective portal base URL points at
  localhost on a port different from the actual serve port; non-localhost
  base URLs are ignored.
- On `apimatic quickstart`, when the account has an API Copilot key, set
  generatePortal.baseUrl to http://localhost:23513, add apiCopilotConfig,
  and enable AI integrations (Cursor, Claude Code, VS Code) for every
  configured language. Skips silently when no key is available.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
sohail2721 and others added 26 commits June 23, 2026 09:36
- Suppress the base-URL port-mismatch warning during quickstart (onAfterServe),
  consistent with the suppressed fallback-port message.
- Exclude the synthetic "http" entry when enabling AI integrations; only real
  SDK languages get aiIntegration.
- Share the default Copilot welcome message between the copilot command and
  quickstart instead of duplicating it.
- Drop the redundant build-file validate() in the mismatch check and guard the
  read against a malformed file.
- Type the apiCopilotConfig literal as CopilotConfig and fix the stale comment.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- selectCopilotKey returns undefined on cancel (prompt.md: select/text/
  multiselect return undefined), matching the other prompts in the class.
- Add trailing newline to urlPath.ts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Portal generation failed with `System.InvalidOperationException: Sequence
contains no matching element` from SdkLanguage.FromSupportedTemplate.

codegen resolves `portalSettings.languageSettings` keys via
`SdkLanguage.FromSupportedTemplate`, which matches the SupportedTemplates id
(e.g. `ts_generic_lib`, `php_generic_lib_v2`) — not the friendly `languageConfig`
key (`typescript`, `php`). enableAiIntegrations was keying by the friendly name,
so the lookup threw and the whole generation failed (surfaced as the generic
"unexpected error").

Map each friendly language to its codegen template id and key languageSettings
by that. Languages without a known template id (e.g. the synthetic `http`) are
skipped rather than emitted with an unresolvable key.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot setup now applies silently; removes the log line that also echoed the
Copilot key to the terminal.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…etup

Setting generatePortal.baseUrl to http://localhost:23513 propagated into
portalSettings.baseUrl, flipping the served portal from the default relative
"./" base to an absolute origin. The locally-served portal loads its content
relative to where it is served, so the absolute base left the portal rendering
only its shell (header) with no docs/content.

The baseUrl provided nothing to API Copilot (the MCP/chatbot URL is resolved
server-side), so drop the assignment. apiCopilotConfig + aiIntegration are kept.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ultBaseUrl

Restores generatePortal.baseUrl (default http://localhost:23513) in quickstart
copilot setup — it is required. Renames the constant copilotBaseUrl ->
defaultBaseUrl. Reverts the removal in e921560 (baseUrl was not the cause of the
portal-not-loading issue).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…renders

The quickstart wrote portalSettings.languageSettings only for SDK languages
getting AI integration. Supplying a partial languageSettings suppresses codegen's
auto-population (which normally creates an entry per language), but codegen still
sets initialPlatform from languageConfig — typically `http_curl_v1`. With no
languageSettings entry for that platform, the portal widget threw during init:
only the static header rendered and docs were never fetched (all network 200).

Write an entry for every languageConfig language (mapping http -> http_curl_v1),
with AI integration on real SDK languages and an empty entry for http. Mirrors
codegen's own auto-population and the known-good build format.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…http entry explicitly

AI integration is written only for the selected SDK languages (the codegen
template-id map no longer includes http). The http_curl_v1 languageSettings entry
the portal needs to render is added as a single explicit line rather than via the
language loop.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Set portalSettings.initialPlatform to the first SDK language (the entry after
http in languageSettings) so the portal opens on a real SDK instead of http.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When the account has multiple Copilot keys and the user cancels the key-selection
prompt, abort the quickstart (ActionResult.cancelled) instead of silently
finishing without Copilot/AI integration. No-key and single-key paths are
unchanged (skip silently / auto-use).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Merge the duplicate `types/build/build.js` import in quickstart.
- Use an optional chain (`!parsedUrl?.isLocalhost()`) in the serve base-URL check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…generation

Previously the serve port was resolved AFTER portal generation, which bakes the
baseUrl into the artifacts. When the preferred port (23513) was taken, serve fell
back to 3000 while the baked baseUrl still said 23513, so the portal loaded its
content from the wrong port and rendered only the header — and in quickstart the
mismatch warning was suppressed, so it failed silently.

Resolve the serve port first, then, for localhost base URLs whose port differs
from the actual serve port, rewrite the baseUrl port and persist it to the build
file BEFORE generation. Covers both `portal serve` and quickstart (which delegates
to PortalServeAction.execute). The informational message is shown for standalone
serve and suppressed during quickstart.

- Replace warnOnBaseUrlPortMismatch with reconcileBaseUrlPort (read -> rewrite ->
  persist) and run it before GenerateAction.
- Add UrlPath.withPort(port).
- Replace the baseUrlPortMismatch warning prompt with baseUrlPortUpdated (info).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The "port X is in use, using Y" message was suppressed in the quickstart flow, so
when the default 23513 (and 3000) were taken the portal silently came up on a
different port with no explanation. Show the fallback notice in both flows; it only
fires when a fallback actually happens. The separate baseUrl-reconcile message stays
suppressed during quickstart to avoid redundancy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…t in quickstart

After Copilot is wired into the build config, show which key it was enabled with
and warn that any existing training data on that key will be overwritten on the
next portal generation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the trailing-slash regex with endsWith/slice to resolve the SonarCloud
super-linear-backtracking warning.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…t too

Show the "base URL updated to match serve port" message in both flows. Drop the
now-always-true informUser parameter from reconcileBaseUrlPort.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Split configureApiCopilot into resolveCopilotKey (account lookup + key selection +
enabled/overwrite warning) and applyCopilotConfig (mutates the build file). Resolve
the key before downloading/setting up the source directory so the Copilot prompt and
warning appear ahead of the "source directory set up" / "src directory created"
messages, then apply the config to the build file after it's fetched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ig class

Turn BuildConfig into a rich class wrapping the parsed APIMATIC-BUILD.json data
(interface renamed to BuildConfigData). All build-config reads and mutations now go
through intent-revealing methods instead of raw `buildConfig.generatePortal!.…`
access scattered across actions:

- reads: contentFolder(), isVersioned(), versionsPath(), hasApiCopilot()
- mutations: setPortalLanguages(), setApiCopilotConfig(), enableApiCopilotForPortal(),
  enableAiIntegrations(), reconcileLocalhostBaseUrlPort(), addRecipeWorkflow()

The codegen template-id map, AI-integration/initialPlatform logic, and the
localhost baseUrl reconciliation move into the class. BuildContext becomes the I/O
boundary only (parse on read, JSON.stringify via toJSON on write).

Callers (quickstart, copilot, serve, toc, recipe-generator) and BuildContext's
versioned-portal helpers updated to use the class API. No raw generatePortal access
remains outside build.ts. Also drops two pre-existing `any` casts in recipe-generator.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
AI editor integrations build MCP install links from the API Copilot key, so they
are meaningless without one. Drop the no-key branch that enabled them anyway;
enableAiIntegrations is now private (only invoked via enableApiCopilotForPortal).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
get-port only checks port availability; the port can be taken between that check
and listen(), and the gap widened now that the port is resolved before generation.
A failed bind emitted an unhandled "error" event and crashed the CLI with a stack
trace. Wait for the server's "listening"/"error" event and, on error, close the
live-reload server and return a clean failure ("port may have been taken, try again").

Also flip the negated condition in BuildConfig.addRecipeWorkflow per SonarCloud.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…) immutable

Replace the in-place mutators with copy-on-write transforms so the build config is
never mutated after creation. Every change now returns a NEW instance:

- BuildConfig: withPortalLanguages, withApiCopilotConfig, withApiCopilotForPortal,
  reconcileLocalhostBaseUrlPort (returns { config, previous, updated }), withRecipeWorkflow.
- New immutable value classes PortalSettings and LanguageSetting (private constructors,
  static `from` factories, with* transforms) own the portalSettings/languageSettings
  and AI-integration construction.

Each transform deep-clones the config (JSON round-trip — the data is plain JSON) and
builds the new value, leaving the receiver untouched. Callers (quickstart, copilot,
serve, recipe-generator) thread the returned config into updateBuildFileContents.
Interfaces renamed to *Data (BuildConfigData/PortalSettingsData/LanguageSettingData).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…node:http

- Inline the former getCopilotKey() helper into execute. A failed account lookup
  still continues silently without Copilot; no key / single key / multi-key
  behave as before (multi-key cancel aborts quickstart). The build file is still
  always written so the user's selected languages persist (pre-existing behavior),
  with the Copilot block applied only when a key is resolved.
- Import Server from "node:http" (SonarCloud node-protocol preference).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…erthrow Result

- reconcileLocalhostBaseUrl now takes the complete serve URL (UrlPath) instead of a
  bare port and replaces the whole configured localhost base URL with it when they
  differ. Returns Result<BaseUrlReconciliation, "unchanged"> instead of an ad-hoc
  `{...} | undefined`.
- waitForServerListening returns Result<void, Error> instead of `Error | undefined`,
  applying the same result type to the other changed spot.
- UrlPath: drop now-unused port()/withPort(); add isEqual() (matches DirectoryPath/
  ProfileId). serve builds the serve URL once and reuses it for reconcile + display.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…on serve

Since the base-URL update reads the build file BEFORE GenerateAction validates it,
a missing/malformed APIMATIC-BUILD.json would otherwise crash serve (or, for invalid
JSON, slip through to a generic server error). updateBaseUrl now shows a clear
"build configuration invalid" message and returns false so execute fails cleanly.

Also rename reconcile -> update across the base-URL flow (updateBuildConfigBaseUrl,
updateBaseUrl, BaseUrlChange) for simpler naming.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
sohail2721 and others added 17 commits June 23, 2026 16:48
- BaseUrlChange.previous/updated are now UrlPath instead of raw strings.
- PortalSettings.withBaseUrl takes a UrlPath (unwraps to string only at the JSON
  boundary); removed the unused baseUrl() getter.
- baseUrlPortUpdated prompt takes UrlPath and unwraps at the display boundary.

The *Data interfaces (BuildConfigData/PortalConfig/PortalSettingsData/…) stay
primitive on purpose: they mirror the on-disk APIMATIC-BUILD.json and must
round-trip through JSON.parse/stringify, which class instances don't.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The single-use helper is inlined into execute: read the build file (failing cleanly
on a missing/invalid file), update the localhost base URL via BuildConfig, persist,
and notify. Removes the indirection of returning a boolean just to signal failure.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…nfig

The before/after URLs were unnecessary: the serve action already has the new URL
(serveUrl). updateBuildConfigBaseUrl now returns Result<BuildConfig, "unchanged">,
the BaseUrlChange type is removed, and baseUrlPortUpdated takes just the new URL.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Previously a failed getAccountInfo was treated like "no key" and silently skipped.
Now a lookup error is fatal: show "Failed to fetch your API Copilot key" and return
ActionResult.failed(). Only an account with no key continues silently (no Copilot);
multi-key cancel still aborts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
livereload binds its own internal HTTP server and only error-handles the WebSocket
server, so a taken live-reload port (race after get-port) emitted an unhandled
"error" and crashed the CLI. Wait on livereload's internal HTTP server
(config.server) right after creation and fail cleanly with serverStartFailed,
mirroring the main server's bind handling.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the single-line portalConfigOf(data) helper with direct data.generatePortal!
access at its three call sites and drop the function.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Match the livereload bind check above it; the guard logic (close the livereload server, report serverStartFailed, fail) is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Stop reaching into livereload's private config.server to await its bind; the cast into library internals was the only thing requiring it. The express portal server's bind is still guarded.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ase URL on hot reload

- Check for portal source directory before negotiating a serve port so
  empty-directory errors are diagnosed immediately with a pointer to
  portal:quickstart rather than a misleading JSON config error
- Re-run base URL reconciliation on every hot-reload cycle so that a
  portalSettings.baseUrl added or changed mid-session (which takes
  precedence over generatePortal.baseUrl) is corrected to the actual
  serve port before generation bakes it in

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Guard the livereload HTTP server's bind the same way as the main server:
the livereload package attaches its error handler to the inner WebSocket
server, not the HTTP server it binds the port on, so a failed bind emitted
an unhandled "error" that crashed the process. On failure we now close the
server and report serverStartFailed instead.

Also model the portal base URL as a UrlPath instead of a raw string in
withApiCopilotForPortal and quickstart's defaultBaseUrl, matching the
sibling updateBuildConfigBaseUrl signature and the value-object convention.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MXbT3oxYm19pgHaviemkfa
@saeedjamshaid saeedjamshaid merged commit 3537e8d into dev Jun 24, 2026
4 checks passed
@saeedjamshaid saeedjamshaid deleted the feat/portal-port-23513-copilot-quickstart branch June 24, 2026 04:56
@sonarqubecloud

Copy link
Copy Markdown

saeedjamshaid pushed a commit that referenced this pull request Jun 24, 2026
…ckstart (#290)

* build: local dev scripts (#291)

* build: utility scripts for local testing

* doc: update run commands

---------

Co-authored-by: Muhammad Sohail <62895181+sohail2721@users.noreply.github.com>
github-actions Bot pushed a commit that referenced this pull request Jun 24, 2026
# [1.1.0-beta.19](v1.1.0-beta.18...v1.1.0-beta.19) (2026-06-24)

### Features

* **portal:** prefer port 23513 and auto-configure API Copilot in quickstart ([#290](#290)) ([45cc0e6](45cc0e6)), closes [#291](#291)
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