Add --codegen-version to apimatic sdk publish
Blocks #304 (apimatic plugin generate). A4 of #304 writes Languages.<lang>.version into
pluginconfig.json from the codegen version a publish ran with. Until this lands that value can
only be "v3", making v4 plugins unreachable. #304 previously listed this work as a deferred
follow-up; it has been re-scoped as a prerequisite.
Specifically, PR 1 (non-interactive) is the prerequisite — not the whole issue. #304's A4 is
explicitly non-interactive-only, so it never reads a codegen version from the wizard. PR 2 can
proceed in parallel with the rest of #304. See Delivery below.
Summary
apimatic sdk generate supports both the v3 and v4 code generators via --codegen-version.
apimatic sdk publish does not — it hardcodes v3. This adds the same flag to publish, plus
--stability (which generate already has), so a v4 SDK can be generated and published.
SdkPublishAction currently pins both values:
// src/actions/sdk/publish.ts:58-59 (before)
CodeGenerationVersion.V3,
Stability.STABLE,
Both become parameters supplied by the caller. The action itself gains no conditional logic.
Background — the two services
The v4 generation path already exists and works. GenerateAction (src/actions/sdk/generate.ts:109)
branches on codeGenVersion and calls PortalService.generateV4Sdk. The only reason sdk publish
can't reach it is the hardcoded literals above.
Publishing itself needs no changes, because the CLI talks to two independent services:
| Stage |
Service |
Endpoint |
| Generation |
api.apimatic.io |
POST /sdk (v3) or POST /sdk/v2 (v4) |
| Publishing |
api.package-publishing.apimatic.io |
POST /publish/{profileId}/{language} |
The CLI generates the SDK itself, then uploads the finished zip to the publishing service
(src/infrastructure/services/publishing-api-service.ts:46-78) along with packageVersion and
publishType. The publishing service never learns which generator produced the zip.
The package version must come from the generator — verified
An earlier draft of this issue assumed --version alone determined the published version, making
the generator's copy redundant. That assumption is wrong. Traced through the
package-publishing and package-publishing-func repositories:
- The CLI posts to
POST /publish/{profileId}/{language}
(publishing-api-service.ts:76), which routes to the PublishGeneratedSdk handler
(PublishingController.cs:20-28) — not the Publish handler on POST /publish/{profileId}.
PublishGeneratedSdk.Handle uploads the CLI's zip verbatim —
await _blobStorageService.UploadBlob(sdkFileStream, publishParams.GenerateBlobName()) — then
enqueues a metadata message. It never transforms the SDK.
- The transforming call,
IApimaticCoreService.GeneratePublishReadySdk(...), and the core config
built from ToCoreConfig(version) are referenced from exactly one place — Publish.cs:210-211,
the other endpoint, where the service generates the SDK itself from an API entity. The CLI's
path never reaches it.
- The worker packs what it was given.
CSharpPublishParams.cs:33-38:
dotnet pack /sdk/*.Standard/*.Standard.csproj -o out
dotnet nuget push -s ${PACKAGE_SOURCE} -k ${NUGET_KEY} /out/*.nupkg
No -p:PackageVersion, no package-id override. The generated project file is authoritative.
So the published version and package identity are whatever the generator baked in. --version
reaches the publish log, the Mixpanel event and the constructed PackageUrl
(CSharpConfiguration.cs:23) — but not the package.
v3 is correct today only because the CLI passes the version to the generator
(portal-service.ts:133, the 5th argument to generateSdkViaBuildInputAsync). generateV4Sdk
passes nothing, so a v4 publish would push the generator's default version while every surrounding
record claims the requested one.
Resolution: the package version must be sent to the v4 generation endpoint exactly as it is for
v3. The generated client does not expose the parameter yet
(generateV2SdkViaBuildInputAsync(contentType, file, language, stabilityTag)), so a TODO marks
the call site until the client is regenerated. Threading --version into generateV4Sdk is a
prerequisite for v4 publishing being correct, not merely reachable.
Design decisions
--stability is an explicit flag, never inferred
An earlier draft coerced stability automatically (v4 ? BETA : STABLE) and added no flag. That
was rejected. The coercion would be baked into a released binary, so when v4 reaches GA every CI
pipeline pinned to an older CLI would keep silently receiving beta SDKs — a user who wrote
--codegen-version v4 months earlier would be opted into unstable output without ever asking for
it. Deleting the ternary at GA only helps people who upgrade.
Instead, publish gains --stability mirroring generate. A user who wants v4 today must write
both flags: --codegen-version v4 --stability beta.
The CLI holds no knowledge of which stability values v4 supports
--codegen-version v4 on its own defaults --stability to STABLE, reaches the server, and comes
back with the service's own error:
The V4 Code Generator currently supports SDK generation for 'csharp' only in 'beta'.
This is deliberate. Validating locally would hardcode "v4 is beta-only" into a shipped binary, so
on GA day every older CLI would reject a perfectly valid --codegen-version v4 --stability stable
with a false error — and unlike a server error, no server-side fix can reach it. The set of
supported (codegen, stability) pairs belongs to the generation service and changes without a CLI
release.
The cost is that the error arrives after a round trip, in the service's words rather than ours.
Accepted.
--stability is inert on v3 — warn, in both commands
portal-service.ts:111 (generateSdk, v3) takes no stability parameter; only
generateV4Sdk (portal-service.ts:172) does. So --codegen-version v3 --stability beta accepts
the flag, sends nothing, and returns a stable SDK while the user believes otherwise.
Emit a warning whenever --stability is explicitly provided alongside --codegen-version v3,
in both sdk generate and sdk publish. sdk generate has this bug today.
Note this is a different kind of knowledge from the point above: "which values v4 supports" is the
server's and must not be hardcoded; "the v3 code path never sends stability" is the CLI's own
knowledge of its own code, which no server change can invalidate.
Trigger is typed-and-v3, not value-is-beta — --stability stable is equally ignored and
equally worth flagging, and beta-specific wording would read as wrong after GA.
Detecting "typed" uses oclif parse metadata, confirmed present in the pinned @oclif/core v4
(node_modules/@oclif/core/lib/interfaces/parser.d.ts:53, MetadataFlag.setFromDefault?: boolean):
const { flags: { ... }, metadata } = await this.parse(SdkPublish);
warnIfStabilityIgnored(codegenVersion, metadata.flags.stability?.setFromDefault !== true);
This is the codebase's first use of parse metadata.
Placement: the command layer, via one shared exported function (src/prompts/sdk/stability.ts).
Not inside GenerateAction, even though both commands converge there — "did the user type this
flag?" is parse metadata available only at the command layer, so warning deeper would mean threading
a presentation-only boolean through four signatures, and it would also fire for sdk quickstart,
which has no stability flag at all. Not duplicated as an identical method on both prompt classes
either: two copies of a message required to stay identical will drift.
SDK customizations on v4 — targeted warning, plus a confirm in the wizard
On the v4 path GenerateAction prints sdkCustomizationsNotSupportedForV4() and returns at
generate.ts:131, before the MergeSourceTreeAction block at line 167. A build containing
sdk-source-tree/.{language} therefore generates — and now publishes — an SDK without those
changes.
Today's warning is weak in three ways: it is unconditional (fires on every v4 run, including the
majority that have no customizations, which trains users to ignore it); its wording is a generic
capability note rather than "your changes will be dropped"; and nothing blocks — it is a
log.warn followed by the generation spinner and, in publish, an irreversible registry push.
hasSdkSourceTree is already computed in the same method (generate.ts:97) and is in scope at line
110. Use it:
- Both commands, both paths: keep the generic notice when there is nothing to lose; emit a
specific message naming the language when the build does have a tracked source tree.
- Interactive
publish only: a blocking confirm when a tracked source tree exists and V4 was
selected; declining returns ActionResult.cancelled().
The confirm is wizard-only because interactive publish ends in an unreversible push reached by
picking a menu item, while a CI user typed the flag deliberately and cannot answer a prompt.
Overloading --force (which means "overwrite the destination directory") to also mean "discard my
customizations" would be a bad conflation.
The targeted message lives in GenerateAction, so sdk generate benefits too.
sdk quickstart is unaffected in practice — actions/sdk/quickstart.ts:204 hardcodes
CodeGenerationVersion.V3 with no flag to change it, so its v4 branch is unreachable.
Interactive mode gets a codegen prompt (PR 2)
An earlier draft stated the wizard could not produce a v4 SDK, on the grounds that
interactive = this.argv.length === 0 (src/commands/sdk/publish.ts:86) means no flags were
passed. Reversed — the wizard gets its own prompt instead of inheriting a flag:
-
Choice labels are V3 and V4 (beta), so the beta nature of v4 is visible at the moment of
choosing. Stability is never prompted: it does not apply to v3, and picking V4 (beta) is the
consent to beta. A later CLI version can offer a stability choice once v4 has more than one tier.
-
Default/initial selection is V3, so an inattentive Enter never lands on beta.
-
Placement: after selectPublishingProfile (interactive.ts:65) and before selectLanguage
(75), because it filters the language list.
-
Choosing V4 narrows the language list to csharp, via a single named constant:
// Interactive only. The V4 generator currently supports csharp alone; the wizard filters to keep
// the choice honest. Non-interactive deliberately does NOT filter — the service is the authority
// there. Update when V4 adds languages.
export const V4_INTERACTIVE_LANGUAGES: readonly Language[] = [Language.CSHARP];
Filtering here does not repeat the shipped-binary trap: a wizard choice cannot be committed to
a pipeline, so a stale list costs discoverability (fixed by upgrading), not correctness. The
non-interactive path stays unfiltered and server-authoritative. The comment above is load-bearing —
without it the next reader sees a language filter and "fixes" the non-interactive path to match.
-
Empty intersection fails. A profile with no csharp configuration plus a V4 choice offers
nothing; exit ActionResult.failed() (1) with a message naming the reason and pointing at
configuring a csharp package in the App. This matches how the wizard already treats unmet
preconditions — no active publishing profiles fails outright at interactive.ts:59-62. Hiding the
V4 (beta) option instead would make it indistinguishable from v4 not existing.
-
Stability is derived, not prompted: V4 → BETA, V3 → STABLE, supplied by the interactive
action to SdkPublishAction.
Out of scope
- A codegen prompt or
--stability handling in sdk quickstart — v3-only, unreachable v4 branch.
- Pulling
sdk generate into the v4/stability coercion debate — generate keeps its existing
--stability flag semantics; it only gains the shared inert-on-v3 warning and the targeted
customizations message.
- Anything touching
--version / SemVersion — see the assumption above.
- A params-object refactor of the long positional signatures.
.ai/skills/action.md has no
convention here, and a swap of codegenVersion/stability is caught by the type checker since
they are distinct enums.
Delivery — two PRs
PR 1 — non-interactive. This is the piece #304 depends on.
--codegen-version and --stability flags on sdk publish
- threading:
commands/sdk/publish.ts → SdkPublishNonInteractiveAction → SdkPublishAction →
GenerateAction
- hardcoded
V3 / STABLE deleted from SdkPublishAction (publish.ts:58-59)
- shared
warnIfStabilityIgnored, wired into both sdk generate and sdk publish
- targeted SDK-customizations message in
GenerateAction
- telemetry payload, example, tests
Seam: SdkPublishAction.execute is called by both action classes, so once the literals become
parameters the interactive caller must pass something. In PR 1 it passes
CodeGenerationVersion.V3, Stability.STABLE explicitly — behaviour identical to today, the literals
simply move up one layer. No dead code, no wizard behaviour change.
PR 2 — interactive.
selectCodegenVersion prompt (V3 / V4 (beta), initial V3) after selectPublishingProfile
- csharp-only language filtering via
V4_INTERACTIVE_LANGUAGES
- hard fail on empty intersection
- blocking confirm when the build has a tracked source tree and V4 was chosen
BETA derived from the V4 choice, replacing PR 1's explicit V3, STABLE arguments
Change detail — PR 1
src/commands/sdk/publish.ts
Import: import { CodeGenerationVersion, Language, Stability } from '../../types/sdk/generate.js';
Flags — added last, after 'dry-run'. Name, description, options and default copied
verbatim from src/commands/sdk/generate.ts:46-55 so the two commands stay consistent:
'codegen-version': Flags.string({
description: 'Version of the code generator to use',
options: Object.values(CodeGenerationVersion).map((v) => v.valueOf()),
default: CodeGenerationVersion.V3
}),
'stability': Flags.string({
description: 'Stability level of the generated SDK',
options: Object.values(Stability).map((s) => s.valueOf()),
default: Stability.STABLE
})
Example — one v4 example added to static examples, including both flags, since that is the
only combination that works:
`${SdkPublish.cmdTxt} ${format.flag('profile-id', 'd4e5f6a1b2c3d4e5f6a1b2c3')} ${format.flag(
'language',
'csharp'
)} ${format.flag('version', '1.0.0')} ${format.flag(
'publish-type',
PublishType.PackagePublishing
)} ${format.flag('codegen-version', 'v4')} ${format.flag('stability', 'beta')}`
test/commands/examples-parse.test.ts parses every command's examples against its own flag
definitions, so this is validated automatically.
Destructure — both flags plus metadata; call the shared warning before dispatching.
Telemetry — add both flags to SdkPublishValidationFailedEvent so a failed v4 publish is
distinguishable from a failed v3 one. Its constructor takes flags: Record<string, unknown>
(src/types/events/sdk-publish-validation-failed.ts:6), so no type change is needed.
Dispatch — non-interactive receives both parsed values; interactive receives neither and passes
V3 / STABLE internally in PR 1. The as CodeGenerationVersion / as Stability casts match how
language as Language is already handled here and how sdk generate does it.
src/actions/sdk/publish/non-interactive.ts
Import CodeGenerationVersion and Stability; add both parameters after dryRun, keeping required
parameters ahead of the callback and the trailing optionals; forward both to SdkPublishAction.
Everything above that call — directory checks, missing-flag collection, version and profile-id
validation, profile lookup, language and publish-type checks — is unchanged.
src/actions/sdk/publish.ts
Add codegenVersion: CodeGenerationVersion and stability: Stability to execute, after dryRun.
Replace the two hardcoded literals with them:
const sdkGenerationResult = await sdkGenerateAction.execute(
buildDirectory,
outputDirectory,
language,
force,
false,
false,
false,
codegenVersion,
stability,
undefined,
semVersion,
packageSettingsDirectory
);
No conditional. Package-settings writing, the dry-run branch, zipping, the publish call and the
error path are all untouched.
src/actions/sdk/generate.ts
Targeted customizations message only (see Design decisions). No signature change — it already
takes codeGenVersion and stability.
src/commands/sdk/generate.ts
Take metadata off the parse result and call warnIfStabilityIgnored. No other change.
Prototype run
A scratch-branch run against api.apimatic.io exercised the threading end to end:
◇ Profile search complete.
▲ The V4 Code Generator does not currently support SDK customizations. ← v4 branch taken
◇ SDK generated successfully. ← beta stability accepted
● The generated SDK can be found at '...\sdk\csharp'.
◇ Publishing initiated. ← zip uploaded
The warning on line 2 exists only inside GenerateAction's v4 branch (generate.ts:110), so its
presence in a publish run shows the value threaded through all four layers and reached
POST /sdk/v2. Without beta stability the same run failed at generation with the error quoted
above — which is what established that stability must be settable from publish at all.
This is a prototype, not a merge gate. It reached Publishing initiated, which proves the zip
was accepted — not what landed on the registry. Per The package version must come from the
generator, what lands there today would carry the wrong version and the wrong package identity.
Known limitations
1. Package identity is wrong on the v4 path. SdkPublishAction writes
package-settings/{language}.json into the build zip, and the CLI ships it on the v4 path too —
getBuildZipPath(tempDirectory, packageSettingsDirectory) is called at generate.ts:106, before
the v4/v3 branch. The v4 generator does not consume it. Per the section above, publishing does
not compensate either, so the generated .csproj, assembly name and README install snippet carry a
default identity rather than the configured packageId — and for --publish-type sourcecode, that
content is what gets pushed to the customer's repository. The file carries package identity
only, not the version (package-settings-context.ts:17-22), so this is a separate defect from the
version problem and needs its own fix on the generator side.
3. The C# publish worker's project glob may not match a v4 SDK.
dotnet pack /sdk/*.Standard/*.Standard.csproj (CSharpPublishParams.cs:34) hardcodes the v3
generator's project layout and .Standard naming. If the v4 generator emits a different directory
or project name, the glob matches nothing and the C# publish fails in the container — after the CLI
has already reported Publishing initiated and returned success. Worth confirming against a real v4
SDK's output before v4 publishing is offered to users.
2. v4 and tracked SDK customizations. Mitigated but not solved — see Design decisions. The
build still generates and publishes without the tracked changes; the change here is that the warning
becomes specific and the wizard blocks. If a hard guard is added later, the check must run against
the versioned build context (generate.ts:98), not the root one.
Add
--codegen-versiontoapimatic sdk publishSummary
apimatic sdk generatesupports both the v3 and v4 code generators via--codegen-version.apimatic sdk publishdoes not — it hardcodes v3. This adds the same flag topublish, plus--stability(whichgeneratealready has), so a v4 SDK can be generated and published.SdkPublishActioncurrently pins both values:Both become parameters supplied by the caller. The action itself gains no conditional logic.
Background — the two services
The v4 generation path already exists and works.
GenerateAction(src/actions/sdk/generate.ts:109)branches on
codeGenVersionand callsPortalService.generateV4Sdk. The only reasonsdk publishcan't reach it is the hardcoded literals above.
Publishing itself needs no changes, because the CLI talks to two independent services:
api.apimatic.ioPOST /sdk(v3) orPOST /sdk/v2(v4)api.package-publishing.apimatic.ioPOST /publish/{profileId}/{language}The CLI generates the SDK itself, then uploads the finished zip to the publishing service
(
src/infrastructure/services/publishing-api-service.ts:46-78) along withpackageVersionandpublishType. The publishing service never learns which generator produced the zip.The package version must come from the generator — verified
An earlier draft of this issue assumed
--versionalone determined the published version, makingthe generator's copy redundant. That assumption is wrong. Traced through the
package-publishingandpackage-publishing-funcrepositories:POST /publish/{profileId}/{language}(
publishing-api-service.ts:76), which routes to thePublishGeneratedSdkhandler(
PublishingController.cs:20-28) — not thePublishhandler onPOST /publish/{profileId}.PublishGeneratedSdk.Handleuploads the CLI's zip verbatim —await _blobStorageService.UploadBlob(sdkFileStream, publishParams.GenerateBlobName())— thenenqueues a metadata message. It never transforms the SDK.
IApimaticCoreService.GeneratePublishReadySdk(...), and the core configbuilt from
ToCoreConfig(version)are referenced from exactly one place —Publish.cs:210-211,the other endpoint, where the service generates the SDK itself from an API entity. The CLI's
path never reaches it.
CSharpPublishParams.cs:33-38:-p:PackageVersion, no package-id override. The generated project file is authoritative.So the published version and package identity are whatever the generator baked in.
--versionreaches the publish log, the Mixpanel event and the constructed
PackageUrl(
CSharpConfiguration.cs:23) — but not the package.v3 is correct today only because the CLI passes the version to the generator
(
portal-service.ts:133, the 5th argument togenerateSdkViaBuildInputAsync).generateV4Sdkpasses nothing, so a v4 publish would push the generator's default version while every surrounding
record claims the requested one.
Resolution: the package version must be sent to the v4 generation endpoint exactly as it is for
v3. The generated client does not expose the parameter yet
(
generateV2SdkViaBuildInputAsync(contentType, file, language, stabilityTag)), so aTODOmarksthe call site until the client is regenerated. Threading
--versionintogenerateV4Sdkis aprerequisite for v4 publishing being correct, not merely reachable.
Design decisions
--stabilityis an explicit flag, never inferredAn earlier draft coerced stability automatically (
v4 ? BETA : STABLE) and added no flag. Thatwas rejected. The coercion would be baked into a released binary, so when v4 reaches GA every CI
pipeline pinned to an older CLI would keep silently receiving beta SDKs — a user who wrote
--codegen-version v4months earlier would be opted into unstable output without ever asking forit. Deleting the ternary at GA only helps people who upgrade.
Instead,
publishgains--stabilitymirroringgenerate. A user who wants v4 today must writeboth flags:
--codegen-version v4 --stability beta.The CLI holds no knowledge of which stability values v4 supports
--codegen-version v4on its own defaults--stabilitytoSTABLE, reaches the server, and comesback with the service's own error:
This is deliberate. Validating locally would hardcode "v4 is beta-only" into a shipped binary, so
on GA day every older CLI would reject a perfectly valid
--codegen-version v4 --stability stablewith a false error — and unlike a server error, no server-side fix can reach it. The set of
supported (codegen, stability) pairs belongs to the generation service and changes without a CLI
release.
The cost is that the error arrives after a round trip, in the service's words rather than ours.
Accepted.
--stabilityis inert on v3 — warn, in both commandsportal-service.ts:111(generateSdk, v3) takes no stability parameter; onlygenerateV4Sdk(portal-service.ts:172) does. So--codegen-version v3 --stability betaacceptsthe flag, sends nothing, and returns a stable SDK while the user believes otherwise.
Emit a warning whenever
--stabilityis explicitly provided alongside--codegen-version v3,in both
sdk generateandsdk publish.sdk generatehas this bug today.Note this is a different kind of knowledge from the point above: "which values v4 supports" is the
server's and must not be hardcoded; "the v3 code path never sends stability" is the CLI's own
knowledge of its own code, which no server change can invalidate.
Trigger is typed-and-v3, not value-is-beta —
--stability stableis equally ignored andequally worth flagging, and beta-specific wording would read as wrong after GA.
Detecting "typed" uses oclif parse metadata, confirmed present in the pinned
@oclif/corev4(
node_modules/@oclif/core/lib/interfaces/parser.d.ts:53,MetadataFlag.setFromDefault?: boolean):This is the codebase's first use of parse metadata.
Placement: the command layer, via one shared exported function (
src/prompts/sdk/stability.ts).Not inside
GenerateAction, even though both commands converge there — "did the user type thisflag?" is parse metadata available only at the command layer, so warning deeper would mean threading
a presentation-only boolean through four signatures, and it would also fire for
sdk quickstart,which has no stability flag at all. Not duplicated as an identical method on both prompt classes
either: two copies of a message required to stay identical will drift.
SDK customizations on v4 — targeted warning, plus a confirm in the wizard
On the v4 path
GenerateActionprintssdkCustomizationsNotSupportedForV4()and returns atgenerate.ts:131, before theMergeSourceTreeActionblock at line 167. A build containingsdk-source-tree/.{language}therefore generates — and now publishes — an SDK without thosechanges.
Today's warning is weak in three ways: it is unconditional (fires on every v4 run, including the
majority that have no customizations, which trains users to ignore it); its wording is a generic
capability note rather than "your changes will be dropped"; and nothing blocks — it is a
log.warnfollowed by the generation spinner and, inpublish, an irreversible registry push.hasSdkSourceTreeis already computed in the same method (generate.ts:97) and is in scope at line110. Use it:
specific message naming the language when the build does have a tracked source tree.
publishonly: a blocking confirm when a tracked source tree exists and V4 wasselected; declining returns
ActionResult.cancelled().The confirm is wizard-only because interactive
publishends in an unreversible push reached bypicking a menu item, while a CI user typed the flag deliberately and cannot answer a prompt.
Overloading
--force(which means "overwrite the destination directory") to also mean "discard mycustomizations" would be a bad conflation.
The targeted message lives in
GenerateAction, sosdk generatebenefits too.sdk quickstartis unaffected in practice —actions/sdk/quickstart.ts:204hardcodesCodeGenerationVersion.V3with no flag to change it, so its v4 branch is unreachable.Interactive mode gets a codegen prompt (PR 2)
An earlier draft stated the wizard could not produce a v4 SDK, on the grounds that
interactive = this.argv.length === 0(src/commands/sdk/publish.ts:86) means no flags werepassed. Reversed — the wizard gets its own prompt instead of inheriting a flag:
Choice labels are
V3andV4 (beta), so the beta nature of v4 is visible at the moment ofchoosing. Stability is never prompted: it does not apply to v3, and picking
V4 (beta)is theconsent to beta. A later CLI version can offer a stability choice once v4 has more than one tier.
Default/initial selection is
V3, so an inattentive Enter never lands on beta.Placement: after
selectPublishingProfile(interactive.ts:65) and beforeselectLanguage(75), because it filters the language list.
Choosing V4 narrows the language list to csharp, via a single named constant:
Filtering here does not repeat the shipped-binary trap: a wizard choice cannot be committed to
a pipeline, so a stale list costs discoverability (fixed by upgrading), not correctness. The
non-interactive path stays unfiltered and server-authoritative. The comment above is load-bearing —
without it the next reader sees a language filter and "fixes" the non-interactive path to match.
Empty intersection fails. A profile with no csharp configuration plus a V4 choice offers
nothing; exit
ActionResult.failed()(1) with a message naming the reason and pointing atconfiguring a csharp package in the App. This matches how the wizard already treats unmet
preconditions — no active publishing profiles fails outright at
interactive.ts:59-62. Hiding theV4 (beta)option instead would make it indistinguishable from v4 not existing.Stability is derived, not prompted: V4 →
BETA, V3 →STABLE, supplied by the interactiveaction to
SdkPublishAction.Out of scope
--stabilityhandling insdk quickstart— v3-only, unreachable v4 branch.sdk generateinto the v4/stability coercion debate —generatekeeps its existing--stabilityflag semantics; it only gains the shared inert-on-v3 warning and the targetedcustomizations message.
--version/SemVersion— see the assumption above..ai/skills/action.mdhas noconvention here, and a swap of
codegenVersion/stabilityis caught by the type checker sincethey are distinct enums.
Delivery — two PRs
PR 1 — non-interactive. This is the piece #304 depends on.
--codegen-versionand--stabilityflags onsdk publishcommands/sdk/publish.ts→SdkPublishNonInteractiveAction→SdkPublishAction→GenerateActionV3/STABLEdeleted fromSdkPublishAction(publish.ts:58-59)warnIfStabilityIgnored, wired into bothsdk generateandsdk publishGenerateActionSeam:
SdkPublishAction.executeis called by both action classes, so once the literals becomeparameters the interactive caller must pass something. In PR 1 it passes
CodeGenerationVersion.V3, Stability.STABLEexplicitly — behaviour identical to today, the literalssimply move up one layer. No dead code, no wizard behaviour change.
PR 2 — interactive.
selectCodegenVersionprompt (V3/V4 (beta), initialV3) afterselectPublishingProfileV4_INTERACTIVE_LANGUAGESBETAderived from the V4 choice, replacing PR 1's explicitV3, STABLEargumentsChange detail — PR 1
src/commands/sdk/publish.tsImport:
import { CodeGenerationVersion, Language, Stability } from '../../types/sdk/generate.js';Flags — added last, after
'dry-run'. Name, description,optionsanddefaultcopiedverbatim from
src/commands/sdk/generate.ts:46-55so the two commands stay consistent:Example — one v4 example added to
static examples, including both flags, since that is theonly combination that works:
test/commands/examples-parse.test.tsparses every command's examples against its own flagdefinitions, so this is validated automatically.
Destructure — both flags plus
metadata; call the shared warning before dispatching.Telemetry — add both flags to
SdkPublishValidationFailedEventso a failed v4 publish isdistinguishable from a failed v3 one. Its constructor takes
flags: Record<string, unknown>(
src/types/events/sdk-publish-validation-failed.ts:6), so no type change is needed.Dispatch — non-interactive receives both parsed values; interactive receives neither and passes
V3/STABLEinternally in PR 1. Theas CodeGenerationVersion/as Stabilitycasts match howlanguage as Languageis already handled here and howsdk generatedoes it.src/actions/sdk/publish/non-interactive.tsImport
CodeGenerationVersionandStability; add both parameters afterdryRun, keeping requiredparameters ahead of the callback and the trailing optionals; forward both to
SdkPublishAction.Everything above that call — directory checks, missing-flag collection, version and profile-id
validation, profile lookup, language and publish-type checks — is unchanged.
src/actions/sdk/publish.tsAdd
codegenVersion: CodeGenerationVersionandstability: Stabilitytoexecute, afterdryRun.Replace the two hardcoded literals with them:
No conditional. Package-settings writing, the dry-run branch, zipping, the publish call and the
error path are all untouched.
src/actions/sdk/generate.tsTargeted customizations message only (see Design decisions). No signature change — it already
takes
codeGenVersionandstability.src/commands/sdk/generate.tsTake
metadataoff the parse result and callwarnIfStabilityIgnored. No other change.Prototype run
A scratch-branch run against
api.apimatic.ioexercised the threading end to end:The warning on line 2 exists only inside
GenerateAction's v4 branch (generate.ts:110), so itspresence in a publish run shows the value threaded through all four layers and reached
POST /sdk/v2. Without beta stability the same run failed at generation with the error quotedabove — which is what established that stability must be settable from
publishat all.This is a prototype, not a merge gate. It reached
Publishing initiated, which proves the zipwas accepted — not what landed on the registry. Per The package version must come from the
generator, what lands there today would carry the wrong version and the wrong package identity.
Known limitations
1. Package identity is wrong on the v4 path.
SdkPublishActionwritespackage-settings/{language}.jsoninto the build zip, and the CLI ships it on the v4 path too —getBuildZipPath(tempDirectory, packageSettingsDirectory)is called atgenerate.ts:106, beforethe v4/v3 branch. The v4 generator does not consume it. Per the section above, publishing does
not compensate either, so the generated
.csproj, assembly name and README install snippet carry adefault identity rather than the configured
packageId— and for--publish-type sourcecode, thatcontent is what gets pushed to the customer's repository. The file carries package identity
only, not the version (
package-settings-context.ts:17-22), so this is a separate defect from theversion problem and needs its own fix on the generator side.
3. The C# publish worker's project glob may not match a v4 SDK.
dotnet pack /sdk/*.Standard/*.Standard.csproj(CSharpPublishParams.cs:34) hardcodes the v3generator's project layout and
.Standardnaming. If the v4 generator emits a different directoryor project name, the glob matches nothing and the C# publish fails in the container — after the CLI
has already reported
Publishing initiatedand returned success. Worth confirming against a real v4SDK's output before v4 publishing is offered to users.
2. v4 and tracked SDK customizations. Mitigated but not solved — see Design decisions. The
build still generates and publishes without the tracked changes; the change here is that the warning
becomes specific and the wizard blocks. If a hard guard is added later, the check must run against
the versioned build context (
generate.ts:98), not the root one.