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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,10 @@ Examples:

### Release workflow

Prepare the complete release candidate, including `docs/releases/vX.Y.Z.md`, package versions, changelog, and root documentation index, before pushing the release PR. Complete the PR template checklist. Commit the preflight-validated tree, push the PR, and require Node 24 and Node 26 checks on its exact head before merging. Verify that merged main has the same tree and successful CI, then tag that merged commit. A different merged tree requires a newly validated candidate before tagging.

Agent delivery ends after the tag-triggered workflow creates and verifies the draft and its artifacts, followed by task-owned local and remote cleanup. Agents must never access GitHub through a browser, publish releases, or dispatch package publication. The maintainer signs in through their own browser and clicks Publish; that event triggers npm publication. Preserve unrelated work, contributor-owned branches, release tags, and abandonment markers during cleanup.

1. Read current package version from `package.json` and the latest dated release section in `CHANGELOG.md`.
2. Determine the target semver bump based on `Unreleased` changes or the user's explicit target.
3. For patch releases, verify the target is the next patch after the latest released version.
Expand All @@ -298,7 +302,7 @@ Examples:
13. Confirm `dist/assets/typescript-wsdl-client-agent-skill-v<version>.zip` and `dist/assets/SHA256SUMS` exist after preflight.
14. If preflight fails or a separate command changes tracked release files afterward, finish the fixes and rerun preflight on the new final candidate. Preflight itself fails if it changes tracked content.
15. After preflight passes, commit the exact validated release tree without changing tracked files.
16. Tag and push the release commit without rerunning preflight, CI, package validation, or skill packaging.
16. Push and merge the release PR after its required checks, then tag the validated merged main commit without rerunning local preflight, CI, package validation, or skill packaging for an unchanged tree.

### Abandoned release candidates

Expand All @@ -310,7 +314,7 @@ The draft and package workflows must call the shared release-state helper before

Drafting, abandonment, and package delivery share a non-cancelling per-tag concurrency group. Preserve that serialization so a marker cannot race package publication for the same candidate.

The marker cannot disable GitHub's Publish button while a draft exists. The supported normal path is to review and publish an unmarked draft through GitHub's Release page; the guarded package workflow then delivers npmjs with provenance. Abandonment retires the draft only after marker verification. Manual package dispatch is recovery-only and enforces the same published-release and marker checks.
The marker cannot disable GitHub's Publish button while a draft exists. The maintainer reviews and publishes an unmarked draft through GitHub's Release page after agent delivery has ended; the guarded package workflow then delivers npmjs with provenance. Abandonment retires the draft only after marker verification. Manual package dispatch is maintainer-operated recovery only and enforces the same published-release and marker checks.

Do not add a workflow that publishes the draft and assumes its normal `GITHUB_TOKEN` will trigger the package workflow. GitHub suppresses that recursive workflow event. The supported UI publication path produces the external `release: published` event, while manual package dispatch remains the explicit recovery path.

Expand Down Expand Up @@ -400,7 +404,8 @@ Keep `CHANGELOG.md` as the canonical version history. Write release notes as a c
- Review the complete uncommitted release diff.
- Run `npm run release:preflight -- vX.Y.Z` once on the final uncommitted release tree.
- Commit the exact validated tree without further tracked-file changes.
- Tag and push without rerunning release validation.
- Push and merge the complete release PR, verify main's tree, then tag the merged commit without repeating unchanged local validation.
- Verify the draft workflow and artifacts, finish task-owned cleanup, and stop for maintainer publication.
- Use the skill ZIP produced by release preflight; do not run a separate skill package step unless preflight is not being used.

### Editing code that affects CLI, OpenAPI, or gateway
Expand Down
3 changes: 3 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ This is a TypeScript code generator that transforms WSDL/XSD SOAP service defini
- Post-release patch work uses the next patch version in commit titles even before `package.json` is bumped.
- A commit title target such as `Version: 0.30.3` does not mean the repository is ready for tag `v0.30.3`.
- Every release commit must include the matching `docs/releases/vX.Y.Z.md` release notes file.
- Prepare release notes and metadata before pushing the release PR; tag the validated merged state of main.
- Agents stop after draft/artifact verification and task-owned cleanup; the maintainer publishes manually.
- Agents never access GitHub through a browser or publish releases or packages.
- Node.js >= 24.0.0, ESM-only (`type: "module"`), strict TypeScript.
- GitHub CI must test Node 24 as the supported floor and Node 26 as the current line.
- GitHub push and PR CI is a fast hosted signal; run `npm run release:preflight -- vX.Y.Z` once on the final uncommitted release tree before committing and tagging.
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,15 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve

## [Unreleased]

## [1.1.2] - 2026-09-25

- fix(client): normalize singleton SOAP responses for repeated elements
- docs(release): assign draft preparation and cleanup to agents and publication to the maintainer

## [1.1.1] - 2026-09-25

This candidate was abandoned before publication because singleton SOAP responses violated generated array contracts.

- fix(compiler): preserve wrapping sequence occurrence bounds on element particles
- chore(deps): refresh soap and Vitest patch floors and generated app soap pins

Expand Down
4 changes: 4 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,12 +209,16 @@ Run `npm run maint:deps` when preparing a release so root dependency minimums an

Every release commit must include `docs/releases/vX.Y.Z.md`. Run `npm run release:preflight -- vX.Y.Z` once on the final uncommitted release tree and confirm the release ZIP exists under `dist/assets/`.

Include release notes, versions, changelog, and the root documentation link before pushing the release PR. After merging, verify that main's tree matches the preflight-validated tree before tagging the merged commit. Follow the [release delivery procedure](docs/releases/README.md#candidate-publication).

Set the release version before preflight. Preflight verifies release metadata, writes only ignored build and temporary outputs, and fails if it changes tracked content.

After preflight passes, commit the exact validated tree without further tracked-file changes. Tag and push that commit without rerunning preflight, full CI, package validation, or skill packaging. If another command changes tracked files, complete the fixes and rerun preflight on the new final candidate.

Pushing the matching `vX.Y.Z` tag creates or updates a GitHub draft release from the release notes file after CI passes. The draft release workflow strips the release-note H1 for GitHub display, packages and uploads the agent skill ZIP, and refuses to mutate a published non-draft release.

The agent verifies the draft and artifacts, cleans up task-owned local and remote branches and temporary outputs, and stops. The maintainer publishes through their own browser. Agents never access GitHub through a browser or publish releases or packages.

When an IDE inspection tool is available, run it on touched Markdown files and generator files that embed generated source. Without IDE inspections, verify relative Markdown links target concrete files or headings and keep TypeScript fenced examples syntactically valid.

## Adding Test Fixtures
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ See [CLI Reference](docs/cli-reference.md) for all flags and examples.
| [Production Guide](docs/production.md) | CI/CD, validation, logging |
| [Troubleshooting](docs/troubleshooting.md) | Common issues and debugging |
| [Release Notes](docs/releases/README.md) | Per-version GitHub release notes used by the draft release workflow |
| [v1.1.2 Release Notes](docs/releases/v1.1.2.md) | Sequence occurrence bounds and singleton SOAP response normalization |
| [v1.1.1 Release Notes](docs/releases/v1.1.1.md) | Wrapping sequence occurrence bounds and generated array contracts |
| [v1.1.0 Release Notes](docs/releases/v1.1.0.md) | Automatic generated TypeScript preambles and consumer lint guidance |

Expand Down
18 changes: 13 additions & 5 deletions docs/releases/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,19 @@ When an abandoned major or minor candidate contains the complete launch narrativ

The supported release path keeps final-form tags immutable:

1. Run release preflight on the final uncommitted tree.
2. Commit the exact validated tree and create `vX.Y.Z`.
3. Push the branch and tag so the draft workflow creates the release and skill asset.
4. Review and publish the unmarked draft through GitHub's Release page.
5. Let the guarded package workflow publish to npmjs with provenance.
1. Prepare versions, changelog, release notes, and the documentation index before pushing the release PR.
2. Run release preflight on the final uncommitted tree and commit that exact validated tree.
3. Push the PR and require Node 24 and Node 26 checks on its final head before merging.
4. Verify the merged main tree matches the validated tree and require CI on the merged commit.
5. Tag the validated merged main commit and push the tag to start the draft workflow.
6. Verify the draft, release notes, uploaded skill ZIP, checksum manifest, and artifact verification results.
7. Clean up task-owned merged branches and temporary outputs, then hand the draft to the maintainer.
8. The maintainer reviews the draft in their own browser and clicks Publish.
9. The guarded package workflow publishes to npmjs with provenance.

The agent stops after draft verification and cleanup. Agents must never access GitHub through a browser, publish a release, or dispatch package publication. Use authenticated git and GitHub CLI commands for preparation and verification. Preserve unrelated work, contributor-owned branches, immutable release tags, and abandonment markers during cleanup.

If the merged tree differs from the preflight-validated tree, stop before tagging and prepare a newly validated candidate. Do not rerun preflight for an unchanged validated tree.

Manual package dispatch is a recovery path after the GitHub Release is published. It enforces the same tag, marker, reachability, draft, and prerelease checks.

Expand Down
2 changes: 2 additions & 0 deletions docs/releases/v1.1.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,6 @@ This patch preserves the previous behavior across `xs:choice`, `xs:all`, zero-ma

Release tag: `v1.1.1`.

This candidate was abandoned before publication. Singleton SOAP responses could remain scalar and fail gateway serialization despite generated array contracts. The immutable tag and abandonment marker retain its release identity.

Fixes [issue #141](https://github.com/TechSpokes/typescript-wsdl-client/issues/141) through [PR #145](https://github.com/TechSpokes/typescript-wsdl-client/pull/145). Thanks to [BCsabaEngine](https://github.com/BCsabaEngine) for the original diagnosis and contribution.
42 changes: 42 additions & 0 deletions docs/releases/v1.1.2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# TypeScript WSDL Client v1.1.2

## Consistent arrays from wrapping sequences

This patch corrects generated array contracts and singleton SOAP response conversion for wrapping sequences. See the [project overview](../../README.md) for the generation pipeline.

## What This Improves

An element inside a repeated `xs:sequence` now receives the sequence's occurrence bounds in the compiled catalog and generated types. A single returned address becomes a one-element array, and multiple addresses retain their order. Generated REST gateways can serialize both responses successfully.

The generated client uses existing catalog metadata to normalize repeated properties, so direct client consumers receive the same correction as gateways. Optional properties remain absent when omitted, and existing null handling is preserved.

## Highlights

- Propagate occurrence bounds through uninterrupted sequences, including nested sequences.
- Normalize singleton primitive and complex responses to their declared array shape.
- Preserve inherited and aliased type metadata during response conversion.
- Verify real SOAP responses with REST wrapper flattening enabled and disabled.
- Exercise the correction through an installed package and its generated client.

## Upgrade Notes

Update the package and regenerate clients, OpenAPI specifications, and gateways. Review affected scalar properties that become arrays and required properties that become optional. Corrected array metadata can also activate existing REST wrapper flattening, changing an object wrapper into an array.

This release retains previous sequence-boundary handling for `xs:choice`, `xs:all`, zero-maximum sequences, disabled elements, and wildcards. It adds no finite-array-length or sequence-order validation and no CLI options or catalog fields. The generated metadata interface adds an optional `RepeatedElements` map; custom metadata without that map retains its previous behavior.

Version 1.1.1 was abandoned before publication. This release includes its sequence compiler correction and dependency maintenance, together with the runtime correction discovered during final review.

## Validation

- Real SOAP transport tests cover singleton and repeated responses and optional request omission.
- Generated client and gateway tests cover both REST wrapper representations.
- Full CI, conformance, and pipeline smoke validation passed.
- Installed package generation, TypeScript contracts, and SOAP response conversion passed.
- NPM package contents and the agent skill artifact were validated.
- Release preflight passed against the target tag.

## Notes

Release tag: `v1.1.2`.

The sequence compiler correction originated in [PR #145](https://github.com/TechSpokes/typescript-wsdl-client/pull/145) for [issue #141](https://github.com/TechSpokes/typescript-wsdl-client/issues/141). Thanks to [BCsabaEngine](https://github.com/BCsabaEngine) for the original diagnosis and contribution.
2 changes: 2 additions & 0 deletions docs/supported-patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ These patterns are handled end-to-end: WSDL parsing, TypeScript type generation,

Wrapping sequence bounds propagate through uninterrupted sequence ancestry to element array types and optionality. Traversal through `xs:choice`, `xs:all`, or a sequence with `maxOccurs="0"` retains the previous flattened behavior; wildcard and disabled-element handling are unchanged. This does not enforce sequence ordering or finite array lengths at runtime.

Generated clients normalize present, non-null singleton responses into arrays for repeated element properties. Real SOAP transport regressions cover singleton and multiple items, omitted optional request fields, and gateway serialization with wrapper flattening enabled and disabled. Regenerate clients to receive this runtime correction; omitted optional response properties remain absent.

- Complex types with `<xs:sequence>`, `<xs:all>`, and `<xs:choice>` compositors, including recursive nesting
- Simple content with attributes using the `$value` pattern to preserve text content alongside attribute properties
- Named simple type restrictions and enumerations emitted as TypeScript aliases and OpenAPI scalar schemas
Expand Down
10 changes: 7 additions & 3 deletions examples/generated-output/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export type WeatherResponse<ResponseType, HeadersType = Record<string, unknown>>
* @property dataTypes - Metadata for data types used in serialization/deserialization.
* @property dataTypes.Attributes - Maps type names to lists of property names that should be treated as XML attributes.
* @property dataTypes.ChildrenTypes - Maps type names to their child element types for recursive processing.
* @property dataTypes.RepeatedElements - Identifies child properties that must retain their array shape for singleton responses.
*
*
* @note Have fun with the generated client! If TechSpokes made your day (or a week),
Expand Down Expand Up @@ -324,6 +325,7 @@ export class Weather {

// Get child type mapping for recursive processing with correct types
const childrenTypes: Readonly<Record<string, string>> = (typeName && this.dataTypes?.ChildrenTypes?.[typeName]) || {};
const repeatedElements: readonly string[] = (typeName && this.dataTypes?.RepeatedElements?.[typeName]) || [];
const result: any = {};

// Preserve text content for mixed XML elements
Expand All @@ -347,9 +349,11 @@ export class Weather {
}
// Recursively convert child elements with their specific type info
const childType: string | undefined = childrenTypes[k];
result[k] = Array.isArray(v)
? v.map(node => this.fromSoapResult(node, childType))
: this.fromSoapResult(v, childType);
// node-soap can return a scalar when repetition is declared on a wrapping sequence (#141).
const value = v != null && !Array.isArray(v) && repeatedElements.includes(k) ? [v] : v;
result[k] = Array.isArray(value)
? value.map(node => this.fromSoapResult(node, childType))
: this.fromSoapResult(value, childType);
}

return result;
Expand Down
22 changes: 22 additions & 0 deletions examples/generated-output/client/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export interface WeatherDataTypes {
Attributes: Record<string, readonly string[]>;
/** Maps type names to their child element types for recursive processing */
ChildrenTypes: Record<string, Readonly<Record<string, string>>>;
/** Maps type names to repeated child properties; optional for custom metadata compatibility. */
RepeatedElements?: Record<string, readonly string[]>;
}

export const WEATHER_DATA_TYPES: WeatherDataTypes = {
Expand Down Expand Up @@ -97,5 +99,25 @@ export const WEATHER_DATA_TYPES: WeatherDataTypes = {
"GetCityWeatherByZIPResponse": {
"GetCityWeatherByZIPResult": "WeatherReturn"
}
},
"RepeatedElements": {
"ArrayOfForecast": [
"Forecast"
],
"ArrayOfWeatherDescription": [
"WeatherDescription"
],
"Forecast": [],
"ForecastReturn": [],
"GetCityForecastByZIP": [],
"GetCityForecastByZIPResponse": [],
"GetCityWeatherByZIP": [],
"GetCityWeatherByZIPResponse": [],
"GetWeatherInformation": [],
"GetWeatherInformationResponse": [],
"POP": [],
"Temp": [],
"WeatherDescription": [],
"WeatherReturn": []
}
} as const;
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@techspokes/typescript-wsdl-client",
"version": "1.1.1",
"version": "1.1.2",
"description": "Turn legacy WSDL/SOAP services into typed TypeScript clients, OpenAPI 3.1 specs, and production-ready Fastify REST gateways. Built for enterprise SOAP modernization.",
"keywords": [
"wsdl",
Expand Down
Loading
Loading