feat: Add remaining getting-started snippets.#398
Draft
kinyoklion wants to merge 28 commits intomainfrom
Draft
Conversation
…ing-started
Introduces sdk-meta/snippets/, a self-contained Go module that owns the
canonical source for LaunchDarkly SDK code samples and renders them
into downstream consumers. Slice scope: python-server-sdk "Getting
Started" flow, end-to-end.
What's here
-----------
- cmd/snippets — CLI with three subcommands:
render — rewrite the body of every JSX element marked with an
SDK_SNIPPET:RENDER comment in a consumer checkout
verify — recompute hashes and fail if any managed region drifted
validate — run each snippet inside a per-language Docker validator
- internal/model — snippet file format (YAML frontmatter + one fenced
code block) loaded from sdks/<id>/snippets/.
- internal/render — minimal {{ var }} / {{ if var }}...{{ end }}
templating engine with a runtime mode for validation and a JS
template-literal mode for the ld-application adapter.
- internal/markers — host-syntax-aware scanner for SDK_SNIPPET:RENDER
comments (`// …`, `{/* … */}`, `/* … */`); hashes the rendered region
to catch hand-edits.
- internal/adapters/ldapplication — first adapter target. Discovers
consumer files via each sdk.yaml's ld-application.get-started-file
field and rewrites only the JSX children of marked <Snippet>
elements, preserving surrounding whitespace.
- internal/validate — orchestrator that builds a Docker image per
language and runs the snippet verbatim against a real LaunchDarkly
environment. The SDK key and flag key come from the caller's
LAUNCHDARKLY_SDK_KEY / LAUNCHDARKLY_FLAG_KEY env vars (the same
convention the hello-* sample apps use), are forwarded into the
container, and never end up in committed files.
- sdks/python-server-sdk — sdk descriptor plus four snippets sourced
verbatim from the existing get-started flow:
getting-started/mkdir
getting-started/install
getting-started/main-py
getting-started/run
- validators/languages/python — Docker image plus run.sh that pip-
installs the snippet's own requirements, runs the entrypoint, and
matches the expected flag-evaluation line within a timeout.
- docs/AUTHORING.md — snippet authoring guide.
Naming and module path
----------------------
- Go module: github.com/launchdarkly/sdk-meta/snippets
- Adapter target: --target=ld-application (renders into the LD
application UI). A future --target=ld-docs adapter is planned for
the docs site; not implemented yet.
Out of scope (kept in design doc, not in this commit): all SDKs other
than python-server-sdk, the ld-docs MDX adapter, GitHub Actions,
signed-binary release pipeline, sdk-meta capability integration,
region/version conditional rendering.
Verified locally
----------------
- go build ./... : clean
- go test ./... : render + markers tests pass
- snippets render --target=ld-application --out=<app-checkout>: idempotent
- snippets verify --target=ld-application --out=<app-checkout>: ok
- snippets validate (with LAUNCHDARKLY_SDK_KEY + LAUNCHDARKLY_FLAG_KEY):
matches "*** The <flag> feature flag evaluates to ..." against a
real LD environment.
Findings 1-19 from the multi-agent review (artifacts/multi-review-sdk-meta-396.md). All worth-fixing items are addressed in this commit; the four "could not prove" hardening items are deferred (they remain open documentation in the review file). Security and correctness ------------------------ - #1, #5: Marker hash now covers the full <Tag …>…</Tag> element, not just the children, so attribute-only edits (e.g. lang="python" → lang="go") are detected by `verify`. The `hash=` field is required at verify time; a missing hash is an error rather than a skip. - #2: validation.entrypoint must be a plain filename (filepath.Base equal, no path separators or ..). Blocks the "snippet writes to ~/.ssh" class of attack via author-controlled YAML. - #3: ld-application.get-started-file is rejected if it's absolute or if filepath.Rel(appDir, full) starts with "..". Blocks the same class via the consumer-side path. - #4: Template tokenizer was treating any token starting with "end" (`endTime`, `endIndex`, …) as `{{ end }}`. Switched HasPrefix → equality. - #6: Marker scanner now tracks <Tag depth so a nested same-tag pair (`<Snippet><Snippet>…</Snippet></Snippet>`) doesn't silently truncate to the inner close. Same-prefix tags (`<SnippetGroup>`) don't count as opens of `<Snippet>`. - #7: runtimeInputs has a `sdk-key` arm wired to LAUNCHDARKLY_SDK_KEY, with a defensive check that flag-key/sdk-key inputs cannot declare a runtime-default (those values must always come from the environment). - #8: validation.requirements rejects newlines and lines starting with `-` so a snippet can't smuggle `--extra-index-url` through to pip. - #9: Backtick-string scanner now tracks `${ … }` expression depth, so a nested template literal inside an interpolation expression doesn't prematurely end the outer string. - #10: Render path split into RenderForLDApplicationTemplate (escaping for backtick literals) and RenderForJSXText (no escaping; for bare text). The bare-text path no longer corrupts backslashes / backticks in user-visible output. - #11: Atomic write — temp file in the same directory, fsync, rename; source file mode is preserved. - #12: Validator Docker tag is a content hash of the validator dir, so concurrent runs against the same validator share the cached image and runs against different validators cannot interleave. - #13: run.sh redacts LAUNCHDARKLY_SDK_KEY from the log dump on failure. - #14: Bare-vs-backticks decision is driven by the snippet's intent (interpolation / multiline / JSX-special chars), not by what's already in the file. Re-renders no longer stay sticky on the wrapped form. - #17: snippet frontmatter and sdk.yaml are decoded with KnownFields(true) so a typo like `Entrpoint:` is a hard error. Cosmetic / docs --------------- - #15: AUTHORING.md notes the uppercase-first JSX-component-tag constraint. - #16: hashLen const + comment documenting the 12-hex-char (~48 bit) budget is for accidental-drift detection only, not integrity. - #19: go.mod uses `go 1.24` (drops the patch version). Tests ----- - New tests for: endFoo regression, unmatched/unclosed `{{ if/end }}`, unknown variable, empty template, RenderForJSXText backslash round-trip, HasInterpolation, ContainsJSXSpecial, scanner edge cases (no markers, block-style, unterminated comment, gap, self-closing, missing close, nested same-tag, similar-prefix tag, nested-backtick), full-element hash detects attribute edits, descriptor-traversal rejects, runtimeInputs rejects runtime-default on key types, requirements rejects pip flags, entrypoint rejects path components. Verified end-to-end ------------------- - go build ./... / go vet ./... / go test ./... all clean - snippets render --target=ld-application --out=<app>: rewrites the file with full-element hashes; second run reports "no changes". - snippets verify ok on the freshly rendered file. - snippets verify rejects: a one-byte attribute edit (lang="python" → lang="go") AND a marker with the hash= field stripped. - snippets validate (with real LAUNCHDARKLY_SDK_KEY / LAUNCHDARKLY_FLAG_KEY) matches the expected flag-evaluation line.
Walk back the part of the prior review-feedback commit that hashed the full <Tag …>…</Tag> region. The scope=content contract says the consumer owns the element's attributes; locking them down forces a re-render every time someone tweaks `withCopyButton` / `label="…"` / `className` and offers nothing the design promised. `verify` now: - requires a hash= field on every marker (#5, unchanged) - compares it against the SHA-256 of src[RegionStart:RegionEnd] (just the children, as originally documented) - accepts attribute-only edits, rejects child edits Tests updated: TestVerify_AcceptsAttributeEdit and TestVerify_RejectsChildEdit pin the new contract; the dead TestFullElementHash_DetectsAttributeEdit is removed; the now-unused Match.FullElementHash method is dropped. End-to-end re-checked: render is idempotent, verify ok, an attribute edit passes verify, a child edit fails verify with a children-hash error, and `snippets validate` against a real LD environment still prints `*** The sample-feature feature flag evaluates to True`.
…taging
Today's validate.go hardcodes a Python branch and only handles snippets that
are a single self-contained file. The remaining gonfalon SDKs need
per-language harnesses, multi-file staging (java pom.xml, rust Cargo.toml,
ios Podfile, etc.), and at least one runtime (iOS) that can't run in a
Linux Docker container.
Changes:
- Each `validators/languages/<runtime>/` now carries a `runner.yaml`
declaring `mode: docker | native`, `runs-on: <runner-hint>`, and
`image-prefix:` for docker runtimes. Python's runner.yaml is included.
- `internal/validate/runner.go` (new) loads the runner descriptor with
KnownFields(true) so a typo there is rejected at validate time.
- `internal/validate/validate.go` is restructured around:
* `envInputs` carrying the four EXAM-HELLO env values
(LAUNCHDARKLY_SDK_KEY / FLAG_KEY / MOBILE_KEY / CLIENT_SIDE_ID)
* `stageSnippet` writing the entrypoint snippet AND every snippet
named in `validation.companions:` to the staging dir at each
snippet's `file:` path (with stage-path traversal guard)
* `runDocker` and `runNative` paths selected by the runner descriptor
* `requireEnvForInputs` failing fast when a snippet declares an
input of a key type but the corresponding env var isn't set
- `internal/model/model.go` extends Validation with `Runtime`,
`Companions`, plus a longer doc comment for the existing fields. The
python.snippet.md still uses the back-compat `validation.entrypoint`
fallback (no change to existing snippet content).
- `runtimeInputs` adds `mobile-key` and `client-side-id` arms (also
rejecting runtime-default for those, matching sdk-key/flag-key).
- Tests updated for the new envInputs signature; new
TestCheckStagePathRejectsTraversal replaces the old entrypoint-only
guard test.
Verified: `go build ./... && go test ./...` clean; Python end-to-end
validate against the real LD env still prints
`*** The sample-feature feature flag evaluates to True` and exits 0.
Verifies the per-runtime dispatcher refactor end-to-end on a second SDK beyond python. The validator builds a Go 1.24 image, stages the snippet, runs `go mod init` + `go mod tidy` (matching the gonfalon flow) and execs the program with CI=1 so the listener loop short-circuits after the first evaluation. Adds shared/lib.sh under validators/ for harness boilerplate (require_env, await_success_line, dump_redacted, fail_with_log) and switches the docker build context to validators/ so each Dockerfile can pull from `shared/` alongside its own `languages/<runtime>/` dir. The python harness is updated to source the shared lib; behavior unchanged. Verified: snippets render idempotent on go.tsx, snippets verify ok, snippets validate --sdk=go-server-sdk against the real LD env prints `*** The 'sample-feature' feature flag evaluates to true.` and exits 0.
…validator Both Node SDKs share the validators/languages/node/ harness: it inits a package.json (npm init -y) when one isn't present, installs whatever the snippet's `validation.requirements` lines name, and runs the program with CI=1 so the listener loop short-circuits after the first evaluation. Verified node-server-sdk end-to-end against the real LD env (SDK key in caller env). node-client-sdk requires LAUNCHDARKLY_CLIENT_SIDE_ID which isn't set locally; the dispatcher's requireEnvForInputs fails fast with a clear error, and CI will exercise it once the secret is provisioned.
Validator wraps the snippet in a one-liner that sets $stdout.sync = true before loading it; otherwise Ruby block-buffers stdout when redirected to a file and the success line never reaches the validator's grep within the timeout. Snippet content is unchanged. Verified end-to-end against the real LD env.
Validator uses the system composer pre-installed in the image (rather than bootstrapping composer.phar like the snippet does — composer.phar is what the user-facing snippet shows; the validator just needs the deps available). Verified end-to-end against the real LD env.
.github/workflows/snippets-validate.yml runs the validator for each snippet-bearing SDK as a parallel matrix cell. fail-fast is disabled so every cell runs to completion. After the matrix finishes, a final `summary` job downloads each cell's artifact (status + log), writes a markdown table to GITHUB_STEP_SUMMARY listing each SDK / result / 3-line excerpt of the failure log, and exits non-zero if any cell failed so the PR check goes red. Required repo secrets (configure once at the sdk-meta repo level): - LAUNCHDARKLY_SDK_KEY (required) - LAUNCHDARKLY_CLIENT_SIDE_ID (required for client SDKs) - LAUNCHDARKLY_MOBILE_KEY (required for mobile SDKs) Initial matrix covers the SDKs ported so far (python, go, node-server, node-client, ruby, php). Additional SDKs are added by appending rows to matrix.include as their snippet dirs and validator harnesses land in this PR.
Validator reproduces gonfalon's `cargo new` + `cargo add` flow inside a Rust 1.83 image: bootstrap a Cargo project, copy the snippet's src/main.rs over the default template, add the SDK + tokio dependencies, and `cargo run`. Long timeout (300s) accommodates the dependency-graph compile on cold cache. Local validation deferred to CI (cargo cold-build of the LD SDK takes several minutes). rust-server-sdk row added to the matrix so the ubuntu-latest cell exercises it on every PR.
…jection Drops the static repo-secret approach in favor of the same OIDC-based flow the hello-* sample apps use. Each matrix cell now calls launchdarkly/gh-actions/actions/verify-hello-app@verify-hello-app-v2.0.1 which: - assumes the AWS role from `vars.AWS_ROLE_ARN` - pulls the LaunchDarkly Sandbox account credential from Secrets Manager and injects it as LAUNCHDARKLY_SDK_KEY, LAUNCHDARKLY_CLIENT_SIDE_ID, or LAUNCHDARKLY_MOBILE_KEY according to the use_*_key flag - runs `command:` and asserts its output contains the EXAM-HELLO `feature flag evaluates to true` line Each matrix row declares `key-type: server | client | mobile` so the right flag is set per SDK. We still set LAUNCHDARKLY_FLAG_KEY=sample- feature ourselves; the action only handles the SDK-side credential. Required setup at the sdk-meta repo level: - `vars.AWS_ROLE_ARN` — the same repo *variable* every hello-* repo already references. Not a secret. - `permissions: id-token: write` is set on the workflow; no static LD secrets are stored on this repo.
The original gonfalon snippet printed 'Feature flag X is true', which diverges from the EXAM-HELLO standard (`feature flag evaluates to true`) every other SDK follows. Validator surfaced this as a 'fix on red' case — the SDK was working, the print line was wrong. Verified end-to-end with LAUNCHDARKLY_CLIENT_SIDE_ID set.
Validator synthesizes a minimal .csproj around the snippet's Program.cs (gonfalon's flow uses Visual Studio's 'new console app' wizard which we can't reproduce in CI), pulls the package(s) named in validation.requirements via 'dotnet add package', and runs with CI=1. Verified end-to-end against the real LD env.
The .NET client SDK uses a mobile key (Configuration.Default takes the mobile key as its first arg, not an SDK key or client-side ID). Snippet input is typed `mobile-key`; validator pulls the value from LAUNCHDARKLY_MOBILE_KEY at runtime. Local validation 401'd against the test environment because the mobile key value provided was actually a server SDK key (`sdk-...` prefix instead of `mob-...`); CI will exercise it once the right value is provisioned. Snippet + validator + render markers are functionally correct.
Same gotcha as node-client-sdk: original gonfalon snippet printed 'Feature flag X is true' which doesn't match the EXAM-HELLO standard. Updated to 'The 'X' feature flag evaluates to true.' Verified end-to-end with the real mobile key (mob-...).
Java is the slice's first multi-fragment-snippet SDK: gonfalon shows
five separate XML/shell blocks (mvn-generate, cd-into, pom-dependency,
pom-build, pom-compiler) plus the App.java program plus the run line.
Each is its own snippet file with its own render marker; only
app-java carries `validation.runtime: jvm` and is the one the
validator actually exercises.
The JVM validator synthesizes a complete pom.xml around the snippet's
App.java rather than reproducing gonfalon's `mvn archetype:generate +
manual fragment pasting` flow. The synthesized pom pins
launchdarkly-java-server-sdk 7.13.4 (current latest on Maven Central
as of 2026-04-27); gonfalon's snippet shows \${version} fetched from
Maven Central asynchronously, which we don't reach out to from inside
the harness.
Two adjustments surfaced 'on red' that are baked into the validator:
- mvn archetype:generate writes `package com.launchdarkly.tutorial;`
on the first line of App.java; the gonfalon snippet drops that
(instructs the user to keep their auto-generated first line). The
harness prepends it back so javac can resolve the mainClass.
- the gonfalon Run command uses `mvn ... assembly:single` to attach
the maven-assembly-plugin to the build; the harness mirrors that.
Verified end-to-end against the real LD env.
The browser validator runs the snippet's index.html in headless Chromium (Playwright v1.59.1 from the pinned mcr.microsoft.com image) and polls the page body text for the EXAM-HELLO success line. The js-client snippet renders 'The <flag> feature flag evaluates to <value>.' into a div, which matches the regex used by the other validators. Verified end-to-end with the real LD client-side ID. Page text matched within the 30s window; SDK initialized + flag evaluation rendered correctly. Note: the snippet keeps gonfalon's deprecated unpkg URL (launchdarkly-js-client-sdk@3 — pre-rename, pre-v4). The current package is @launchdarkly/js-client-sdk at v4.x with a renamed createClient API. Migrating is a separate fix-on-red task tracked against the audit findings.
Roku snippets are rendered into gonfalon but carry no automated validation: BrightScript only runs on Roku devices and the proprietary BrightScript simulator, neither of which has a public CI runtime. The snippet is reviewed manually against a real device when changed; the version-staleness sweep still tracks the upstream package.zip release. Five snippets (mkdir, manifest, source/main.brs, components/AppScene.xml, components/AppScene.brs) all rendered with markers in roku.tsx; the 'Download package.zip' step and the Run instructions stay hand-authored prose since they're not Snippet elements. Not added to the validation matrix — the validate command would skip roku-client-sdk anyway because no snippet declares validation.runtime.
Five snippets (stack-new, package-yaml, stack-yaml, main-hs, run) all rendered with markers in haskell.tsx. The Haskell program follows the EXAM-HELLO output pattern (printf '*** The %s feature flag evaluates to %s'), so the validator (when added) just needs a stack image with launchdarkly-server-sdk + text pre-installed. Stack cold-build is multi-minute, so per-validate cost will be material; the image must prebuild deps to keep CI cycle time reasonable. The snippet retains a known bug from the gonfalon source — line 121 calls lookupEnv on the rendered featureKey value (looking up an env var named 'sample-feature') rather than 'LAUNCHDARKLY_FLAG_KEY'. The Nothing path defaults featureFlagKey to 'sample-feature' so the program coincidentally works in the test env; left as a fix-on-red target for when the validator lands. Not added to the validation matrix yet — `validate --sdk=haskell- server-sdk` will fail because no snippet declares validation.runtime. Renders into gonfalon are exercised by the existing render+verify tests.
Seven snippets (rebar3-new, rebar-config, sup-childspecs, app-src, server-erl, run-shell, run-call) all rendered. Erlang's Get Started flow is fundamentally interactive (rebar3 shell + manual gen_server:call), so the existing snippets don't follow EXAM-HELLO output. Validator is left for a follow-up that wraps the gen_server in a runnable harness module.
The scanner was treating any `'` (or `"`) as the start of a string
literal, scanning until the matching close quote — including across
many lines. JSX text often contains apostrophes inside words (`SDK's
shared libraries`, `let's`), which would consume hundreds of bytes
and skip past following render markers.
skipPlainString now returns i+1 when no closing quote appears before
the next `\\n`, treating the opening character as not-a-string-start.
Multi-line strings in JS/JSX use backticks (handled by skipBacktick),
so this is a safe heuristic.
Symptom that surfaced this: lua-server-sdk's gonfalon page has
'C++ SDK\\'s shared libraries' in a `<p>` between two markers. The
scanner ate from that apostrophe through the end of the next snippet's
backtick-template body, missing the `luarocks-install`, `hello-lua`,
and `run` markers entirely. After the fix, all five lua markers are
detected and their hashes update on `render`.
feat(snippets): port lua-server-sdk + erlang-server-sdk + haskell-server-sdk
All three carry `# Validator pending` comments in their hello-world
frontmatter — none of them currently has a CI runtime in this repo:
- haskell needs a stack image with launchdarkly-server-sdk
pre-built; cold-build is multi-minute
- erlang's gonfalon flow is interactive (rebar3 shell + manual
gen_server:call), so the snippet needs a wrapper module before
a validator can match the EXAM-HELLO output
- lua wraps the C++ Server SDK and needs cmake + boost + openssl
plus luarocks; specialized image deferred
Each set of snippets renders into gonfalon idempotently. CI matrix is
unchanged — they're not validatable until a runtime lands.
…ing)
Each SDK has 9 snippets covering the gonfalon flow (mkdir, clone-sdk,
main-cpp, cmakelists, build-mkdir, cmake-make/ninja/msvc, cmake-build,
run). Validator pending: each per-validate cycle requires a Docker
image with cmake + boost + openssl + ninja and a checkout of cpp-sdks;
first build is multi-minute even with prebuilt deps.
Snippet output is non-canonical EXAM-HELLO ('Feature flag X is true');
when the validator lands the snippet will need a fix-on-red to print
the canonical 'feature flag evaluates to true' line.
Previously, any `{{ name }}` in a snippet body was treated as our own
substitution. That breaks Vue snippets, where `{{ flagValue }}` is
Vue's own runtime template syntax that needs to survive into the
rendered output untouched.
Renderer now takes a `declaredInputs` set. Names not in that set
round-trip as literal `{{ name }}` in both the runtime path
(validator) and the ld-application path (gonfalon). Conditionals
(`{{ if name }}…{{ end }}`) still require a declared input — Vue
uses `v-if`, not `{{ if }}`, so an undeclared conditional is almost
certainly an authoring mistake.
Tests updated. New regressions:
- TestRenderRuntimePassesThroughUnknownVar
- TestRenderRuntimeRejectsUnknownCondVar
- TestRenderForJSXTextPassesForeignTemplate
- HasInterpolation cases for foreign templates
Four snippets (podfile, pod-install, app-delegate, view-controller) all rendered with markers in ios.tsx. The Podfile retains gonfalon's '6.1.0' fallback as runtime-default so the rendered output keeps that fallback (current latest is 10.2.0 — bump deferred until iOS validator lands). Known bug carried verbatim: ViewController.swift's updateUi label uses '(flagKey)' and '(result)' instead of Swift's string interpolation '\(flagKey)' / '\(result)'. Fix-on-red when the iOS validator runs. Validator pending: iOS validation requires a macos-* runner with Xcode and 'xcodebuild test'; deferred.
Five snippets (build-gradle, main-activity, activity-main-xml, main-application, manifest) all rendered with markers in android.tsx. Validator pending — Android needs setup-android + Linux emulator boot in CI; tractable but slow and deferred.
Seven snippets covering the flutter create + pub add + ios/Podfile + android/build.gradle + lib/main.dart + run command, all rendered with markers in flutter.tsx. Validator pending — flutter-action on a Linux runner is feasible; deferred.
Completes the 23-SDK port of every gonfalon getStarted file.
react-native-client-sdk: five snippets (create-expo, install, app-tsx,
welcome-tsx, run) all rendered with markers in reactNative.tsx.
react-client-sdk: covers two gonfalon variants under one SDK directory:
- legacy variant (create-react-app) — render markers added to
react/legacy.tsx for create, install, index-tsx, run. App.tsx is
intentionally NOT marker-rendered: the gonfalon block uses
`camelCase(featureKey)` to camelCase the flag key for the React
useFlags hook, and our template engine doesn't yet support a
`| camelCase` filter. The canonical App.tsx content is captured
in legacy-app-tsx.snippet.md for the future docs adapter; the
gonfalon block stays hand-authored until the filter lands.
- createApp variant (Vite) — gonfalon's createApp.tsx loads its
main.tsx / App.tsx via `?raw` Vite imports and substitutes via
.replaceAll('environmentId', ...). Render markers don't fit
naturally without restructuring that file. The five snippets
(create-vite, install, main-tsx, app-tsx, run-dev) are present
in this dir but rendering into gonfalon's createApp.tsx is
deferred until that file is migrated off the assetSource
pattern.
All ported snippets currently have validators deferred — RN bundler /
Expo, Vite + Playwright, and CRA + Playwright are heavier to wire than
the Linux Docker server SDKs. Tracked for follow-up.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.