diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 046eaa9..85bafd5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,6 +3,10 @@ name: CI (build + client samples) pull_request: branches: - main + # Run on demand against any branch. The sample jobs exercise LD_API_KEY against + # the real 'openapi' project, so this doubles as a check that that credential + # still works — the token audit can only confirm it reads, not that it writes. + workflow_dispatch: env: LD_API_KEY: ${{ secrets.LD_API_KEY }} @@ -66,6 +70,24 @@ jobs: with: name: api-clients path: /tmp/api-clients + selfcheck: + name: Release automation selfcheck + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Ensure PyYAML + run: python3 -c "import yaml" 2>/dev/null || pip install --quiet pyyaml + # Trust-policy checks self-skip here: the policy lives in the private + # launchdarkly/.github-private, which GITHUB_TOKEN cannot read. Run this + # locally for the full set. + - name: Selfcheck + # GITHUB_TOKEN is enough to read the pinned action repos (all public), which + # is what the ACTION-INPUTS check needs. Without it that check resolves + # nothing and reports SKIP. + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: ./scripts/release/selfcheck.sh + test-go: runs-on: ubuntu-latest needs: build @@ -114,7 +136,7 @@ jobs: with: name: targets path: targets - - uses: ruby/setup-ruby@3ff19f5e2baf30647122352b96108b1fbe250c64 # v1 + - uses: ruby/setup-ruby@319994f95fa847cf3fb3cd3dbe89f6dcde9f178f # v1 with: ruby-version: '2.7' - name: Prepare RubyGems / ffi @@ -191,6 +213,7 @@ jobs: name: Check Success needs: - build + - selfcheck - test-go - test-python - test-ruby diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3dfd06f..eda1479 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,20 +1,129 @@ +name: Release client libraries + +# Implements the full release runbook. +# +# Dispatch with no inputs for a normal release. Everything the runbook asks a +# human to look up — whether gonfalon is healthy, whether prod is caught up, the +# bump type, the version, the changelog body — is derived in the preflight job, +# which aborts with a specific reason rather than releasing on a guess. on: workflow_dispatch: inputs: releaseVersion: - description: Next release version - required: true - type: string - changeLog: - description: Pending changelog - required: true + description: 'Override the derived version (bare semver, e.g. 24.0.0). Blank = derive.' + required: false type: string + bumpType: + description: 'Override the bump inferred from the changelog headings.' + required: false + type: choice + default: '' + options: ['', 'major', 'minor', 'patch'] + dryRun: + description: 'Rehearse: clone and commit, but push nothing and publish nothing.' + required: false + type: boolean + default: false + force: + description: 'Delete tags left behind by a previous failed attempt instead of aborting.' + required: false + type: boolean + default: false + openDownstreamPr: + description: 'Open the downstream PRs (gonfalon and terraform-provider-launchdarkly).' + required: false + type: boolean + default: true + mirrorPublic: + description: 'Squash-mirror this repo onto the public launchdarkly/ld-openapi.' + required: false + type: boolean + default: true + skipCiGate: + description: "Release even if gonfalon's main branch is red." + required: false + type: boolean + default: false + skipSpecParity: + description: "Release the spec production is serving even if gonfalon main is ahead. Check the gonfalon PR's changelog entries before merging it." + required: false + type: boolean + default: false + +# Two concurrent releases would race on client-repo tags. +concurrency: + group: release-client-libraries + cancel-in-progress: false jobs: + # Runs on every release, not just rehearsals. A bad registry credential would + # otherwise surface only after `make push` had already tagged five client repos, + # which is the case that forces the delete-the-tags-and-retry dance. A dry run + # cannot cover this on its own: create-release is skipped, pr-downstream skips + # PR creation, and the publish dry-run scripts never authenticate to a registry. + token-audit: + name: Token audit + # A reusable workflow is capped by what its caller grants, so id-token has to + # be granted here as well as inside token-audit.yml. + permissions: + id-token: write + contents: read + uses: ./.github/workflows/token-audit.yml + with: + # Only demand the credentials this run will actually use, so a switched-off + # job cannot block the release — and so the flow can be rehearsed before + # those tokens have been created. + requireMirrorToken: ${{ inputs.mirrorPublic }} + requireDownstreamToken: ${{ inputs.openDownstreamPr }} + secrets: inherit + + preflight: + name: Preflight + runs-on: ubuntu-latest + # Required for the OIDC token the octosts-action step exchanges. + permissions: + id-token: write + contents: read + outputs: + version: ${{ steps.derive.outputs.version }} + major: ${{ steps.derive.outputs.major }} + bump: ${{ steps.derive.outputs.bump }} + changelog: ${{ steps.derive.outputs.changelog }} + previous_version: ${{ steps.derive.outputs.previous_version }} + steps: + - uses: actions/checkout@v4 + # Reading gonfalon needs a credential the releaser token does not have. + # Minted at runtime rather than stored: no long-lived secret, and the trust + # policy in launchdarkly/.github-private is what grants it. + - uses: launchdarkly/octosts-action@v1 + id: sts + with: + identity: ld-openapi-downstream + scope: launchdarkly + - name: Derive release inputs and check gates + id: derive + # Two tokens: the releaser token for the client repos (tag checks, and tag + # deletes when force=true), and the federated token for reading gonfalon. + env: + GH_TOKEN: ${{ secrets.BOT_TOKEN }} + GONFALON_TOKEN: ${{ steps.sts.outputs.token }} + VERSION_OVERRIDE: ${{ inputs.releaseVersion }} + BUMP_OVERRIDE: ${{ inputs.bumpType }} + FORCE: ${{ inputs.force }} + SKIP_CI_GATE: ${{ inputs.skipCiGate }} + SKIP_SPEC_PARITY: ${{ inputs.skipSpecParity }} + run: ./scripts/release/preflight.sh + release: + name: Build and publish clients + needs: [preflight, token-audit] runs-on: ubuntu-latest env: - LD_RELEASE_VERSION: ${{ inputs.releaseVersion }} + LD_RELEASE_VERSION: ${{ needs.preflight.outputs.version }} + DRY_RUN: ${{ inputs.dryRun }} + # The scripts/release-dry-run/*.sh scripts copy the artifacts they would + # have published into this directory, and fail without it. + LD_RELEASE_ARTIFACTS_DIR: ${{ github.workspace }}/dry-run-artifacts GH_TOKEN: ${{ secrets.BOT_TOKEN }} PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} RUBYGEM_API_KEY: ${{ secrets.RUBYGEM_API_KEY }} @@ -64,24 +173,145 @@ jobs: # * Push built clients to their respective Github repos # * Tag those commits with the release version # * Publish each client to it's respective platform (e.g. Rubygems) + # With DRY_RUN=true it clones and commits but pushes/publishes nothing. - name: Publish clients run: | export PATH=/opt/gradle/bin:/usr/local/go/bin:$HOME/.rubygems/bin:$PATH export GEM_HOME=$HOME/.rubygems + mkdir -p "$LD_RELEASE_ARTIFACTS_DIR" ./scripts/release/publish.sh - # This step creates a release with changelog from the tag + # Lets a rehearsal be inspected: these are the exact artifacts a real run + # would have pushed to the registries. + - name: Upload dry-run artifacts + if: ${{ inputs.dryRun }} + uses: actions/upload-artifact@v4 + with: + name: dry-run-artifacts + path: ${{ github.workspace }}/dry-run-artifacts + if-no-files-found: error + + verify-published: + name: Verify published + needs: [preflight, release] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Poll registries and client repo tags + if: ${{ !inputs.dryRun }} + env: + GH_TOKEN: ${{ secrets.BOT_TOKEN }} + run: ./scripts/release/verify-published.sh "${{ needs.preflight.outputs.version }}" + - name: Skipped + if: ${{ inputs.dryRun }} + run: echo "Dry run — nothing was published, so there is nothing to verify." + create-release: + name: Create GitHub release + needs: [preflight, verify-published] runs-on: ubuntu-latest permissions: contents: write - needs: release strategy: matrix: repo: ['api-client-go', 'api-client-java', 'api-client-python', 'api-client-ruby', 'api-client-typescript'] steps: - uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0 + if: ${{ !inputs.dryRun }} with: repo: ${{ matrix.repo }} token: ${{ secrets.BOT_TOKEN }} - tag: v${{ inputs.releaseVersion }} - body: ${{ inputs.changeLog }} + tag: v${{ needs.preflight.outputs.version }} + body: ${{ needs.preflight.outputs.changelog }} + + downstream-gonfalon: + name: Gonfalon PR (changelog + api-client-go bump) + needs: [preflight, verify-published] + # Skipped on a rehearsal. The pinned pr-downstream has no `dryrun` input, so + # passing one was silently ignored and a dry run would open a real PR; it only + # avoided doing so because the version it bumps to did not exist yet. + if: ${{ inputs.openDownstreamPr && !inputs.dryRun }} + permissions: + id-token: write + contents: read + # gonfalon's `make tidy` resolves Go modules from private launchdarkly repos. + # Without this, go consults proxy.golang.org for them and gets a 404. The + # equivalent workflow in launchdarkly/foundation sets the same variable. + env: + GOPRIVATE: github.com/launchdarkly/* + # gonfalon's `make tidy` and gazelle need bazel and a beefy runner, matching + # the gonfalon job in launchdarkly/foundation's pr-downstream workflow. + runs-on: runs-on=${{ github.run_id }}/runner=ub24-large-amd + steps: + - uses: runs-on/action@15385172809cc0346c6821b00c3c3dd2598785b4 # v2.1.0 + # Checked out at the workspace root; pr-downstream clones gonfalon into a + # subdirectory, so the update script stays reachable by absolute path. + - uses: actions/checkout@v4 + - uses: bazel-contrib/setup-bazel@083175551ceeceebc757ebee2127fde78840ca77 # 0.18.0 + with: + bazelisk-version: '1.x' + bazelisk-cache: true + - uses: launchdarkly/octosts-action@v1 + id: sts + with: + identity: ld-openapi-downstream + scope: launchdarkly + - uses: launchdarkly-labs/pr-downstream@08b3feb5df321ebfed2d0855493e81edbb06a2cf # v1.4.0 + with: + repository: launchdarkly/gonfalon + token: ${{ steps.sts.outputs.token }} + author: launchdarkly-octoauth[bot]<226478175+launchdarkly-octoauth[bot]@users.noreply.github.com> + # A user, not a team: an OctoSTS App installation token cannot request team + # reviewers ("If requesting team reviewers a 'repo' scoped PAT is required"), + # which failed the job even though the PR was created. CODEOWNERS in the + # downstream repo still pulls in the owning team. + reviewers: ${{ github.actor }} + title: '[bot] API ${{ needs.preflight.outputs.version }}: release changelog and bump api-client-go' + branch: bump-api-client-go/${{ needs.preflight.outputs.version }} + commit-message: '[bot] Release API ${{ needs.preflight.outputs.version }}: changelog + api-client-go bump' + # A major bump rewrites import paths across ~18 Go files and the Bazel + # graph, so this wants human eyes before it merges. + auto-merge: false + update-command: $GITHUB_WORKSPACE/scripts/release/gonfalon-update.sh ${{ needs.preflight.outputs.version }} + + downstream-terraform-provider: + name: Terraform provider PR (api-client-go bump) + needs: [preflight, verify-published] + if: ${{ inputs.openDownstreamPr && !inputs.dryRun }} + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + steps: + - uses: launchdarkly/octosts-action@v1 + id: sts + with: + identity: ld-openapi-downstream + scope: launchdarkly + - uses: launchdarkly-labs/pr-downstream@08b3feb5df321ebfed2d0855493e81edbb06a2cf # v1.4.0 + with: + repository: launchdarkly/terraform-provider-launchdarkly + token: ${{ steps.sts.outputs.token }} + author: launchdarkly-octoauth[bot]<226478175+launchdarkly-octoauth[bot]@users.noreply.github.com> + reviewers: ${{ github.actor }} + title: 'chore(deps): bump api-client-go to v${{ needs.preflight.outputs.version }}' + branch: bump-api-client-go/${{ needs.preflight.outputs.version }} + # The provider uses release-please, which reads this prefix. `chore` keeps + # the bump from cutting a provider release on its own; change to `feat` + # if a client bump should ship one. + commit-message: 'chore(deps): bump api-client-go to v${{ needs.preflight.outputs.version }}' + # A major bump rewrites the import path across ~220 files here. + auto-merge: false + update-command: make update-api-client-go API_CLIENT_GO_VERSION=${{ needs.preflight.outputs.version }} + + mirror-public: + name: Mirror to public ld-openapi + needs: [preflight, verify-published] + if: ${{ inputs.mirrorPublic }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Squash-mirror private main onto the public repo + env: + BOT_TOKEN: ${{ secrets.BOT_TOKEN }} + DRY_RUN: ${{ inputs.dryRun }} + run: ./scripts/release/mirror-public.sh "${{ needs.preflight.outputs.version }}" diff --git a/.github/workflows/token-audit.yml b/.github/workflows/token-audit.yml new file mode 100644 index 0000000..433234a --- /dev/null +++ b/.github/workflows/token-audit.yml @@ -0,0 +1,67 @@ +name: Token audit + +# Read-only check that every credential the release needs still has the +# permissions it needs. Makes no changes, so it is safe to run at any time. +# +# Exists because a dry run cannot answer this on its own: create-release is +# skipped, pr-downstream skips PR creation, and the publish dry-run scripts never +# authenticate to a registry. `release.yml` runs this as a gate before it builds, +# so a bad credential fails the run before any tag has been pushed rather than +# halfway through publishing. +on: + workflow_dispatch: + inputs: + requireMirrorToken: + description: "Treat BOT_TOKEN lacking push on the public ld-openapi as a failure." + required: false + type: boolean + default: true + requireDownstreamToken: + description: 'Require push on the downstream PR targets.' + required: false + type: boolean + default: true + workflow_call: + inputs: + requireMirrorToken: + required: false + type: boolean + default: true + requireDownstreamToken: + required: false + type: boolean + default: true + +permissions: + # id-token is what lets the audit mint the same federated token the release + # jobs use; without it the exchange fails before it reaches the trust policy. + id-token: write + contents: read + +jobs: + audit: + name: Audit release credentials + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + # Mint the same federated token the release jobs use, so the audit proves + # the trust policy works rather than inspecting a stored secret. + - uses: launchdarkly/octosts-action@v1 + id: sts + continue-on-error: true + with: + identity: ld-openapi-downstream + scope: launchdarkly + - name: Audit + env: + REQUIRE_MIRROR_TOKEN: ${{ inputs.requireMirrorToken }} + REQUIRE_DOWNSTREAM_TOKEN: ${{ inputs.requireDownstreamToken }} + BOT_TOKEN: ${{ secrets.BOT_TOKEN }} + STS_TOKEN: ${{ steps.sts.outputs.token }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} + RUBYGEM_API_KEY: ${{ secrets.RUBYGEM_API_KEY }} + CENTRAL_PORTAL_USERNAME: ${{ secrets.CENTRAL_PORTAL_USERNAME }} + CENTRAL_PORTAL_PASSWORD: ${{ secrets.CENTRAL_PORTAL_PASSWORD }} + LD_API_KEY: ${{ secrets.LD_API_KEY }} + run: ./scripts/release/token-audit.sh diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..e2ef89f --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,164 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## What this repo is + +A code *generator*, not a library. It turns LaunchDarkly's OpenAPI spec into REST API client libraries for Go, Java, Python, Ruby, and TypeScript, then pushes each generated client into its own public `launchdarkly/api-client-` repo and publishes to the language package registry. + +Two consequences that shape everything: + +1. **The spec is not in the repo.** `make` downloads it from `https://app.launchdarkly.com/api/v2/openapi.json` at build time, so build output depends on what is currently deployed to production. +2. **Almost all client code is generated.** The only hand-written, committed artifacts are the Makefile, the mustache template overrides, the samples, and the release scripts. Client source appears under `targets/` (gitignored) after a build. + +## Build + +Requires `java`, `curl`, `jq`. The generator jar is downloaded to the repo root on first build. + +```bash +make # all clients + both HTML doc targets +make go # single target: go | java | python | ruby | typescript-axios +make html html2 # doc targets only +make targets_docker # run `make all` in the release container image (matches CI) +make clean # rm -rf targets/ client-clones/ +``` + +Output layout (all gitignored): + +- `targets/openapi.json` — downloaded spec, the input to every generator run +- `targets/api-client-/` — generated client +- `targets/html/`, `targets/html2/` — generated API docs +- `targets/build/api-client-/` — throwaway copy used by `build_clients`/`publish`, so build artifacts never land in a source tree + +Version comes from `LD_RELEASE_VERSION` (default `0.0.1-SNAPSHOT`); `TAG` defaults to it. + +## Verifying a change + +No unit test suite. Validation is three layers. + +**1. Static checks over the release automation** — seconds, no side effects: + +```bash +./scripts/release/selfcheck.sh +``` + +Every check corresponds to a defect that shipped and cost a dispatch-and-wait cycle: a job minting a federated token without `id-token: write`; an API call the trust policy doesn't permit; a token-scope pattern that can never match; a downstream target missing from its allowlist; a referenced secret that doesn't exist; an input passed to a pinned action that the action doesn't declare; a `make` dry-run target that is a silent no-op; `mapfile`/bare `sed -i`, which break on macOS bash 3.2. + +Two rules it enforces about itself, both learned the hard way: + +- **A check that verified nothing reports SKIP or FAIL, never PASS.** Several checks derive a count by extracting from the workflows, and a zero there means the extractor broke far more often than it means the repo is clean — so they cross-check against the raw file text and fail on a mismatch. +- **An unrecognised API endpoint is a failure, not an assumed pass.** When adding a call made with the federated token, add its endpoint to the endpoint→permission table. That table is what turns a would-be 403 into a local error. + +Checks needing credentials self-skip, so it is useful in CI with less coverage. + +**2. Does the generated code compile and package?** + +```bash +LD_RELEASE_VERSION=0.0.1 make +make BUILD_TARGETS="go java python ruby typescript-axios" build_clients +``` + +`build_clients` runs `scripts/build/.sh` per target. Narrow it while iterating: `make BUILD_TARGETS=go build_clients`. + +**3. Does the client work against the API?** Each `samples//` program creates a multivariate flag `test-`, prints it, and deletes it — live integration tests needing `LD_API_KEY`. Run one after `make `: + +```bash +cd samples/go && make +cd samples/python && pip install -e ../../targets/api-client-python && python main.py +cd targets/api-client-ruby && gem build launchdarkly_api.gemspec && gem install ./launchdarkly_api*.gem && cd ../../samples/ruby && ruby main.rb +cd targets/api-client-java && mvn clean install && cd ../../samples/java && mvn clean install exec:java # pom.xml has an API_CLIENT_VERSION placeholder CI seds +cd targets/api-client-typescript-axios && npm install && npm run build && npm link && cd ../../samples/typescript-axios && npm link launchdarkly-api-typescript && npm install && npm run build && npm start +``` + +Sample code is spliced into each client's README, so sample edits change published docs. + +## Architecture notes + +### Template overrides (`swagger-codegen-templates/`) + +Only the HTTP-layer file per language is overridden; everything else uses stock generator templates. Each override is a **verbatim copy of the upstream generator template** with additions fenced by `// CUSTOM-START` / `// CUSTOM-END`, injecting the default `LD-API-Version` header (and, for TypeScript, `X-LaunchDarkly-User-Agent`). + +**When bumping `GENERATOR_VERSION` you must re-copy the upstream templates and re-apply the CUSTOM blocks** — otherwise the override silently pins old generator behaviour. The Makefile header lists the upstream URL for each file. + +### Two versions that move independently + +- `GENERATOR_VERSION` — openapi-generator release used +- `LATEST_API_VERSION` (e.g. `20240415`) — the API version the spec corresponds to, baked into every client as the default `LD-API-Version` header. Update it when the spec's API version changes, or clients keep sending a stale header. + +Per-language flags live in `CODEGEN_PARAMS_` — package names, registry metadata, and each language's version convention (Go uses only the major from `TAG`; others the full `TAG`). + +**Client dependency versions float, so a third-party release can break the build with no change here.** That has happened: `axios` is pinned to an exact `1.18.1` because 1.19.0 broke the TypeScript client's build. See the comment above `CODEGEN_PARAMS_typescript-axios` for the cause and the revert condition. Nothing builds on a schedule, so the next PR is what discovers such breakage. + +### Release plumbing (`Makefile` + `scripts/release/`) + +- `make push` — clones each public `api-client-` repo into `client-clones/`, wipes it, copies the generated client in, commits, tags, pushes. Go is tagged `v$(TAG)`; every other target `$(TAG)` bare. Rehearse with `make push_dry_run` (real clone + commit, `git push --dry-run`) or `make push_test` (echoes git commands). +- The TypeScript target is named `typescript-axios`, so `push` clones `launchdarkly/api-client-typescript-axios` — that repo was renamed to `api-client-typescript` and GitHub redirects the old name. Both work; don't "fix" the apparent mismatch with `release.yml`'s `create-release` matrix. +- Non-Go client repos end up with **two tags per release**: bare `X.Y.Z` from `make push`, and `vX.Y.Z` created by `create-release`. Go gets only `vX.Y.Z`. +- `make publish` — pushes artifacts to PyPI, RubyGems, npm, and Maven Central. Go has no registry step; its **tag is the release**. +- `scripts/release/prepare.sh` writes registry credentials. Deliberately set up *after* build/test so build scripts cannot push. +- `push_dry_run` needs its `push` prerequisite; without it the target is only variable assignments, so `make` reports "Nothing to be done" and exits 0 — a rehearsal that silently does nothing. The `release-dry-run/*.sh` scripts also need `LD_RELEASE_ARTIFACTS_DIR` set or they fail on `mkdir`. + +## Releasing client libraries + +`release.yml` automates the release runbook end to end (the process and its rationale are documented internally). Dispatch it with **no inputs** — version, bump type, and release notes are derived, and each precondition is an enforced gate that aborts with a specific reason. + +All inputs are optional escape hatches: `releaseVersion` (bare semver), `bumpType`, `dryRun`, `force` (delete tags left by a failed attempt), `openDownstreamPr`, `mirrorPublic`, `skipCiGate`, `skipSpecParity`. + +### Job graph + +(`preflight`, `token-audit`) → `release` → `verify-published` → (`create-release`, downstream PR jobs, `mirror-public`) + +`scripts/release/preflight.sh` does the derivation and gating, and runs locally given a token with read access to the spec's source repo. + +- **Gate 1 — spec parity** (bypass with `skipSpecParity`). The spec's source repo commits the published spec, and production serves that file byte for byte, so comparing git blob SHAs settles whether production is caught up and nothing is mid-flight. Compared by blob SHA rather than downloading both copies, because the file is ~2.8 MB — over the contents API's 1 MB raw limit — while the tree API always returns a blob SHA. On a mismatch it names the commit production is serving and the one it is waiting for. The build ships production's bytes either way, so `skipSpecParity` changes only whether the run refuses to proceed — but version and notes still come from the source repo's tip, so the notes may then describe merged-but-undeployed changes. +- **Gate 2 — source repo CI green**, pinned to the commit parity resolved to. Uses check-runs only; the combined-status endpoint needs a permission the token isn't granted. +- **Gate 3 — no tag collisions** from a partial release, in any client repo, for either tag form. + +Everything read from the source repo is pinned to the one commit Gate 1 settled on. Its default branch moves often enough that reading the spec and the notes at separate moments can pair notes with a spec they don't describe. + +**Bump inference** comes from the `### ` headings of the source repo's unreleased changelog section: `Removed`/`Changed` → major, `Added`/`Deprecated` → minor, `Bug Fixes`/`Fixed`/`Security` → patch. `### Changed` is genuinely ambiguous, so the rule errs toward major; override with `bumpType`. An unrecognised heading aborts rather than guesses. + +### Post-release jobs + +- `verify-published` polls PyPI, RubyGems and npm as **hard** failures, plus the Go module proxy and Maven Central as **soft** warnings, then checks client repo tags. The Go proxy is soft on purpose: Go has no publish step, the tag is checked directly, and the proxy caches *negative* lookups — so a `go get` for a version before it exists (a rehearsal, say) makes it serve "unknown revision" for a while afterwards. That failed this job once on an otherwise complete release. It checks `v` on Go and a **bare ``** elsewhere, since that is all `make push` has created by then. +- Two downstream PR jobs bump the published client in the repos that consume it, each calling that repo's own `make update-api-client-go`. Keeping each codemod in the repo it edits means the logic lives beside the imports it rewrites. Neither auto-merges: a major bump moves the Go module path (`api-client-go/vN`), so it rewrites imports across dozens to hundreds of files, and most API releases are major. +- **Both downstream jobs are skipped on a rehearsal.** The pinned `pr-downstream` version has no `dryrun` input, so passing one was silently ignored and a rehearsal was attempting real PR creation. They also can't be meaningfully rehearsed: they bump *to* the version being released, which does not exist until it is published. +- **Reviewers are requested as a user, not a team.** A federated App installation token cannot request team reviewers. CODEOWNERS in each downstream repo still pulls in the owning team. +- `mirror-public` squash-mirrors onto the public `launchdarkly/ld-openapi`. **Aborts on conflict rather than resolving** — that push is not practically reversible, and the public repo has diverged. Failure here does not invalidate the release. + +### Credentials + +`BOT_TOKEN` needs push on all five `api-client-*` repos and on the public `ld-openapi`; the registry credentials need publish rights; `LD_API_KEY` needs write on the project the samples use. Anything touching the spec's source repo or the downstream repos uses a token **minted at runtime**, not a stored secret. + +Stored secrets are managed in internal infrastructure, not here. Where a parameter is created holding a placeholder and the real value written separately, there is a window where the secret exists but is the placeholder — the audit detects that and names it, rather than reporting an opaque authentication failure. + +`token-audit.yml` audits every credential read-only and is dispatchable on its own. `release.yml` runs it as a gate on **every** release: a bad registry credential would otherwise surface only after `make push` had tagged five client repos, which is what forces the delete-the-tags-and-retry dance. + +Two registry credentials cannot be positively verified and report `UNVERIFIED`, which is not a failure: PyPI has no token introspection, and a correctly-scoped RubyGems key (push-only) is rejected by the profile endpoint by design. + +**Every job that mints a federated token needs `permissions: id-token: write`.** Without it the exchange fails before it reaches the trust policy. For a reusable workflow it must be granted **twice** — on the calling job and at the called workflow's own level — because a reusable workflow is capped by what its caller holds. + +### Rehearsing + +`dryRun: true` runs the audit, preflight, and the full build, uses `push_dry_run`/`publish_dry_run`, and skips the downstream PR jobs. The artifacts a real run would have published are uploaded as `dry-run-artifacts`. + +**A rehearsal's log looks almost identical to a real release.** `push_dry_run` does real clones, commits and local tags, and `git push --dry-run` prints the same `* [new tag]` lines a real push does; `npm publish --dry-run` prints its usual notices. The distinguishing markers are `(dry-run)` in the npm output and "Simulating the updates we would do" from `make`. When in doubt, verify against the world — repo tags and registry HTTP status — rather than trusting the log's appearance. + +`push_dry_run` does authenticate and check write access, so it genuinely exercises `BOT_TOKEN`'s push rights. + +### Testing changes to these workflows + +`workflow_dispatch` only registers if the workflow file carrying that trigger exists on **`main`**. A new workflow, or a newly added trigger on an existing one, is therefore not dispatchable until merged. `release.yml` itself is dispatchable from any branch and GitHub runs the definition from the branch you select — **except** the federated-token steps, whose trust policy is restricted to `main`. Anything needing a minted token only works there. `ci.yml` runs on every PR regardless. + +### Troubleshooting + +- `Access Denied` fetching `openapi.json` — that endpoint fails intermittently; re-run. +- Publish failure to one registry — **published registry versions are immutable.** A partial publish cannot be re-run; it needs a new patch version. Before re-running anything, establish what actually landed: repo tags and each registry's HTTP status. `force: true` deletes tags from a prior attempt but cannot un-publish. +- Re-running a run: use **re-run failed jobs**, never re-run all, or the publish job repeats against registries that already hold the version. +- Gate 1 timing out — either the spec commit has not deployed, or spec changes keep landing. Waiting is the correct default; `skipSpecParity` is the override. +- A healthy run takes ~6 min through publish. Much longer means something is hanging. + +## Conventions + +- Never edit generated output under `targets/` to fix a problem; change the spec, the template override, or the `CODEGEN_PARAMS_` flags. +- This repo is kept in sync with the public `launchdarkly/ld-openapi`, so treat everything committed here as publishable. diff --git a/Makefile b/Makefile index aa7b417..316d738 100644 --- a/Makefile +++ b/Makefile @@ -80,15 +80,33 @@ CODEGEN_PARAMS_java = \ --additional-properties=gradleProperties=systemProp.org.gradle.internal.http.connectionTimeout=300000$$'\n'systemProp.org.gradle.internal.http.socketTimeout=300000$$'\n'org.gradle.jvmargs=-Xss2m \ --additional-properties=launchDarklyApiVersion=${LATEST_API_VERSION} CODEGEN_PARAMS_python = \ + -t $(TEMPLATES_PATH)/python \ --additional-properties=packageName=launchdarkly_api \ --additional-properties=packageVersion=$(TAG) \ --additional-properties=launchDarklyApiVersion=${LATEST_API_VERSION} +# TEMPORARY: axios is pinned to 1.18.1 rather than a floating ^1.13.1 range. +# axios 1.19.0 (2026-07-29) added `declare const axiosResponseDefault: unique symbol` +# to its index.d.ts and refers to it from the public return type of `request()`. +# The symbol is never exported, so tsc cannot name it when it infers the type of +# the generated common.ts `createRequestFunction`, and the client fails to build: +# common.ts(108,14): error TS2527: The inferred type of 'createRequestFunction' +# references an inaccessible 'unique symbol' type. A type annotation is necessary. +# Everything up to and including 1.18.1 builds. This is not fixed upstream: +# openapi-generator master still emits the unannotated function, and 1.19.0 is +# still axios latest. +# +# The real fix is a swagger-codegen-templates/typescript/common.mustache override +# annotating the returned closure — `): Promise =>` plus +# `as Promise` on the axios.request call — which compiles clean against 1.19.0 +# and against older axios. Deferred: it adds a fifth template to keep in sync with +# generator upgrades. Revert this pin when that override lands, or when axios +# exports the symbol. CODEGEN_PARAMS_typescript-axios = \ -t $(TEMPLATES_PATH)/typescript \ --additional-properties=npmName=launchdarkly-api-typescript \ --additional-properties=npmVersion=$(TAG) \ --additional-properties=supportsES6=true \ - --additional-properties=axiosVersion=^1.13.1 \ + --additional-properties=axiosVersion=1.18.1 \ --additional-properties=launchDarklyApiVersion=${LATEST_API_VERSION} CODEGEN_PARAMS_ruby = \ -t $(TEMPLATES_PATH)/ruby \ @@ -170,6 +188,10 @@ push_test: push push_dry_run: GIT_PUSH_COMMAND=git push --dry-run push_dry_run: GIT_PUSH_DESC=Simulating the updates we would do +# Without this prerequisite the target is only a set of variable assignments, so +# `make push_dry_run` prints "Nothing to be done" and exits 0 — a rehearsal that +# silently does nothing. push_test has always had it; this one was missing it. +push_dry_run: push # for each client library, clone the repository and replace the contents with the newly generated client push: mkdir $(CLIENT_CLONES_PATH); \ diff --git a/README.md b/README.md index 640ea19..3163a4a 100644 --- a/README.md +++ b/README.md @@ -8,12 +8,46 @@ The LaunchDarkly REST API is for custom integrations, data export, or automating Server/client code for the API can be automatically generated. To generate the code: - 1. Ensure that you have `curl` and `jq` installed. + 1. Ensure that you have `java`, `curl`, and `jq` installed. 1. The default make command will generate all target libraries: ``` > make ``` +Generated output lands under `targets/`, which is not committed. To generate a single language, name it as the target — for example `make go`. Valid targets are `go`, `java`, `python`, `ruby`, and `typescript-axios`. + +Only the HTTP-layer template is overridden per language, in `swagger-codegen-templates/`. Each override is a verbatim copy of the corresponding upstream generator template with LaunchDarkly additions fenced by `CUSTOM-START` / `CUSTOM-END` markers, so it must be re-copied and re-applied whenever `GENERATOR_VERSION` in the Makefile changes. + +## Verifying generated clients + +There is no unit test suite. Two checks stand in for one, both run by CI on every pull request: + + 1. `make BUILD_TARGETS="go java python ruby typescript-axios" build_clients` — confirms each generated client compiles and packages. + 1. The programs under `samples/` — each creates a feature flag against the live API, prints it, and deletes it, exercising the generated client end to end. They need an API key. + +`./scripts/release/selfcheck.sh` runs static checks over the release automation itself, in a couple of seconds and with no side effects. + ## How releases work -This project is set up to use [Github Actions](https://github.com/launchdarkly/ld-openapi-private/actions/workflows/release.yml) to release new versions. \ No newline at end of file +Releases run from the [`release.yml`](.github/workflows/release.yml) GitHub Actions workflow. Dispatch it with no inputs for a normal release: the version, the semver bump, and the release notes are all derived, and each precondition is an enforced gate that fails with a specific reason rather than releasing on a guess. + +A release, in order: + + 1. Audits every credential it will need, read-only, before doing any work. + 1. Checks that production is serving the spec the release will be built from, and that the spec's source is in a good state. + 1. Derives the next version from the most recent client tag and the bump implied by the pending changelog entries. + 1. Generates and builds all five clients. + 1. Pushes each client to its own public `launchdarkly/api-client-` repository, tags it, and publishes to PyPI, RubyGems, npm, and Maven Central. Go has no registry step — its git tag is the release. + 1. Confirms each package is actually retrievable from its registry before continuing. + 1. Creates a GitHub release on each client repository, and opens pull requests bumping the new client version in the repositories that consume it. + +All workflow inputs are optional overrides. The useful ones: + +| Input | Effect | +| --- | --- | +| `dryRun` | Rehearses everything without pushing or publishing anything | +| `releaseVersion` | Releases a specific version instead of the derived one | +| `bumpType` | Forces `major`, `minor`, or `patch` | +| `force` | Deletes tags left behind by a previous failed attempt | + +Published registry versions are immutable, so a release that fails partway through cannot simply be re-run — it needs a new patch version. If a run fails, establish what actually reached each registry before retrying, and re-run only the failed jobs so the publish step is not repeated. diff --git a/samples/javascript/package-lock.json b/samples/javascript/package-lock.json deleted file mode 100644 index f950ba0..0000000 --- a/samples/javascript/package-lock.json +++ /dev/null @@ -1,3875 +0,0 @@ -{ - "name": "launchdarkly-api-javascript-sample", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "launchdarkly-api-javascript-sample", - "version": "1.0.0", - "license": "Apache 2.0", - "dependencies": { - "launchdarkly-api": "file:../../targets/api-client-javascript" - } - }, - "../../targets/api-client-javascript": { - "name": "launchdarkly-api", - "version": "0.0.1-SNAPSHOT", - "license": "Apache 2.0", - "dependencies": { - "@babel/cli": "^7.0.0", - "superagent": "^5.3.0" - }, - "devDependencies": { - "@babel/core": "^7.0.0", - "@babel/plugin-proposal-class-properties": "^7.0.0", - "@babel/plugin-proposal-decorators": "^7.0.0", - "@babel/plugin-proposal-do-expressions": "^7.0.0", - "@babel/plugin-proposal-export-default-from": "^7.0.0", - "@babel/plugin-proposal-export-namespace-from": "^7.0.0", - "@babel/plugin-proposal-function-bind": "^7.0.0", - "@babel/plugin-proposal-function-sent": "^7.0.0", - "@babel/plugin-proposal-json-strings": "^7.0.0", - "@babel/plugin-proposal-logical-assignment-operators": "^7.0.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-proposal-numeric-separator": "^7.0.0", - "@babel/plugin-proposal-optional-chaining": "^7.0.0", - "@babel/plugin-proposal-pipeline-operator": "^7.0.0", - "@babel/plugin-proposal-throw-expressions": "^7.0.0", - "@babel/plugin-syntax-dynamic-import": "^7.0.0", - "@babel/plugin-syntax-import-meta": "^7.0.0", - "@babel/preset-env": "^7.0.0", - "@babel/register": "^7.0.0", - "expect.js": "^0.3.1", - "mocha": "^8.0.1", - "sinon": "^7.2.0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/cli": { - "version": "7.28.3", - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.28", - "commander": "^6.2.0", - "convert-source-map": "^2.0.0", - "fs-readdir-recursive": "^1.1.0", - "glob": "^7.2.0", - "make-dir": "^2.1.0", - "slash": "^2.0.0" - }, - "bin": { - "babel": "bin/babel.js", - "babel-external-helpers": "bin/babel-external-helpers.js" - }, - "engines": { - "node": ">=6.9.0" - }, - "optionalDependencies": { - "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.3", - "chokidar": "^3.6.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/code-frame": { - "version": "7.27.1", - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/compat-data": { - "version": "7.28.4", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/core": { - "version": "7.28.4", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.4", - "@babel/parser": "^7.28.4", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.4", - "@babel/types": "^7.28.4", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/generator": { - "version": "7.28.3", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.28.3", - "@babel/types": "^7.28.2", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/helper-annotate-as-pure": { - "version": "7.27.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.3" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.28.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-member-expression-to-functions": "^7.27.1", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.28.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "regexpu-core": "^6.2.0", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "debug": "^4.4.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.22.10" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/helper-globals": { - "version": "7.28.0", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/helper-module-transforms": { - "version": "7.28.3", - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.28.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/helper-optimise-call-expression": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-wrap-function": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/helper-replace-supers": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.27.1", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/helper-wrap-function": { - "version": "7.28.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.3", - "@babel/types": "^7.28.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/helpers": { - "version": "7.28.4", - "license": "MIT", - "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/parser": { - "version": "7.28.4", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.4" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.13.0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.28.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-proposal-class-properties": { - "version": "7.18.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-proposal-decorators": { - "version": "7.28.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-syntax-decorators": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-proposal-do-expressions": { - "version": "7.28.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-proposal-export-default-from": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-proposal-export-namespace-from": { - "version": "7.18.9", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-proposal-function-bind": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-proposal-function-sent": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-wrap-function": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-proposal-json-strings": { - "version": "7.18.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-json-strings": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.20.7", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.18.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-proposal-numeric-separator": { - "version": "7.18.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-proposal-optional-chaining": { - "version": "7.21.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-proposal-pipeline-operator": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-syntax-pipeline-operator": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-proposal-throw-expressions": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-syntax-decorators": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-syntax-pipeline-operator": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-syntax-unicode-sets-regex": { - "version": "7.18.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.28.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-remap-async-to-generator": "^7.27.1", - "@babel/traverse": "^7.28.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-remap-async-to-generator": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.28.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-class-properties": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.28.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.28.3", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-classes": { - "version": "7.28.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-globals": "^7.28.0", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", - "@babel/traverse": "^7.28.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/template": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-destructuring": { - "version": "7.28.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-explicit-resource-management": { - "version": "7.28.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.28.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-for-of": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-function-name": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-json-strings": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-literals": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-new-target": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.28.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.28.0", - "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/traverse": "^7.28.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-object-super": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-parameters": { - "version": "7.27.7", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-private-methods": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-property-literals": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-regenerator": { - "version": "7.28.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-regexp-modifiers": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-spread": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-template-literals": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/preset-env": { - "version": "7.28.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.28.0", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1", - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.3", - "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-import-assertions": "^7.27.1", - "@babel/plugin-syntax-import-attributes": "^7.27.1", - "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.27.1", - "@babel/plugin-transform-async-generator-functions": "^7.28.0", - "@babel/plugin-transform-async-to-generator": "^7.27.1", - "@babel/plugin-transform-block-scoped-functions": "^7.27.1", - "@babel/plugin-transform-block-scoping": "^7.28.0", - "@babel/plugin-transform-class-properties": "^7.27.1", - "@babel/plugin-transform-class-static-block": "^7.28.3", - "@babel/plugin-transform-classes": "^7.28.3", - "@babel/plugin-transform-computed-properties": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.28.0", - "@babel/plugin-transform-dotall-regex": "^7.27.1", - "@babel/plugin-transform-duplicate-keys": "^7.27.1", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", - "@babel/plugin-transform-dynamic-import": "^7.27.1", - "@babel/plugin-transform-explicit-resource-management": "^7.28.0", - "@babel/plugin-transform-exponentiation-operator": "^7.27.1", - "@babel/plugin-transform-export-namespace-from": "^7.27.1", - "@babel/plugin-transform-for-of": "^7.27.1", - "@babel/plugin-transform-function-name": "^7.27.1", - "@babel/plugin-transform-json-strings": "^7.27.1", - "@babel/plugin-transform-literals": "^7.27.1", - "@babel/plugin-transform-logical-assignment-operators": "^7.27.1", - "@babel/plugin-transform-member-expression-literals": "^7.27.1", - "@babel/plugin-transform-modules-amd": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.27.1", - "@babel/plugin-transform-modules-systemjs": "^7.27.1", - "@babel/plugin-transform-modules-umd": "^7.27.1", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", - "@babel/plugin-transform-new-target": "^7.27.1", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", - "@babel/plugin-transform-numeric-separator": "^7.27.1", - "@babel/plugin-transform-object-rest-spread": "^7.28.0", - "@babel/plugin-transform-object-super": "^7.27.1", - "@babel/plugin-transform-optional-catch-binding": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.27.1", - "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/plugin-transform-private-methods": "^7.27.1", - "@babel/plugin-transform-private-property-in-object": "^7.27.1", - "@babel/plugin-transform-property-literals": "^7.27.1", - "@babel/plugin-transform-regenerator": "^7.28.3", - "@babel/plugin-transform-regexp-modifiers": "^7.27.1", - "@babel/plugin-transform-reserved-words": "^7.27.1", - "@babel/plugin-transform-shorthand-properties": "^7.27.1", - "@babel/plugin-transform-spread": "^7.27.1", - "@babel/plugin-transform-sticky-regex": "^7.27.1", - "@babel/plugin-transform-template-literals": "^7.27.1", - "@babel/plugin-transform-typeof-symbol": "^7.27.1", - "@babel/plugin-transform-unicode-escapes": "^7.27.1", - "@babel/plugin-transform-unicode-property-regex": "^7.27.1", - "@babel/plugin-transform-unicode-regex": "^7.27.1", - "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", - "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.14", - "babel-plugin-polyfill-corejs3": "^0.13.0", - "babel-plugin-polyfill-regenerator": "^0.6.5", - "core-js-compat": "^3.43.0", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/preset-modules": { - "version": "0.1.6-no-external-plugins", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/register": { - "version": "7.28.3", - "dev": true, - "license": "MIT", - "dependencies": { - "clone-deep": "^4.0.1", - "find-cache-dir": "^2.0.0", - "make-dir": "^2.1.0", - "pirates": "^4.0.6", - "source-map-support": "^0.5.16" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/template": { - "version": "7.27.2", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/traverse": { - "version": "7.28.4", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.4", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "../../targets/api-client-javascript/node_modules/@babel/types": { - "version": "7.28.4", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "../../targets/api-client-javascript/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "../../targets/api-client-javascript/node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "../../targets/api-client-javascript/node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "../../targets/api-client-javascript/node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "license": "MIT" - }, - "../../targets/api-client-javascript/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "../../targets/api-client-javascript/node_modules/@nicolo-ribaudo/chokidar-2": { - "version": "2.1.8-no-fsevents.3", - "license": "MIT", - "optional": true - }, - "../../targets/api-client-javascript/node_modules/@sinonjs/commons": { - "version": "1.8.6", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "../../targets/api-client-javascript/node_modules/@sinonjs/formatio": { - "version": "3.2.2", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^1", - "@sinonjs/samsam": "^3.1.0" - } - }, - "../../targets/api-client-javascript/node_modules/@sinonjs/samsam": { - "version": "3.3.3", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^1.3.0", - "array-from": "^2.1.1", - "lodash": "^4.17.15" - } - }, - "../../targets/api-client-javascript/node_modules/@sinonjs/text-encoding": { - "version": "0.7.3", - "dev": true, - "license": "(Unlicense OR Apache-2.0)" - }, - "../../targets/api-client-javascript/node_modules/@ungap/promise-all-settled": { - "version": "1.1.2", - "dev": true, - "license": "ISC" - }, - "../../targets/api-client-javascript/node_modules/ansi-colors": { - "version": "4.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "../../targets/api-client-javascript/node_modules/ansi-regex": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "../../targets/api-client-javascript/node_modules/ansi-styles": { - "version": "4.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "../../targets/api-client-javascript/node_modules/anymatch": { - "version": "3.1.3", - "devOptional": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "../../targets/api-client-javascript/node_modules/argparse": { - "version": "2.0.1", - "dev": true, - "license": "Python-2.0" - }, - "../../targets/api-client-javascript/node_modules/array-from": { - "version": "2.1.1", - "dev": true, - "license": "MIT" - }, - "../../targets/api-client-javascript/node_modules/asynckit": { - "version": "0.4.0", - "license": "MIT" - }, - "../../targets/api-client-javascript/node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.14", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.27.7", - "@babel/helper-define-polyfill-provider": "^0.6.5", - "semver": "^6.3.1" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "../../targets/api-client-javascript/node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.13.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.5", - "core-js-compat": "^3.43.0" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "../../targets/api-client-javascript/node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.5" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "../../targets/api-client-javascript/node_modules/balanced-match": { - "version": "1.0.2", - "license": "MIT" - }, - "../../targets/api-client-javascript/node_modules/baseline-browser-mapping": { - "version": "2.8.14", - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.js" - } - }, - "../../targets/api-client-javascript/node_modules/binary-extensions": { - "version": "2.3.0", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../../targets/api-client-javascript/node_modules/brace-expansion": { - "version": "1.1.12", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "../../targets/api-client-javascript/node_modules/braces": { - "version": "3.0.3", - "devOptional": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "../../targets/api-client-javascript/node_modules/browser-stdout": { - "version": "1.3.1", - "dev": true, - "license": "ISC" - }, - "../../targets/api-client-javascript/node_modules/browserslist": { - "version": "4.26.3", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.8.9", - "caniuse-lite": "^1.0.30001746", - "electron-to-chromium": "^1.5.227", - "node-releases": "^2.0.21", - "update-browserslist-db": "^1.1.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "../../targets/api-client-javascript/node_modules/buffer-from": { - "version": "1.1.2", - "dev": true, - "license": "MIT" - }, - "../../targets/api-client-javascript/node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "../../targets/api-client-javascript/node_modules/call-bound": { - "version": "1.0.4", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../../targets/api-client-javascript/node_modules/camelcase": { - "version": "6.3.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../../targets/api-client-javascript/node_modules/caniuse-lite": { - "version": "1.0.30001749", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "../../targets/api-client-javascript/node_modules/chalk": { - "version": "4.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "../../targets/api-client-javascript/node_modules/chalk/node_modules/supports-color": { - "version": "7.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../../targets/api-client-javascript/node_modules/chokidar": { - "version": "3.6.0", - "license": "MIT", - "optional": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "../../targets/api-client-javascript/node_modules/cliui": { - "version": "7.0.4", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "../../targets/api-client-javascript/node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../../targets/api-client-javascript/node_modules/cliui/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../../targets/api-client-javascript/node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "../../targets/api-client-javascript/node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "../../targets/api-client-javascript/node_modules/clone-deep": { - "version": "4.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "../../targets/api-client-javascript/node_modules/color-convert": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "../../targets/api-client-javascript/node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "license": "MIT" - }, - "../../targets/api-client-javascript/node_modules/combined-stream": { - "version": "1.0.8", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "../../targets/api-client-javascript/node_modules/commander": { - "version": "6.2.1", - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "../../targets/api-client-javascript/node_modules/commondir": { - "version": "1.0.1", - "dev": true, - "license": "MIT" - }, - "../../targets/api-client-javascript/node_modules/component-emitter": { - "version": "1.3.1", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../../targets/api-client-javascript/node_modules/concat-map": { - "version": "0.0.1", - "license": "MIT" - }, - "../../targets/api-client-javascript/node_modules/convert-source-map": { - "version": "2.0.0", - "license": "MIT" - }, - "../../targets/api-client-javascript/node_modules/cookiejar": { - "version": "2.1.4", - "license": "MIT" - }, - "../../targets/api-client-javascript/node_modules/core-js-compat": { - "version": "3.45.1", - "dev": true, - "license": "MIT", - "dependencies": { - "browserslist": "^4.25.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "../../targets/api-client-javascript/node_modules/debug": { - "version": "4.4.3", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "../../targets/api-client-javascript/node_modules/decamelize": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../../targets/api-client-javascript/node_modules/delayed-stream": { - "version": "1.0.0", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "../../targets/api-client-javascript/node_modules/diff": { - "version": "5.0.0", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "../../targets/api-client-javascript/node_modules/dunder-proto": { - "version": "1.0.1", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "../../targets/api-client-javascript/node_modules/electron-to-chromium": { - "version": "1.5.233", - "license": "ISC" - }, - "../../targets/api-client-javascript/node_modules/emoji-regex": { - "version": "8.0.0", - "dev": true, - "license": "MIT" - }, - "../../targets/api-client-javascript/node_modules/es-define-property": { - "version": "1.0.1", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "../../targets/api-client-javascript/node_modules/es-errors": { - "version": "1.3.0", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "../../targets/api-client-javascript/node_modules/es-object-atoms": { - "version": "1.1.1", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "../../targets/api-client-javascript/node_modules/es-set-tostringtag": { - "version": "2.1.0", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "../../targets/api-client-javascript/node_modules/escalade": { - "version": "3.2.0", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "../../targets/api-client-javascript/node_modules/escape-string-regexp": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../../targets/api-client-javascript/node_modules/esutils": { - "version": "2.0.3", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "../../targets/api-client-javascript/node_modules/expect.js": { - "version": "0.3.1", - "dev": true - }, - "../../targets/api-client-javascript/node_modules/fast-safe-stringify": { - "version": "2.1.1", - "license": "MIT" - }, - "../../targets/api-client-javascript/node_modules/fill-range": { - "version": "7.1.1", - "devOptional": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "../../targets/api-client-javascript/node_modules/find-cache-dir": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "../../targets/api-client-javascript/node_modules/find-up": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../../targets/api-client-javascript/node_modules/flat": { - "version": "5.0.2", - "dev": true, - "license": "BSD-3-Clause", - "bin": { - "flat": "cli.js" - } - }, - "../../targets/api-client-javascript/node_modules/form-data": { - "version": "3.0.4", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.35" - }, - "engines": { - "node": ">= 6" - } - }, - "../../targets/api-client-javascript/node_modules/formidable": { - "version": "1.2.6", - "license": "MIT", - "funding": { - "url": "https://ko-fi.com/tunnckoCore/commissions" - } - }, - "../../targets/api-client-javascript/node_modules/fs-readdir-recursive": { - "version": "1.1.0", - "license": "MIT" - }, - "../../targets/api-client-javascript/node_modules/fs.realpath": { - "version": "1.0.0", - "license": "ISC" - }, - "../../targets/api-client-javascript/node_modules/function-bind": { - "version": "1.1.2", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../../targets/api-client-javascript/node_modules/gensync": { - "version": "1.0.0-beta.2", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "../../targets/api-client-javascript/node_modules/get-caller-file": { - "version": "2.0.5", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "../../targets/api-client-javascript/node_modules/get-intrinsic": { - "version": "1.3.0", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../../targets/api-client-javascript/node_modules/get-proto": { - "version": "1.0.1", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "../../targets/api-client-javascript/node_modules/glob": { - "version": "7.2.3", - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "../../targets/api-client-javascript/node_modules/glob-parent": { - "version": "5.1.2", - "devOptional": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "../../targets/api-client-javascript/node_modules/gopd": { - "version": "1.2.0", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../../targets/api-client-javascript/node_modules/growl": { - "version": "1.10.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4.x" - } - }, - "../../targets/api-client-javascript/node_modules/has-flag": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../../targets/api-client-javascript/node_modules/has-symbols": { - "version": "1.1.0", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../../targets/api-client-javascript/node_modules/has-tostringtag": { - "version": "1.0.2", - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../../targets/api-client-javascript/node_modules/hasown": { - "version": "2.0.2", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "../../targets/api-client-javascript/node_modules/he": { - "version": "1.2.0", - "dev": true, - "license": "MIT", - "bin": { - "he": "bin/he" - } - }, - "../../targets/api-client-javascript/node_modules/inflight": { - "version": "1.0.6", - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "../../targets/api-client-javascript/node_modules/inherits": { - "version": "2.0.4", - "license": "ISC" - }, - "../../targets/api-client-javascript/node_modules/is-binary-path": { - "version": "2.1.0", - "devOptional": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "../../targets/api-client-javascript/node_modules/is-core-module": { - "version": "2.16.1", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../../targets/api-client-javascript/node_modules/is-extglob": { - "version": "2.1.1", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "../../targets/api-client-javascript/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "../../targets/api-client-javascript/node_modules/is-glob": { - "version": "4.0.3", - "devOptional": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "../../targets/api-client-javascript/node_modules/is-number": { - "version": "7.0.0", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "../../targets/api-client-javascript/node_modules/is-plain-obj": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../../targets/api-client-javascript/node_modules/is-plain-object": { - "version": "2.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "../../targets/api-client-javascript/node_modules/isarray": { - "version": "0.0.1", - "dev": true, - "license": "MIT" - }, - "../../targets/api-client-javascript/node_modules/isexe": { - "version": "2.0.0", - "dev": true, - "license": "ISC" - }, - "../../targets/api-client-javascript/node_modules/isobject": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "../../targets/api-client-javascript/node_modules/js-tokens": { - "version": "4.0.0", - "license": "MIT" - }, - "../../targets/api-client-javascript/node_modules/js-yaml": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "../../targets/api-client-javascript/node_modules/jsesc": { - "version": "3.1.0", - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "../../targets/api-client-javascript/node_modules/json5": { - "version": "2.2.3", - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "../../targets/api-client-javascript/node_modules/just-extend": { - "version": "4.2.1", - "dev": true, - "license": "MIT" - }, - "../../targets/api-client-javascript/node_modules/kind-of": { - "version": "6.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "../../targets/api-client-javascript/node_modules/locate-path": { - "version": "6.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../../targets/api-client-javascript/node_modules/lodash": { - "version": "4.17.21", - "dev": true, - "license": "MIT" - }, - "../../targets/api-client-javascript/node_modules/lodash.debounce": { - "version": "4.0.8", - "dev": true, - "license": "MIT" - }, - "../../targets/api-client-javascript/node_modules/log-symbols": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "../../targets/api-client-javascript/node_modules/lolex": { - "version": "4.2.0", - "dev": true, - "license": "BSD-3-Clause" - }, - "../../targets/api-client-javascript/node_modules/lru-cache": { - "version": "5.1.1", - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "../../targets/api-client-javascript/node_modules/make-dir": { - "version": "2.1.0", - "license": "MIT", - "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "engines": { - "node": ">=6" - } - }, - "../../targets/api-client-javascript/node_modules/make-dir/node_modules/semver": { - "version": "5.7.2", - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "../../targets/api-client-javascript/node_modules/math-intrinsics": { - "version": "1.1.0", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "../../targets/api-client-javascript/node_modules/methods": { - "version": "1.1.2", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "../../targets/api-client-javascript/node_modules/mime": { - "version": "2.6.0", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "../../targets/api-client-javascript/node_modules/mime-db": { - "version": "1.52.0", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "../../targets/api-client-javascript/node_modules/mime-types": { - "version": "2.1.35", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "../../targets/api-client-javascript/node_modules/minimatch": { - "version": "3.1.2", - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "../../targets/api-client-javascript/node_modules/mocha": { - "version": "8.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@ungap/promise-all-settled": "1.1.2", - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.1", - "debug": "4.3.1", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.1.6", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "4.0.0", - "log-symbols": "4.0.0", - "minimatch": "3.0.4", - "ms": "2.1.3", - "nanoid": "3.1.20", - "serialize-javascript": "5.0.1", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "which": "2.0.2", - "wide-align": "1.1.3", - "workerpool": "6.1.0", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha" - }, - "engines": { - "node": ">= 10.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" - } - }, - "../../targets/api-client-javascript/node_modules/mocha/node_modules/chokidar": { - "version": "3.5.1", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.1" - } - }, - "../../targets/api-client-javascript/node_modules/mocha/node_modules/debug": { - "version": "4.3.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "../../targets/api-client-javascript/node_modules/mocha/node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "dev": true, - "license": "MIT" - }, - "../../targets/api-client-javascript/node_modules/mocha/node_modules/glob": { - "version": "7.1.6", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "../../targets/api-client-javascript/node_modules/mocha/node_modules/minimatch": { - "version": "3.0.4", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "../../targets/api-client-javascript/node_modules/mocha/node_modules/readdirp": { - "version": "3.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "../../targets/api-client-javascript/node_modules/ms": { - "version": "2.1.3", - "license": "MIT" - }, - "../../targets/api-client-javascript/node_modules/nanoid": { - "version": "3.1.20", - "dev": true, - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "../../targets/api-client-javascript/node_modules/nise": { - "version": "1.5.3", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/formatio": "^3.2.1", - "@sinonjs/text-encoding": "^0.7.1", - "just-extend": "^4.0.2", - "lolex": "^5.0.1", - "path-to-regexp": "^1.7.0" - } - }, - "../../targets/api-client-javascript/node_modules/nise/node_modules/lolex": { - "version": "5.1.2", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^1.7.0" - } - }, - "../../targets/api-client-javascript/node_modules/node-releases": { - "version": "2.0.23", - "license": "MIT" - }, - "../../targets/api-client-javascript/node_modules/normalize-path": { - "version": "3.0.0", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "../../targets/api-client-javascript/node_modules/object-inspect": { - "version": "1.13.4", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../../targets/api-client-javascript/node_modules/once": { - "version": "1.4.0", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "../../targets/api-client-javascript/node_modules/p-limit": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../../targets/api-client-javascript/node_modules/p-locate": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../../targets/api-client-javascript/node_modules/p-try": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "../../targets/api-client-javascript/node_modules/path-exists": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../../targets/api-client-javascript/node_modules/path-is-absolute": { - "version": "1.0.1", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "../../targets/api-client-javascript/node_modules/path-parse": { - "version": "1.0.7", - "dev": true, - "license": "MIT" - }, - "../../targets/api-client-javascript/node_modules/path-to-regexp": { - "version": "1.9.0", - "dev": true, - "license": "MIT", - "dependencies": { - "isarray": "0.0.1" - } - }, - "../../targets/api-client-javascript/node_modules/picocolors": { - "version": "1.1.1", - "license": "ISC" - }, - "../../targets/api-client-javascript/node_modules/picomatch": { - "version": "2.3.1", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "../../targets/api-client-javascript/node_modules/pify": { - "version": "4.0.1", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "../../targets/api-client-javascript/node_modules/pirates": { - "version": "4.0.7", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "../../targets/api-client-javascript/node_modules/pkg-dir": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "../../targets/api-client-javascript/node_modules/pkg-dir/node_modules/find-up": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "../../targets/api-client-javascript/node_modules/pkg-dir/node_modules/locate-path": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "../../targets/api-client-javascript/node_modules/pkg-dir/node_modules/p-limit": { - "version": "2.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../../targets/api-client-javascript/node_modules/pkg-dir/node_modules/p-locate": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "../../targets/api-client-javascript/node_modules/pkg-dir/node_modules/path-exists": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "../../targets/api-client-javascript/node_modules/qs": { - "version": "6.14.0", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../../targets/api-client-javascript/node_modules/randombytes": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "../../targets/api-client-javascript/node_modules/readable-stream": { - "version": "3.6.2", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "../../targets/api-client-javascript/node_modules/readdirp": { - "version": "3.6.0", - "license": "MIT", - "optional": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "../../targets/api-client-javascript/node_modules/regenerate": { - "version": "1.4.2", - "dev": true, - "license": "MIT" - }, - "../../targets/api-client-javascript/node_modules/regenerate-unicode-properties": { - "version": "10.2.2", - "dev": true, - "license": "MIT", - "dependencies": { - "regenerate": "^1.4.2" - }, - "engines": { - "node": ">=4" - } - }, - "../../targets/api-client-javascript/node_modules/regexpu-core": { - "version": "6.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.2.2", - "regjsgen": "^0.8.0", - "regjsparser": "^0.13.0", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.2.1" - }, - "engines": { - "node": ">=4" - } - }, - "../../targets/api-client-javascript/node_modules/regjsgen": { - "version": "0.8.0", - "dev": true, - "license": "MIT" - }, - "../../targets/api-client-javascript/node_modules/regjsparser": { - "version": "0.13.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "jsesc": "~3.1.0" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "../../targets/api-client-javascript/node_modules/require-directory": { - "version": "2.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "../../targets/api-client-javascript/node_modules/resolve": { - "version": "1.22.10", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../../targets/api-client-javascript/node_modules/safe-buffer": { - "version": "5.2.1", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "../../targets/api-client-javascript/node_modules/semver": { - "version": "6.3.1", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "../../targets/api-client-javascript/node_modules/serialize-javascript": { - "version": "5.0.1", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "../../targets/api-client-javascript/node_modules/shallow-clone": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "../../targets/api-client-javascript/node_modules/side-channel": { - "version": "1.1.0", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../../targets/api-client-javascript/node_modules/side-channel-list": { - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../../targets/api-client-javascript/node_modules/side-channel-map": { - "version": "1.0.1", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../../targets/api-client-javascript/node_modules/side-channel-weakmap": { - "version": "1.0.2", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../../targets/api-client-javascript/node_modules/sinon": { - "version": "7.5.0", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^1.4.0", - "@sinonjs/formatio": "^3.2.1", - "@sinonjs/samsam": "^3.3.3", - "diff": "^3.5.0", - "lolex": "^4.2.0", - "nise": "^1.5.2", - "supports-color": "^5.5.0" - } - }, - "../../targets/api-client-javascript/node_modules/sinon/node_modules/diff": { - "version": "3.5.0", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "../../targets/api-client-javascript/node_modules/sinon/node_modules/has-flag": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "../../targets/api-client-javascript/node_modules/sinon/node_modules/supports-color": { - "version": "5.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "../../targets/api-client-javascript/node_modules/slash": { - "version": "2.0.0", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "../../targets/api-client-javascript/node_modules/source-map": { - "version": "0.6.1", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "../../targets/api-client-javascript/node_modules/source-map-support": { - "version": "0.5.21", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "../../targets/api-client-javascript/node_modules/string_decoder": { - "version": "1.3.0", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "../../targets/api-client-javascript/node_modules/string-width": { - "version": "2.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "../../targets/api-client-javascript/node_modules/strip-ansi": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "../../targets/api-client-javascript/node_modules/strip-json-comments": { - "version": "3.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "../../targets/api-client-javascript/node_modules/superagent": { - "version": "5.3.1", - "license": "MIT", - "dependencies": { - "component-emitter": "^1.3.0", - "cookiejar": "^2.1.2", - "debug": "^4.1.1", - "fast-safe-stringify": "^2.0.7", - "form-data": "^3.0.0", - "formidable": "^1.2.2", - "methods": "^1.1.2", - "mime": "^2.4.6", - "qs": "^6.9.4", - "readable-stream": "^3.6.0", - "semver": "^7.3.2" - }, - "engines": { - "node": ">= 7.0.0" - } - }, - "../../targets/api-client-javascript/node_modules/superagent/node_modules/semver": { - "version": "7.7.3", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "../../targets/api-client-javascript/node_modules/supports-color": { - "version": "8.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "../../targets/api-client-javascript/node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "../../targets/api-client-javascript/node_modules/to-regex-range": { - "version": "5.0.1", - "devOptional": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "../../targets/api-client-javascript/node_modules/type-detect": { - "version": "4.0.8", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "../../targets/api-client-javascript/node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "../../targets/api-client-javascript/node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "../../targets/api-client-javascript/node_modules/unicode-match-property-value-ecmascript": { - "version": "2.2.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "../../targets/api-client-javascript/node_modules/unicode-property-aliases-ecmascript": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "../../targets/api-client-javascript/node_modules/update-browserslist-db": { - "version": "1.1.3", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "../../targets/api-client-javascript/node_modules/util-deprecate": { - "version": "1.0.2", - "license": "MIT" - }, - "../../targets/api-client-javascript/node_modules/which": { - "version": "2.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "../../targets/api-client-javascript/node_modules/wide-align": { - "version": "1.1.3", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^1.0.2 || 2" - } - }, - "../../targets/api-client-javascript/node_modules/workerpool": { - "version": "6.1.0", - "dev": true, - "license": "Apache-2.0" - }, - "../../targets/api-client-javascript/node_modules/wrap-ansi": { - "version": "7.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "../../targets/api-client-javascript/node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "5.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../../targets/api-client-javascript/node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../../targets/api-client-javascript/node_modules/wrap-ansi/node_modules/string-width": { - "version": "4.2.3", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "../../targets/api-client-javascript/node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "../../targets/api-client-javascript/node_modules/wrappy": { - "version": "1.0.2", - "license": "ISC" - }, - "../../targets/api-client-javascript/node_modules/y18n": { - "version": "5.0.8", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "../../targets/api-client-javascript/node_modules/yallist": { - "version": "3.1.1", - "license": "ISC" - }, - "../../targets/api-client-javascript/node_modules/yargs": { - "version": "16.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "../../targets/api-client-javascript/node_modules/yargs-parser": { - "version": "20.2.4", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "../../targets/api-client-javascript/node_modules/yargs-unparser": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "../../targets/api-client-javascript/node_modules/yargs/node_modules/ansi-regex": { - "version": "5.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../../targets/api-client-javascript/node_modules/yargs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "../../targets/api-client-javascript/node_modules/yargs/node_modules/string-width": { - "version": "4.2.3", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "../../targets/api-client-javascript/node_modules/yargs/node_modules/strip-ansi": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "../../targets/api-client-javascript/node_modules/yocto-queue": { - "version": "0.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/launchdarkly-api": { - "resolved": "../../targets/api-client-javascript", - "link": true - } - } -} diff --git a/samples/javascript/package.json b/samples/javascript/package.json deleted file mode 100644 index 949d9de..0000000 --- a/samples/javascript/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "launchdarkly-api-javascript-sample", - "version": "1.0.0", - "description": "Sample app for launchdarkly-api", - "main": "index.js", - "scripts": { - "start": "node index.js" - }, - "license": "Apache 2.0", - "dependencies": { - "launchdarkly-api": "file:../../targets/api-client-javascript" - } -} diff --git a/scripts/release/gonfalon-update.sh b/scripts/release/gonfalon-update.sh new file mode 100755 index 0000000..c80ddc9 --- /dev/null +++ b/scripts/release/gonfalon-update.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash +# +# Runbook steps 10 and 12, as a single downstream change: +# 10. rename gonfalon's [Unreleased] changelog heading to the released version +# and open a fresh empty [Unreleased] above it +# 12. bump the api-client-go version gonfalon depends on +# +# Runs from the root of a gonfalon checkout — pr-downstream clones the downstream +# repo into a subdirectory and runs update-command there, so this script is +# invoked by absolute path out of the ld-openapi-private workspace. +# +# Usage: gonfalon-update.sh VERSION [RELEASE_DATE] +# +# The dependency bump itself lives in gonfalon as `make update-api-client-go`, +# beside the imports it rewrites — a major bump moves the module path +# (api-client-go/vN) and has to move Go sources, BUILD.bazel labels, and +# MODULE.bazel together. This script only drives it and handles the changelog. + +set -euo pipefail + +version=${1:?usage: gonfalon-update.sh VERSION [RELEASE_DATE]} +release_date=${2:-$(date -u +%Y-%m-%d)} + +CHANGELOG=apidocs/CHANGELOG.md + +fail() { echo "gonfalon-update FAILED: $*" >&2; exit 1; } +note() { echo "==> $*"; } + +[ -f go.mod ] || fail "no go.mod here — expected to run from the gonfalon repo root (pwd=$(pwd))" +[ -f "${CHANGELOG}" ] || fail "${CHANGELOG} not found" +grep -q '^update-api-client-go:' Makefile || \ + fail "gonfalon has no 'update-api-client-go' make target — it may have been renamed" + +# --- Step 10: changelog ------------------------------------------------------ +note "Renaming [Unreleased] to [${version}] - ${release_date} in ${CHANGELOG}" +grep -qx '## \[Unreleased\]' "${CHANGELOG}" || \ + fail "no '## [Unreleased]' heading in ${CHANGELOG}; it may have been renamed already" + +# Without this, a retried release re-renames the freshly created [Unreleased] +# heading and leaves two [VERSION] sections behind. +grep -qE "^## \[${version}\]" "${CHANGELOG}" && \ + fail "${CHANGELOG} already has a '## [${version}]' section — this release was already recorded in gonfalon" + +awk -v version="${version}" -v date="${release_date}" ' + !done && /^## \[Unreleased\]$/ { + print "## [Unreleased]" + print "" + print "## [" version "] - " date + done = 1 + next + } + { print } +' "${CHANGELOG}" > "${CHANGELOG}.tmp" +mv "${CHANGELOG}.tmp" "${CHANGELOG}" + +# --- Step 12: api-client-go --------------------------------------------------- +note "make update-api-client-go API_CLIENT_GO_VERSION=${version}" +make update-api-client-go "API_CLIENT_GO_VERSION=${version}" + +# Guard against a silent no-op: if nothing changed, the PR would be empty and the +# release would look complete while gonfalon still points at the old client. +if git diff --quiet; then + fail "no changes produced — the changelog rewrite and dependency bump both no-opped" +fi + +note "Done. Changed files:" +git diff --name-only | sed 's/^/ /' diff --git a/scripts/release/mirror-public.sh b/scripts/release/mirror-public.sh new file mode 100755 index 0000000..5ae5d53 --- /dev/null +++ b/scripts/release/mirror-public.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash +# +# Runbook step 11: squash-mirror this private repo onto the public launchdarkly/ld-openapi. +# +# Usage: mirror-public.sh VERSION +# +# Env: +# BOT_TOKEN the releaser token, used for both repos (required) +# DRY_RUN true = `git push --dry-run` +# +# One token covers both sides: public ld-openapi and this repo both grant +# "sa-release-bots" = "Releaser" in terraform, the same grant the api-client-* +# repos use for the pushes `make push` already does every release. +# +# This makes private-repo content public and is not practically reversible, so it +# deliberately does NOT resolve conflicts: the runbook's guidance is "generally +# I'd choose the private repo one, but use your discretion", which is a human +# decision. On conflict this aborts and leaves the release otherwise complete. + +set -euo pipefail + +version=${1:?usage: mirror-public.sh VERSION} +: "${BOT_TOKEN:?BOT_TOKEN is required}" + +PUBLIC_REPO=launchdarkly/ld-openapi +PRIVATE_REPO=launchdarkly/ld-openapi-private + +workdir=$(mktemp -d) +trap 'rm -rf "${workdir}"' EXIT + +note() { echo "==> $*"; } +fail() { echo "MIRROR FAILED: $*" >&2; exit 1; } + +note "Cloning ${PUBLIC_REPO}" +git clone --quiet "https://x-access-token:${BOT_TOKEN}@github.com/${PUBLIC_REPO}.git" "${workdir}/public" +cd "${workdir}/public" + +git config user.name LaunchDarklyReleaseBot +git config user.email launchdarklyreleasebot@launchdarkly.com + +git remote add private "https://x-access-token:${BOT_TOKEN}@github.com/${PRIVATE_REPO}.git" +git fetch --quiet private main + +if git diff --quiet HEAD private/main; then + note "Public repo already matches private main — nothing to mirror." + exit 0 +fi + +note "Files that will change on ${PUBLIC_REPO}" +git diff --name-status HEAD private/main | sed 's/^/ /' + +if ! git merge --squash private/main; then + conflicts=$(git diff --name-only --diff-filter=U || true) + fail "merge conflicts while squashing private/main: +$(sed 's/^/ /' <<<"${conflicts}") + Resolve by hand per the runbook and push to ${PUBLIC_REPO} yourself. + The release itself already completed; only the public mirror is pending." +fi + +if git diff --cached --quiet; then + note "Squash produced no staged changes — nothing to commit." + exit 0 +fi + +git commit --quiet -m "Sync from ${PRIVATE_REPO} for release ${version}" + +if [ "${DRY_RUN:-false}" = "true" ]; then + note "DRY_RUN: would push to ${PUBLIC_REPO} main" + git push --dry-run origin main +else + note "Pushing to ${PUBLIC_REPO} main" + git push origin main +fi + +if [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then + { + echo "### Public mirror" + echo + echo "Squashed \`${PRIVATE_REPO}@main\` onto [\`${PUBLIC_REPO}\`](https://github.com/${PUBLIC_REPO}) for release ${version}." + if [ "${DRY_RUN:-false}" = "true" ]; then + echo + echo "_Dry run — nothing was pushed._" + fi + } >> "${GITHUB_STEP_SUMMARY}" +fi diff --git a/scripts/release/preflight.sh b/scripts/release/preflight.sh new file mode 100755 index 0000000..8f94a90 --- /dev/null +++ b/scripts/release/preflight.sh @@ -0,0 +1,325 @@ +#!/usr/bin/env bash +# +# Derives every input the release needs and enforces the preflight gates from the +# release runbook. +# +# Requires: gh, curl, git. +# +# Two GitHub tokens, because they answer to different repos: +# GH_TOKEN the releaser token (BOT_TOKEN) — client repos only, used for +# the tag-collision check and, with FORCE, tag deletes. +# GONFALON_TOKEN read on launchdarkly/gonfalon, for the spec-parity gate, the +# CI gate, and the changelog. In CI this is an OctoSTS token +# minted at runtime from the ld-openapi-downstream trust policy. +# The releaser token deliberately has no gonfalon access and +# should not be granted any just to read a file. +# +# Env: +# VERSION_OVERRIDE skip version derivation and use this bare semver +# BUMP_OVERRIDE major|minor|patch — skip changelog inference +# FORCE true = delete colliding tags instead of aborting +# PARITY_TIMEOUT seconds to wait for prod to catch up to gonfalon main (default 1800) +# PARITY_INTERVAL seconds between parity polls (default 60) +# SKIP_CI_GATE true = don't require gonfalon main to be green +# SKIP_SPEC_PARITY true = release what prod serves even if main is ahead +# +# Writes version/bump/changelog to $GITHUB_OUTPUT when running under Actions, +# and always prints a human-readable summary. + +set -euo pipefail + +GONFALON=launchdarkly/gonfalon +SPEC_PATH=apidocs/openapi-public-final.json +CHANGELOG_PATH=apidocs/CHANGELOG.md +SPEC_URL=https://app.launchdarkly.com/api/v2/openapi.json +GO_CLIENT_REPO=launchdarkly/api-client-go + +# Canonical repo names. `make push` clones api-client-typescript-axios, which is a +# GitHub redirect to api-client-typescript; use the canonical name for API calls. +CLIENT_REPOS=( + launchdarkly/api-client-go + launchdarkly/api-client-java + launchdarkly/api-client-python + launchdarkly/api-client-ruby + launchdarkly/api-client-typescript +) + +PARITY_TIMEOUT=${PARITY_TIMEOUT:-1800} +PARITY_INTERVAL=${PARITY_INTERVAL:-60} +workdir=$(mktemp -d) +trap 'rm -rf "${workdir}"' EXIT + +fail() { echo "PREFLIGHT FAILED: $*" >&2; exit 1; } +note() { echo "==> $*"; } + +# Every gonfalon read goes through this, so the releaser token is never used +# against a repo it has no business reading. +GONFALON_TOKEN=${GONFALON_TOKEN:-} +[ -n "${GONFALON_TOKEN}" ] || \ + fail "GONFALON_TOKEN must be set — it is what reads ${GONFALON}. In CI it comes from the octosts-action step; locally, pass a token with read access. BOT_TOKEN has no access there." +gh_gonfalon() { GH_TOKEN="${GONFALON_TOKEN}" gh "$@"; } + +# --- Gate 1: prod serves exactly what gonfalon main has committed ------------- +# This replaces both the Slack "nothing mid-flight" check and the "wait for the +# commit to be deployed to production" wait. gonfalon commits the published spec, +# and prod serves that file byte for byte, so a git blob SHA comparison settles it: +# equal -> prod is caught up AND nothing is mid-flight; safe to release. +# unequal -> either a merged spec change isn't deployed yet, or one just landed. +# +# Compared via `git hash-object` rather than downloading gonfalon's copy: the file +# is ~2.8MB, over the contents API's 1MB raw limit, but the tree API always +# returns its blob SHA. +# +# main is re-resolved on every poll so we converge on wherever main currently is, +# then frozen: every later read (CI status, changelog) uses that one commit. Doing +# otherwise lets a merge land mid-preflight and pair a changelog with a spec it +# does not describe — gonfalon's main moves often enough for that to be real. +note "Gate 1: comparing prod spec against ${GONFALON} main:${SPEC_PATH}" +spec_dir=$(dirname "${SPEC_PATH}") +spec_file=$(basename "${SPEC_PATH}") +deadline=$(( $(date +%s) + PARITY_TIMEOUT )) +while :; do + gonfalon_sha=$(gh_gonfalon api "repos/${GONFALON}/commits/main" --jq '.sha') + [ -n "${gonfalon_sha}" ] || fail "could not resolve ${GONFALON} main" + + curl -s -L --fail "${SPEC_URL}" -o "${workdir}/prod-openapi.json" || \ + fail "could not download ${SPEC_URL} (this endpoint fails intermittently — retry the run)" + + prod_blob=$(git hash-object "${workdir}/prod-openapi.json") + main_blob=$(gh_gonfalon api "repos/${GONFALON}/git/trees/${gonfalon_sha}:${spec_dir}" \ + --jq ".tree[] | select(.path == \"${spec_file}\") | .sha") + + [ -n "${main_blob}" ] || fail "could not read blob SHA for ${SPEC_PATH} at ${GONFALON}@${gonfalon_sha}" + + if [ "${prod_blob}" = "${main_blob}" ]; then + echo " in sync at ${gonfalon_sha:0:9} (blob ${prod_blob})" + break + fi + + # SKIP_SPEC_PARITY releases what production is serving even though main is ahead. + # The build downloads the spec from prod either way, so this does not change what + # ships — it changes whether we refuse to ship. What it costs: the changelog and + # version still come from main, so the release notes may describe spec changes + # that are merged but not yet deployed, and the downstream gonfalon PR would + # record them against this version. Trim those entries from that PR before + # merging it; it requires review anyway. + if [ "${SKIP_SPEC_PARITY:-false}" = "true" ]; then + echo " NOT IN SYNC, but SKIP_SPEC_PARITY=true — proceeding." + echo " prod blob ${prod_blob}" + echo " main blob ${main_blob} at ${gonfalon_sha:0:9}" + echo " Releasing the spec production currently serves. Check the changelog" + echo " entries in the gonfalon PR against what actually shipped before merging it." + break + fi + + # Two blob hashes say nothing about what is being waited on. Resolve prod's blob + # back to the gonfalon commit that produced it, once, on the first mismatch. + if [ -z "${drift_explained:-}" ]; then + drift_explained=1 + echo " not in sync. Resolving which commit prod is serving..." + spec_history=$(gh_gonfalon api \ + "repos/${GONFALON}/commits?path=${SPEC_PATH}&per_page=20" \ + --jq '.[] | "\(.sha) \(.commit.committer.date) \(.commit.message | split("\n")[0])"' 2>/dev/null || true) + + prod_commit="" + while IFS= read -r line; do + [ -n "${line}" ] || continue + c=${line%% *} + b=$(gh_gonfalon api "repos/${GONFALON}/git/trees/${c}:${spec_dir}" \ + --jq ".tree[] | select(.path == \"${spec_file}\") | .sha" 2>/dev/null || true) + if [ "${b}" = "${prod_blob}" ]; then prod_commit=${line}; break; fi + done <<<"${spec_history}" + + main_line=$(head -1 <<<"${spec_history}") + if [ -n "${prod_commit}" ]; then + echo " prod is serving ${SPEC_PATH} from ${prod_commit}" + else + echo " WARNING: prod's spec matches no recent commit on main. It may be serving" + echo " something unreleased, or the file moved. Investigate before releasing." + fi + echo " main's latest spec commit is ${main_line}" + echo " Waiting for that to reach production." + fi + + now=$(date +%s) + if [ "${now}" -ge "${deadline}" ]; then + fail "prod spec (${prod_blob}) still differs from ${GONFALON} main (${main_blob}) after ${PARITY_TIMEOUT}s. + Waiting on: ${main_line:-the latest spec commit on main} to deploy to production. + Releasing before it does would publish release notes describing a spec that + production is not yet serving. Either wait for the deploy and re-dispatch, or + raise PARITY_TIMEOUT. Check the gonfalon deploy pipeline and #proj-openapi." + fi + echo " still waiting, retrying in ${PARITY_INTERVAL}s ($(( deadline - now ))s left)" + sleep "${PARITY_INTERVAL}" +done + +# --- Gate 2: that gonfalon commit is green ------------------------------------ +# The runbook's "make sure CI is passing (basically: Gonfalon is currently in a +# good state)", pinned to the commit whose spec we are about to ship. +# Uses check-runs only. The combined-status endpoint +# (/commits/{sha}/status) needs the `statuses` permission, which no policy in +# launchdarkly/.github-private grants and which may not be available on the +# OctoSTS app at all; `checks: read` is granted and has precedent +# (service-template-go-sync). check-runs covers the GitHub Actions checks, which +# is what "is gonfalon green" means here. The tradeoff is that legacy commit +# statuses from external systems are not considered. +if [ "${SKIP_CI_GATE:-false}" != "true" ]; then + note "Gate 2: checking CI for ${GONFALON}@${gonfalon_sha:0:9}" + check_runs=$(gh_gonfalon api "repos/${GONFALON}/commits/${gonfalon_sha}/check-runs" --paginate) || \ + fail "could not read check runs for ${GONFALON}@${gonfalon_sha:0:9}. + If this is HTTP 403, the federated token is missing 'checks: read' — add it to + .github/launchdarkly/ld-openapi-downstream.sts.yaml in launchdarkly/.github-private." + + total=$(jq '[.check_runs[]] | length' <<<"${check_runs}") + failed_checks=$(jq '[.check_runs[] | select(.conclusion == "failure" or .conclusion == "timed_out")] | length' <<<"${check_runs}") + + if [ "${failed_checks}" -gt 0 ]; then + jq -r '.check_runs[] | select(.conclusion == "failure" or .conclusion == "timed_out") | " failed: \(.name)"' <<<"${check_runs}" >&2 || true + fail "${GONFALON}@${gonfalon_sha:0:9} is not green (${failed_checks} of ${total} checks failed). Set SKIP_CI_GATE=true to override." + fi + echo " green (${total} check runs, none failed)" +else + echo " Gate 2 skipped (SKIP_CI_GATE=true)" +fi + +# --- Changelog: extract the [Unreleased] body -------------------------------- +note "Reading ${CHANGELOG_PATH} at ${GONFALON}@${gonfalon_sha:0:9}" +gh_gonfalon api "repos/${GONFALON}/contents/${CHANGELOG_PATH}?ref=${gonfalon_sha}" \ + -H "Accept: application/vnd.github.raw" > "${workdir}/CHANGELOG.md" || \ + fail "could not read ${CHANGELOG_PATH} from ${GONFALON}@${gonfalon_sha}" + +awk ' + /^## \[Unreleased\]/ { capture = 1; next } + /^## \[/ { capture = 0 } + capture { print } +' "${workdir}/CHANGELOG.md" > "${workdir}/unreleased.md" + +# Trim leading/trailing blank lines. +sed -i'' -e '/./,$!d' "${workdir}/unreleased.md" +printf '%s\n' "$(cat "${workdir}/unreleased.md")" > "${workdir}/unreleased.md" + +if ! grep -q '[^[:space:]]' "${workdir}/unreleased.md"; then + fail "the [Unreleased] section of ${CHANGELOG_PATH} is empty — there is nothing to release" +fi + +# --- Bump type: inferred from the [Unreleased] section headings --------------- +# Removed/Changed -> major (both have historically carried breaking changes) +# Added/Deprecated -> minor +# Bug Fixes/Fixed -> patch +# +# NOTE: `### Changed` is genuinely ambiguous — it covers breaking and additive +# changes alike — so this rule deliberately errs toward major. Override with +# BUMP_OVERRIDE when you know better. +headings=$(grep -E '^### ' "${workdir}/unreleased.md" | sed 's/^### //' | sort -u || true) +[ -n "${headings}" ] || fail "no '### ' headings found under [Unreleased]; cannot infer a bump type" + +if [ -n "${BUMP_OVERRIDE:-}" ]; then + bump=${BUMP_OVERRIDE} + case "${bump}" in + major|minor|patch) ;; + *) fail "BUMP_OVERRIDE must be major, minor, or patch (got '${bump}')" ;; + esac + note "Bump type: ${bump} (overridden)" +elif grep -qxE 'Removed|Changed' <<<"${headings}"; then + bump="major" +elif grep -qxE 'Added|Deprecated' <<<"${headings}"; then + bump="minor" +elif grep -qxE 'Bug Fixes|Fixed|Security' <<<"${headings}"; then + bump="patch" +else + fail "unrecognised [Unreleased] headings, cannot infer a bump type: +$(sed 's/^/ /' <<<"${headings}") + Add the heading to preflight.sh's rule or pass BUMP_OVERRIDE." +fi +[ -n "${BUMP_OVERRIDE:-}" ] || note "Bump type: ${bump} (from headings: $(paste -sd, - <<<"${headings}"))" + +# --- Version: previous api-client-go tag + bump ------------------------------ +# Sort explicitly rather than trusting the tags endpoint's order, which GitHub +# does not document as semver-descending — and lexicographic order would put +# v9.0.0 above v17.0.0 anyway. +prev_tag=$(gh api --paginate "repos/${GO_CLIENT_REPO}/tags" --jq '.[].name' \ + | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -1) +[ -n "${prev_tag}" ] || fail "could not read the latest tag from ${GO_CLIENT_REPO}" +prev_version=${prev_tag#v} + +IFS=. read -r prev_major prev_minor prev_patch <<<"${prev_version}" +case "${bump}" in + major) version="$(( prev_major + 1 )).0.0" ;; + minor) version="${prev_major}.$(( prev_minor + 1 )).0" ;; + patch) version="${prev_major}.${prev_minor}.$(( prev_patch + 1 ))" ;; +esac + +if [ -n "${VERSION_OVERRIDE:-}" ]; then + grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$' <<<"${VERSION_OVERRIDE}" || \ + fail "VERSION_OVERRIDE must be bare semver MAJOR.MINOR.PATCH with no leading 'v' (got '${VERSION_OVERRIDE}')" + note "Version: ${VERSION_OVERRIDE} (overridden; derivation said ${version})" + version=${VERSION_OVERRIDE} +else + note "Version: ${version} (previous ${prev_version}, ${bump} bump)" +fi +major=${version%%.*} + +# --- Gate 3: no colliding tags ---------------------------------------------- +# A re-run after a partial failure trips over tags the previous attempt created. +# Non-Go repos get two tags per release: bare X.Y.Z from `make push`, and vX.Y.Z +# from the create-release job. Both have to go. +note "Gate 3: checking for existing ${version} / v${version} tags" +collisions=() +for repo in "${CLIENT_REPOS[@]}"; do + for ref in "${version}" "v${version}"; do + if gh api "repos/${repo}/git/ref/tags/${ref}" >/dev/null 2>&1; then + collisions+=("${repo}#${ref}") + fi + done +done + +if [ ${#collisions[@]} -gt 0 ]; then + if [ "${FORCE:-false}" = "true" ]; then + for collision in "${collisions[@]}"; do + repo=${collision%%#*} + ref=${collision##*#} + echo " deleting ${repo} tag ${ref}" + gh api -X DELETE "repos/${repo}/git/refs/tags/${ref}" + done + else + fail "tags for ${version} already exist: +$(printf ' %s\n' "${collisions[@]}") + A previous release attempt got partway through. Delete these tags, or re-run with force=true." + fi +fi + +# --- Emit -------------------------------------------------------------------- +if [ -n "${GITHUB_OUTPUT:-}" ]; then + delimiter="changelog-$(openssl rand -hex 8)" + { + echo "version=${version}" + echo "major=${major}" + echo "previous_version=${prev_version}" + echo "bump=${bump}" + echo "gonfalon_sha=${gonfalon_sha}" + echo "changelog<<${delimiter}" + cat "${workdir}/unreleased.md" + echo "${delimiter}" + } >> "${GITHUB_OUTPUT}" +fi + +if [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then + { + echo "### Release ${version}" + echo + echo "| | |" + echo "|---|---|" + echo "| Previous | \`${prev_version}\` |" + echo "| Bump | ${bump} |" + echo "| gonfalon commit | [\`${gonfalon_sha:0:9}\`](https://github.com/${GONFALON}/commit/${gonfalon_sha}) |" + echo "| Spec blob | \`${prod_blob}\` |" + echo + echo "
Changelog" + echo + cat "${workdir}/unreleased.md" + echo + echo "
" + } >> "${GITHUB_STEP_SUMMARY}" +fi + +note "Preflight passed. Releasing ${version}." diff --git a/scripts/release/publish.sh b/scripts/release/publish.sh index 7adb8cd..7e3aea5 100755 --- a/scripts/release/publish.sh +++ b/scripts/release/publish.sh @@ -9,6 +9,16 @@ echo >>~/.netrc "machine github.com login LaunchDarklyReleaseBot password ${GH_T git config --global user.name LaunchDarklyReleaseBot git config --global user.email launchdarklyreleasebot@launchdarkly.com +if [ "${DRY_RUN:-false}" = "true" ]; then + # Rehearsal: clones and commits for real, but pushes nothing and publishes nothing. + echo Simulating updates to client repositories... + make RELEASE_TARGETS="go java python ruby typescript-axios" TAG=${LD_RELEASE_VERSION} push_dry_run + + echo Simulating publishing of client artifacts... + make PUBLISH_TARGETS="python ruby typescript-axios java" publish_dry_run + exit 0 +fi + # Publish updates to client repositories echo Publishing updates to client repositories... make RELEASE_TARGETS="go java python ruby typescript-axios" TAG=${LD_RELEASE_VERSION} push diff --git a/scripts/release/selfcheck.sh b/scripts/release/selfcheck.sh new file mode 100755 index 0000000..2a4375f --- /dev/null +++ b/scripts/release/selfcheck.sh @@ -0,0 +1,354 @@ +#!/usr/bin/env bash +# +# Static checks over the release automation, runnable locally in seconds. +# +# ./scripts/release/selfcheck.sh +# +# Exists because every failure in this automation so far was discoverable without +# running a release, but nothing was checking for it. Each check below corresponds +# to a defect that actually shipped and cost a dispatch-and-wait cycle: +# +# OIDC a job used octosts-action without id-token: write +# POLICY-PERM the CI gate called an endpoint the trust policy did not permit +# POLICY-MATCH a job_workflow_ref pattern that could never match (unanchored) +# POLICY-REPOS a downstream target missing from the policy's allowlist +# SECRETS a workflow referenced a secret that did not exist +# DRYRUN a make dry-run target that was a silent no-op +# PORTABILITY mapfile / bare sed -i, which break on macOS bash 3.2 +# +# Checks needing the trust policy are SKIPped when it is unreachable (it lives in +# the private launchdarkly/.github-private), so this is useful in CI too, just +# with less coverage. Point POLICY_FILE at a local checkout to get the full set. + +set -uo pipefail + +cd "$(dirname "${BASH_SOURCE[0]}")/../.." || exit 1 + +REPO=launchdarkly/ld-openapi-private +POLICY_REPO=launchdarkly/.github-private +POLICY_PATH=.github/launchdarkly/ld-openapi-downstream.sts.yaml +POLICY_FILE=${POLICY_FILE:-$HOME/code/launchdarkly/.github-private/${POLICY_PATH}} + +# Scripts this automation owns. The per-target publish scripts predate it and +# carry unused-positional-arg warnings that are part of their interface. +OWNED_SCRIPTS=( + scripts/release/preflight.sh + scripts/release/token-audit.sh + scripts/release/verify-published.sh + scripts/release/mirror-public.sh + scripts/release/gonfalon-update.sh + scripts/release/publish.sh + scripts/release/selfcheck.sh +) + +pass=0; fail=0; skip=0 +ok() { echo " PASS $1"; pass=$((pass+1)); } +bad() { echo " FAIL $1"; fail=$((fail+1)); } +nope() { echo " SKIP $1"; skip=$((skip+1)); } + +# Resolve the trust policy: local checkout, else the API, else skip. +# +# Two traps here, both of which have bitten: +# - `|| true` on the fetch keeps a failed request's response body. A GitHub API +# error body is valid YAML, so it parses into a dict with none of the keys the +# checks read, and they then "fail" against something that is not a policy. +# With a token that cannot see the policy repo - CI, for one - that is the +# normal case, not an edge case. +# - so the fetch must respect gh's exit status AND the result must be shape-checked +# before use. Anything that is not recognisably a trust policy means SKIP. +policy="" +policy_problem="" +if [ -f "${POLICY_FILE}" ]; then + policy=$(cat "${POLICY_FILE}") +elif command -v gh >/dev/null 2>&1; then + if fetched=$(gh api "repos/${POLICY_REPO}/contents/${POLICY_PATH}" \ + -H "Accept: application/vnd.github.raw" 2>/dev/null); then + policy="${fetched}" + fi +fi +if [ -n "${policy}" ]; then + for key in subject_pattern claim_pattern permissions repositories; do + grep -qE "^${key}:" <<<"${policy}" || policy_problem="${policy_problem} ${key}" + done + if [ -n "${policy_problem}" ]; then + policy="" + policy_problem="fetched a response that is not a trust policy (missing:${policy_problem})" + fi +fi + +echo "== shellcheck ==" +if command -v shellcheck >/dev/null 2>&1; then + if out=$(shellcheck -S warning "${OWNED_SCRIPTS[@]}" 2>&1); then + ok "no warnings in ${#OWNED_SCRIPTS[@]} owned scripts" + else + bad "shellcheck warnings:" + sed 's/^/ /' <<<"${out}" | head -20 + fi +else + nope "shellcheck not installed" +fi + +echo +echo "== PORTABILITY: constructs that break on macOS bash 3.2 / BSD sed ==" +# This file necessarily contains the patterns it searches for, so exclude it. +port_targets=() +for f in "${OWNED_SCRIPTS[@]}"; do [ "${f}" = "scripts/release/selfcheck.sh" ] || port_targets+=("${f}"); done +port_hits=$(grep -nE '(^|[^-])\bmapfile\b|sed -i +[^.'"'"'"]' "${port_targets[@]}" 2>/dev/null || true) +if [ -z "${port_hits}" ]; then + ok "no mapfile, no bare 'sed -i'" +else + bad "non-portable constructs:" + sed 's/^/ /' <<<"${port_hits}" +fi + +echo +echo "== OIDC: every octosts-action step has id-token: write ==" +oidc=$(python3 - <<'PY' +import yaml, glob, os, sys +problems, checked = [], 0 +wf = {os.path.basename(p): yaml.safe_load(open(p)) for p in glob.glob('.github/workflows/*.yml')} + +def grants(doc, job): + for src in (job.get('permissions'), doc.get('permissions')): + if isinstance(src, dict) and src.get('id-token') == 'write': + return True + return False + +for fname, doc in wf.items(): + for jname, job in (doc.get('jobs') or {}).items(): + uses_sts = any('octosts-action' in str(s.get('uses', '')) for s in (job.get('steps') or [])) + if uses_sts: + checked += 1 + if not grants(doc, job): + problems.append(f"{fname}/{jname} runs octosts-action without id-token: write") + # A reusable-workflow call is capped by the caller's permissions. + called = str(job.get('uses', '')) + if called.startswith('./'): + target = wf.get(os.path.basename(called)) + if target and any( + 'octosts-action' in str(s.get('uses', '')) + for tj in (target.get('jobs') or {}).values() + for s in (tj.get('steps') or []) + ): + checked += 1 + if not grants(doc, job): + problems.append(f"{fname}/{jname} calls {os.path.basename(called)} (which mints a token) without granting id-token: write") +print(f"CHECKED {checked}") +for p in problems: + print(f"PROBLEM {p}") +PY +) +n=$(grep -oE 'CHECKED [0-9]+' <<<"${oidc}" | awk '{print $2}') +# If the raw YAML mentions octosts-action but the parser found no minting job, the +# extractor is broken and a pass would be meaningless. +raw_sts=$(grep -rlc 'octosts-action' .github/workflows/*.yml 2>/dev/null | wc -l | tr -d ' ') +if [ "${n:-0}" = "0" ] && [ "${raw_sts}" != "0" ]; then + bad "workflows reference octosts-action but no token-minting job was detected — the check is broken" +elif grep -q PROBLEM <<<"${oidc}"; then + bad "id-token gaps:" + grep PROBLEM <<<"${oidc}" | sed 's/PROBLEM / /' +else + ok "${n:-0} token-minting job(s), all grant id-token: write" +fi + +echo +echo "== POLICY-MATCH: the trust policy matches this repo's workflows on main ==" +if [ -z "${policy}" ]; then + nope "trust policy unavailable: ${policy_problem:-not found locally and not readable via gh} (set POLICY_FILE, or authenticate gh against ${POLICY_REPO})" +else + match=$(POLICY="${policy}" REPO="${REPO}" python3 - <<'PY' +import os, re, yaml, glob +pol = yaml.safe_load(os.environ['POLICY']); repo = os.environ['REPO'] +# octo-sts compiles claim patterns anchored: regexp.Compile("^" + v + "$") +jr = re.compile("^" + pol['claim_pattern']['job_workflow_ref'] + "$") +sr = re.compile("^" + pol['subject_pattern'] + "$") +problems = [] +if not sr.match(f"repo:{repo}:ref:refs/heads/main"): + problems.append(f"subject_pattern does not match a dispatch on main") +minting = [] +for p in glob.glob('.github/workflows/*.yml'): + doc = yaml.safe_load(open(p)) + if any('octosts-action' in str(s.get('uses','')) + for j in (doc.get('jobs') or {}).values() for s in (j.get('steps') or [])): + minting.append(os.path.basename(p)) +for f in sorted(minting): + claim = f"{repo}/.github/workflows/{f}@refs/heads/main" + if not jr.match(claim): + problems.append(f"job_workflow_ref does not match {claim}") +print(f"CHECKED {len(minting)}") +for p in problems: print("PROBLEM " + p) +PY +) + if grep -q PROBLEM <<<"${match}"; then + bad "policy would deny these:" + grep PROBLEM <<<"${match}" | sed 's/PROBLEM / /' + else + ok "$(grep -oE 'CHECKED [0-9]+' <<<"${match}" | awk '{print $2}') minting workflow(s) match on refs/heads/main" + fi +fi + +echo +echo "== POLICY-PERM: the policy permits every gonfalon endpoint preflight calls ==" +if [ -z "${policy}" ]; then + nope "trust policy unavailable${policy_problem:+: ${policy_problem}}" +else + # Endpoint -> required GitHub App permission. Hand-maintained; extend it when a + # new endpoint is called. This is the check that catches a 403 before CI does. + perm=$(POLICY="${policy}" python3 - <<'PY' +import os, re, yaml +pol = yaml.safe_load(os.environ['POLICY']) +granted = pol.get('permissions') or {} +RULES = [ + (r'/commits/[^/"]+/status', 'statuses'), + (r'/commits/[^/"]+/check-runs', 'checks'), + (r'/git/trees/', 'contents'), + (r'/contents/', 'contents'), + (r'/commits\?path=', 'contents'), # list commits touching a path + (r'/commits/main', 'contents'), +] +src = open('scripts/release/preflight.sh').read() +# Join backslash line-continuations first. Without this, a call whose URL sits on +# the next line is invisible to the extractor — so it is neither validated nor +# reported as unrecognised, which is precisely the silent gap this table exists to +# close. One such call shipped before this was fixed. +src = re.sub(r'\\\n\s*', ' ', src) +# Only calls routed through the gonfalon token are governed by this policy. +calls = re.findall(r'gh_gonfalon api\s+"([^"]+)"', src) +if not calls: + print("PROBLEM found no gh_gonfalon calls at all — the extractor is broken") +problems, seen = [], set() +for c in calls: + for pattern, need in RULES: + if re.search(pattern, c): + seen.add(need) + if need not in granted: + problems.append(f"{c} needs '{need}' but the policy grants: {sorted(granted)}") + break + else: + problems.append(f"{c} is not in selfcheck's endpoint->permission table; add it") +print(f"CHECKED {len(calls)} call(s), permissions needed: {sorted(seen)}") +for p in sorted(set(problems)): print("PROBLEM " + p) +PY +) + echo " $(grep -oE 'CHECKED.*' <<<"${perm}")" + if grep -q PROBLEM <<<"${perm}"; then + bad "permission gaps:" + grep PROBLEM <<<"${perm}" | sed 's/PROBLEM / /' + else + ok "every gonfalon endpoint is covered by the policy's permissions" + fi +fi + +echo +echo "== POLICY-REPOS: every downstream target is in the policy allowlist ==" +if [ -z "${policy}" ]; then + nope "trust policy unavailable${policy_problem:+: ${policy_problem}}" +else + targets=$(grep -oE 'repository: launchdarkly/[a-z0-9._-]+' .github/workflows/release.yml | awk '{print $2}' | sort -u) + allowed=$(POLICY="${policy}" python3 -c " +import os, yaml +print('\n'.join('launchdarkly/' + r for r in (yaml.safe_load(os.environ['POLICY']).get('repositories') or [])))") + missing="" + for t in ${targets}; do grep -qxF "${t}" <<<"${allowed}" || missing="${missing} ${t}"; done + if [ -z "${targets}" ]; then + bad "found no pr-downstream targets in release.yml — the extraction is broken" + elif [ -n "${missing}" ]; then + bad "pr-downstream targets absent from the policy's repositories:${missing}" + else + ok "all pr-downstream targets allowed ($(tr '\n' ' ' <<<"${targets}"))" + fi +fi + +echo +echo "== SECRETS: every secret the workflows reference exists on the repo ==" +if command -v gh >/dev/null 2>&1 && actual=$(gh api "repos/${REPO}/actions/secrets" --jq '.secrets[].name' 2>/dev/null) && [ -n "${actual}" ]; then + referenced=$(grep -rhoE 'secrets\.[A-Z0-9_]+' .github/workflows/ | cut -d. -f2 | sort -u) + missing="" + for s in ${referenced}; do + [ "${s}" = "GITHUB_TOKEN" ] && continue + grep -qxF "${s}" <<<"${actual}" || missing="${missing} ${s}" + done + if [ -n "${missing}" ]; then + bad "referenced but not present as repo secrets (may be org-level — verify):${missing}" + else + ok "all referenced secrets exist ($(tr '\n' ' ' <<<"${referenced}"))" + fi +else + nope "cannot list repo secrets (needs gh auth with admin on ${REPO})" +fi + +echo +echo "== ACTION-INPUTS: every input passed to a pinned action actually exists ==" +# A workflow passing an input the action does not declare gets only a warning, which +# nobody reads, and the value is silently ignored. `dryrun: ${{ inputs.dryRun }}` was +# passed to a pinned pr-downstream that has no such input, so rehearsals were +# attempting real PR creation. +if command -v gh >/dev/null 2>&1; then + ai=$(python3 - <<'PYEOF' +import yaml, glob, os, re, subprocess, sys +problems, checked, unresolved = [], 0, [] +for path in glob.glob('.github/workflows/*.yml'): + doc = yaml.safe_load(open(path)) + for jname, job in (doc.get('jobs') or {}).items(): + for step in (job.get('steps') or []): + uses, with_ = str(step.get('uses','')), (step.get('with') or {}) + # Only third-party actions pinned to a ref we can resolve. + if not with_ or '@' not in uses or uses.startswith('./'): + continue + repo, ref = uses.rsplit('@', 1) + if repo.count('/') != 1: + continue + try: + raw = subprocess.run( + ['gh','api',f'repos/{repo}/contents/action.yml?ref={ref}', + '-H','Accept: application/vnd.github.raw'], + capture_output=True, text=True, timeout=25) + if raw.returncode != 0 or not raw.stdout.strip(): + unresolved.append(f"{repo}@{ref[:12]}") + continue + declared = set((yaml.safe_load(raw.stdout).get('inputs') or {}).keys()) + except Exception: + unresolved.append(f"{repo}@{ref[:12]}") + continue + checked += 1 + for k in with_: + if k not in declared: + problems.append(f"{os.path.basename(path)}/{jname}: '{k}' is not an input of {repo}@{ref[:12]}") +print(f"CHECKED {checked}") +print(f"UNRESOLVED {len(set(unresolved))} {' '.join(sorted(set(unresolved)))}".rstrip()) +for p in sorted(set(problems)): print("PROBLEM " + p) +PYEOF +) + echo " $(grep -oE 'CHECKED [0-9]+' <<<"${ai}") pinned action(s) with inputs" + unresolved=$(grep -oE 'UNRESOLVED [0-9]+.*' <<<"${ai}" | cut -d' ' -f2-) + if grep -q PROBLEM <<<"${ai}"; then + bad "inputs that will be silently ignored:" + grep PROBLEM <<<"${ai}" | sed 's/PROBLEM / /' + elif grep -q 'CHECKED 0' <<<"${ai}"; then + # Passing here would be false assurance: it verified nothing. Needs a token + # with read access to the action repos (GITHUB_TOKEN suffices; they are public). + nope "resolved no pinned actions at all, so nothing was verified${unresolved:+ (unresolved: ${unresolved#* })}" + elif [ "${unresolved%% *}" != "0" ]; then + nope "some pinned actions could not be resolved and were not verified: ${unresolved#* }" + else + ok "all inputs passed to pinned actions are declared by them" + fi +else + nope "cannot resolve pinned actions (gh not available)" +fi + +echo +echo "== DRYRUN: rehearsal make targets are not silent no-ops ==" +for target in push_dry_run publish_dry_run; do + out=$(make -n "${target}" 2>&1 || true) + if grep -qi "nothing to be done" <<<"${out}" || [ -z "${out}" ]; then + bad "make ${target} does nothing — it needs a recipe or a prerequisite" + else + ok "make ${target} expands to $(wc -l <<<"${out}" | tr -d ' ') line(s) of recipe" + fi +done + +echo +echo "-----" +echo "PASS ${pass} FAIL ${fail} SKIP ${skip}" +[ "${fail}" -eq 0 ] || exit 1 diff --git a/scripts/release/token-audit.sh b/scripts/release/token-audit.sh new file mode 100755 index 0000000..b152db3 --- /dev/null +++ b/scripts/release/token-audit.sh @@ -0,0 +1,247 @@ +#!/usr/bin/env bash +# +# Read-only audit of every credential the release needs. Makes no changes: only +# GETs and whoami-style calls, so it is safe to run any time. +# +# A dry run cannot answer "do the tokens have the right permissions?" on its own. +# It skips create-release entirely, pr-downstream skips PR creation, and the +# publish dry-run scripts never authenticate to any registry. This closes that +# gap by asking each provider directly. +# +# Exits non-zero if a required GitHub permission is missing or a verifiable +# registry credential is rejected. Credentials with no read-only verification +# path are reported as UNVERIFIED rather than silently passed. +# +# Secrets are read from the environment and never printed. + +set -uo pipefail + +# Set to false when the corresponding job is switched off for this run, so a +# missing credential cannot block a release that was never going to use it. This +# also lets the flow be rehearsed before those tokens exist. +REQUIRE_MIRROR_TOKEN=${REQUIRE_MIRROR_TOKEN:-true} +REQUIRE_DOWNSTREAM_TOKEN=${REQUIRE_DOWNSTREAM_TOKEN:-true} + +pass=(); fail=(); unverified=(); skipped=() + +ok() { echo " PASS $1"; pass+=("$1"); } +bad() { echo " FAIL $1"; fail+=("$1"); } +unver() { echo " UNVERIFIED $1"; unverified+=("$1"); } +skip() { echo " SKIP $1"; skipped+=("$1"); } + +# --- GitHub tokens ----------------------------------------------------------- +# `.permissions` on the repo endpoint reflects what THIS token can do, so it +# answers the push/read question without writing anything. +check_repo_perm() { + local label=$1 token=$2 repo=$3 needed=$4 + + if [ -z "${token}" ]; then + bad "${label}: secret is not set (needs ${needed} on ${repo})" + return + fi + + # Terraform creates these SSM parameters holding a placeholder, so between the + # first apply and someone writing the real value there is a window where the + # secret exists but is the literal placeholder. Say so, rather than reporting + # an opaque "Bad credentials". + if [ "${token}" = "SET_IN_PARAMETER_STORE" ]; then + bad "${label}: still holds the terraform placeholder. Write the real value to its SSM parameter, then re-apply terraform so the secret picks it up." + return + fi + + local body + if ! body=$(GH_TOKEN="${token}" gh api "repos/${repo}" 2>&1); then + if grep -qi "404\|not found" <<<"${body}"; then + bad "${label}: cannot see ${repo} at all — token lacks access or repo is wrong (needs ${needed})" + else + bad "${label}: error reading ${repo} — $(head -1 <<<"${body}")" + fi + return + fi + + local granted + granted=$(jq -r '.permissions // {} | to_entries | map(select(.value)) | map(.key) | join(",")' <<<"${body}") + + if [ "${needed}" = "pull" ]; then + # Any successful read satisfies a read requirement. + ok "${label}: can read ${repo} (granted: ${granted:-read})" + elif jq -e --arg p "${needed}" '.permissions[$p] == true' <<<"${body}" >/dev/null 2>&1; then + ok "${label}: has ${needed} on ${repo} (granted: ${granted})" + else + bad "${label}: missing ${needed} on ${repo} (granted: ${granted:-none})" + fi +} + +echo "== GitHub: BOT_TOKEN ==" +echo " used by: preflight tag checks, publish.sh push, create-release, mirror-public" +for repo in api-client-go api-client-java api-client-python api-client-ruby api-client-typescript; do + # push also covers creating tags and GitHub releases, which create-release needs. + check_repo_perm "BOT_TOKEN" "${BOT_TOKEN:-}" "launchdarkly/${repo}" "push" +done + +echo +echo "== OctoSTS federated token ==" +echo " minted from the ld-openapi-downstream trust policy in launchdarkly/.github-private" +echo " used by: preflight (reads gonfalon's spec blob, CI status, changelog)" +echo " downstream-gonfalon and downstream-terraform-provider (branch + PR)" +# There is no stored secret to inspect here. What can go wrong is the federation +# itself: a policy that does not match, or a repo missing from its `repositories` +# allowlist. Checked via /installation/repositories, which enumerates exactly what +# the minted token is scoped to. +# +# NOT via `.permissions` on the repo endpoint: that field reports a *user's* role +# (push/pull/admin), and an App installation token has no role, only granular +# permissions — so it always reads as empty and every check would fail. +if [ -z "${STS_TOKEN:-}" ]; then + bad "OctoSTS: no federated token was minted. Check the octosts-action step and that .github/launchdarkly/ld-openapi-downstream.sts.yaml in launchdarkly/.github-private matches this workflow." +else + required=(launchdarkly/gonfalon) + if [ "${REQUIRE_DOWNSTREAM_TOKEN}" = "true" ]; then + required+=(launchdarkly/terraform-provider-launchdarkly) + else + skip "OctoSTS token on terraform-provider-launchdarkly: downstream PRs are disabled for this run" + fi + + if scoped=$(GH_TOKEN="${STS_TOKEN}" gh api /installation/repositories --paginate --jq '.repositories[].full_name' 2>/dev/null) && [ -n "${scoped}" ]; then + for repo in "${required[@]}"; do + if grep -qxF "${repo}" <<<"${scoped}"; then + ok "OctoSTS token is scoped to ${repo}" + else + bad "OctoSTS token cannot reach ${repo} — add it to the policy's 'repositories' list. In scope: $(paste -sd, - <<<"${scoped}")" + fi + done + # An installation token's permission set cannot be introspected over the API, + # so contents/pull_requests write is only proven when a PR is actually opened. + unver "OctoSTS token: permission level (contents and pull_requests write) cannot be read back; it is proven only when a downstream PR is opened" + else + # Fall back to reachability if that endpoint is unavailable — still catches a + # repo missing from the allowlist, which is the common failure. + for repo in "${required[@]}"; do + if GH_TOKEN="${STS_TOKEN}" gh api "repos/${repo}" >/dev/null 2>&1; then + ok "OctoSTS token can reach ${repo}" + else + bad "OctoSTS token cannot reach ${repo} — check the policy's 'repositories' list" + fi + done + unver "OctoSTS token: could not enumerate scope via /installation/repositories; fell back to per-repo reachability" + fi +fi + +echo +echo "== GitHub: BOT_TOKEN on the public mirror target ==" +echo " used by: mirror-public (squash push to launchdarkly/ld-openapi)" +if [ "${REQUIRE_MIRROR_TOKEN}" = "true" ]; then + check_repo_perm "BOT_TOKEN" "${BOT_TOKEN:-}" "launchdarkly/ld-openapi" "push" +else + skip "BOT_TOKEN on launchdarkly/ld-openapi: mirroring is disabled for this run" +fi + +# --- Registries --------------------------------------------------------------- +echo +echo "== Registries ==" + +if [ -z "${NPM_TOKEN:-}" ]; then + bad "NPM_TOKEN: secret is not set" +elif user=$(curl -s -f -H "Authorization: Bearer ${NPM_TOKEN}" https://registry.npmjs.org/-/whoami | jq -r '.username' 2>/dev/null) && [ -n "${user}" ]; then + ok "NPM_TOKEN: authenticates as ${user}" +else + bad "NPM_TOKEN: rejected by registry.npmjs.org/-/whoami" +fi + +# RubyGems API keys are scoped. A key scoped to push_rubygem — all that releasing +# needs, and the right level of privilege — is rejected by the profile endpoint, +# so a read check there says nothing useful about whether the key can publish. +# There is no read-only endpoint a push-only key can pass, so don't pretend. +if [ -n "${RUBYGEM_API_KEY:-}" ]; then + code=$(curl -s -o /dev/null -w '%{http_code}' -H "Authorization: ${RUBYGEM_API_KEY}" \ + https://rubygems.org/api/v1/profile/me.json || echo 000) + if [ "${code}" = "200" ]; then + ok "RUBYGEM_API_KEY: authenticates (profile readable, so the key is broadly scoped)" + else + unver "RUBYGEM_API_KEY: set; profile endpoint returned HTTP ${code}, which is expected for a push-only scoped key and does not indicate a problem" + fi +else + bad "RUBYGEM_API_KEY: secret is not set" +fi + +# PyPI has no read-only token introspection endpoint; the only way to exercise an +# upload token is to upload. Presence is all we can assert. +if [ -n "${PYPI_TOKEN:-}" ]; then + unver "PYPI_TOKEN: set, but PyPI offers no read-only way to validate an upload token" +else + bad "PYPI_TOKEN: secret is not set" +fi + +if [ -n "${CENTRAL_PORTAL_USERNAME:-}" ] && [ -n "${CENTRAL_PORTAL_PASSWORD:-}" ]; then + bearer=$(printf '%s:%s' "${CENTRAL_PORTAL_USERNAME}" "${CENTRAL_PORTAL_PASSWORD}" | base64 | tr -d '\n') + code=$(curl -s -o /dev/null -w '%{http_code}' -X POST \ + -H "Authorization: Bearer ${bearer}" \ + 'https://central.sonatype.com/api/v1/publisher/deployments?size=1' || echo 000) + case "${code}" in + 200) ok "CENTRAL_PORTAL_*: authenticates to central.sonatype.com" ;; + 401|403) bad "CENTRAL_PORTAL_*: rejected by central.sonatype.com (HTTP ${code})" ;; + # Anything else means the request shape was wrong, not the credentials. The + # portal has no documented whoami, so this is best-effort only. + *) unver "CENTRAL_PORTAL_*: set; portal returned HTTP ${code} to a best-effort probe, which reflects the probe rather than the credentials" ;; + esac +else + bad "CENTRAL_PORTAL_USERNAME/PASSWORD: not set" +fi + +# --- LD API key used by the CI sample programs -------------------------------- +echo +echo "== LaunchDarkly API ==" +echo " used by: ci.yml sample programs (create + delete a flag in the 'openapi' project)" +if [ -z "${LD_API_KEY:-}" ]; then + bad "LD_API_KEY: secret is not set" +else + code=$(curl -s -o /dev/null -w '%{http_code}' \ + -H "Authorization: ${LD_API_KEY}" \ + https://app.launchdarkly.com/api/v2/projects/openapi || echo 000) + case "${code}" in + 200) ok "LD_API_KEY: can read the 'openapi' project" ;; + 401) bad "LD_API_KEY: unauthorized" ;; + 403) bad "LD_API_KEY: forbidden on the 'openapi' project" ;; + 404) bad "LD_API_KEY: the 'openapi' project is not visible to this token" ;; + *) unver "LD_API_KEY: HTTP ${code} from the projects endpoint" ;; + esac + # The samples create and delete flags; a read-only token passes the check above + # but fails in CI, so flag that this audit cannot prove write access. + unver "LD_API_KEY: write access (samples create/delete flags) cannot be checked without writing" +fi + +# --- Report ------------------------------------------------------------------- +summary() { + echo "### Token audit" + echo + echo "| Result | Count |" + echo "|---|---|" + echo "| PASS | ${#pass[@]} |" + echo "| FAIL | ${#fail[@]} |" + echo "| UNVERIFIED | ${#unverified[@]} |" + echo "| SKIP | ${#skipped[@]} |" + if [ ${#fail[@]} -gt 0 ]; then + echo + echo "**Failures**" + printf -- '- %s\n' "${fail[@]}" + fi + if [ ${#unverified[@]} -gt 0 ]; then + echo + echo "**Unverified** (no read-only check exists; these can only fail at publish time)" + printf -- '- %s\n' "${unverified[@]}" + fi +} + +echo +echo "-----" +echo "PASS ${#pass[@]} FAIL ${#fail[@]} UNVERIFIED ${#unverified[@]} SKIP ${#skipped[@]}" +[ -n "${GITHUB_STEP_SUMMARY:-}" ] && summary >> "${GITHUB_STEP_SUMMARY}" + +if [ ${#fail[@]} -gt 0 ]; then + echo + echo "Credentials needing attention:" >&2 + printf ' %s\n' "${fail[@]}" >&2 + exit 1 +fi + +echo "All checkable credentials are good." diff --git a/scripts/release/verify-published.sh b/scripts/release/verify-published.sh new file mode 100755 index 0000000..e832f99 --- /dev/null +++ b/scripts/release/verify-published.sh @@ -0,0 +1,109 @@ +#!/usr/bin/env bash +# +# Runbook step 9: verify the release actually landed everywhere, instead of +# trusting that someone remembered to spot-check a repo. +# +# Usage: verify-published.sh VERSION +# +# Hard checks fail the job. Soft checks only warn: Maven Central's sync from the +# publishing portal to repo1 can lag well past a sensible CI timeout, so a miss +# there means "check later", not "the release is broken". + +set -euo pipefail + +version=${1:?usage: verify-published.sh VERSION} +major=${version%%.*} + +POLL_TIMEOUT=${POLL_TIMEOUT:-600} +POLL_INTERVAL=${POLL_INTERVAL:-20} + +failures=() +warnings=() + +# poll NAME MODE URL +# MODE is "hard" or "soft". +poll() { + local name=$1 mode=$2 url=$3 + local deadline=$(( $(date +%s) + POLL_TIMEOUT )) + + echo "==> ${name}" + while :; do + local code + code=$(curl -s -o /dev/null -L -w '%{http_code}' "${url}" || echo 000) + if [ "${code}" = "200" ]; then + echo " published (${url})" + return 0 + fi + + if [ "$(date +%s)" -ge "${deadline}" ]; then + if [ "${mode}" = "hard" ]; then + failures+=("${name} — last HTTP ${code} from ${url}") + echo " NOT FOUND after ${POLL_TIMEOUT}s (HTTP ${code})" + else + warnings+=("${name} — not visible yet (HTTP ${code}); registry sync may still be in flight") + echo " not visible yet (HTTP ${code}) — soft check, continuing" + fi + return 0 + fi + sleep "${POLL_INTERVAL}" + done +} + +poll "PyPI launchdarkly-api" hard "https://pypi.org/pypi/launchdarkly-api/${version}/json" +poll "RubyGems launchdarkly_api" hard "https://rubygems.org/api/v2/rubygems/launchdarkly_api/versions/${version}.json" +poll "npm launchdarkly-api-typescript" hard "https://registry.npmjs.org/launchdarkly-api-typescript/${version}" +# Soft, deliberately. Go has no publish step — the git tag IS the release, and the +# tag is checked below. proxy.golang.org is a demand-populated cache that also +# caches negative lookups, so a `go get` for the version before it existed (a dry +# run, say) makes it serve "unknown revision" for a while afterwards. That happened +# on 24.0.0 and failed this job on an otherwise complete release. +poll "Go module proxy api-client-go/v${major}" soft "https://proxy.golang.org/github.com/launchdarkly/api-client-go/v${major}/@v/v${version}.info" +poll "Maven Central com.launchdarkly:api-client" soft "https://repo1.maven.org/maven2/com/launchdarkly/api-client/${version}/api-client-${version}.pom" + +# Tags on the client repos, checking only what exists at THIS point in the graph. +# `make push` tags Go as v but the other four as a bare ; their +# v tag is created later, by the create-release job, which runs after +# this one. Checking v everywhere would fail four repos on every healthy +# release and stop the run before create-release ever got the chance. +echo "==> client repo tags" +for repo in api-client-go api-client-java api-client-python api-client-ruby api-client-typescript; do + if [ "${repo}" = "api-client-go" ]; then ref="v${version}"; else ref="${version}"; fi + if gh api "repos/launchdarkly/${repo}/git/ref/tags/${ref}" >/dev/null 2>&1; then + echo " launchdarkly/${repo} ${ref}" + else + failures+=("launchdarkly/${repo} has no ${ref} tag") + echo " launchdarkly/${repo} MISSING ${ref}" + fi +done + +if [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then + { + echo "### Publish verification for ${version}" + if [ ${#failures[@]} -eq 0 ]; then + echo + echo "All hard checks passed." + else + echo + echo "**Failures**" + printf -- '- %s\n' "${failures[@]}" + fi + if [ ${#warnings[@]} -gt 0 ]; then + echo + echo "**Warnings**" + printf -- '- %s\n' "${warnings[@]}" + fi + } >> "${GITHUB_STEP_SUMMARY}" +fi + +if [ ${#warnings[@]} -gt 0 ]; then + printf 'WARNING: %s\n' "${warnings[@]}" >&2 +fi + +if [ ${#failures[@]} -gt 0 ]; then + printf 'FAILED: %s\n' "${failures[@]}" >&2 + echo >&2 + echo "Re-running the release requires deleting the tags it already created (both X.Y.Z and vX.Y.Z on non-Go repos) or dispatching with force=true." >&2 + exit 1 +fi + +echo "All hard checks passed for ${version}." diff --git a/swagger-codegen-templates/javascript/package.mustache b/swagger-codegen-templates/javascript/package.mustache deleted file mode 100644 index 1e3e1f6..0000000 --- a/swagger-codegen-templates/javascript/package.mustache +++ /dev/null @@ -1,52 +0,0 @@ -{ - "name": "{{{projectName}}}", - "version": "{{{projectVersion}}}", - "description": "{{{projectDescription}}}", - "license": "{{licenseName}}", - "main": "dist{{#invokerPackage}}/{{.}}{{/invokerPackage}}/index.js", - "scripts": { - "build": "babel src -d dist", - "prepare": "npm run build", - "test": "mocha --require @babel/register --recursive" - }, - "browser": { - "fs": false - }, -{{#npmRepository}} - "publishConfig":{ - "registry":"{{npmRepository}}" - }, -{{/npmRepository}} - "dependencies": { - "@babel/cli": "^7.0.0", - "superagent": "^10.0.0" - }, - "devDependencies": { - "@babel/core": "^7.0.0", - "@babel/plugin-proposal-class-properties": "^7.0.0", - "@babel/plugin-proposal-decorators": "^7.0.0", - "@babel/plugin-proposal-do-expressions": "^7.0.0", - "@babel/plugin-proposal-export-default-from": "^7.0.0", - "@babel/plugin-proposal-export-namespace-from": "^7.0.0", - "@babel/plugin-proposal-function-bind": "^7.0.0", - "@babel/plugin-proposal-function-sent": "^7.0.0", - "@babel/plugin-proposal-json-strings": "^7.0.0", - "@babel/plugin-proposal-logical-assignment-operators": "^7.0.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-proposal-numeric-separator": "^7.0.0", - "@babel/plugin-proposal-optional-chaining": "^7.0.0", - "@babel/plugin-proposal-pipeline-operator": "^7.0.0", - "@babel/plugin-proposal-throw-expressions": "^7.0.0", - "@babel/plugin-syntax-dynamic-import": "^7.0.0", - "@babel/plugin-syntax-import-meta": "^7.0.0", - "@babel/preset-env": "^7.0.0", - "@babel/register": "^7.0.0", - "expect.js": "^0.3.1", - "mocha": "^8.0.1", - "sinon": "^7.2.0" - }, - "files": [ - "dist" - ] -} -