Skip to content

feat(snippets): entrypoint-based render + sdk.yaml reduced to id#405

Merged
kinyoklion merged 3 commits intomainfrom
rlamb/snippets-entrypoints
Apr 30, 2026
Merged

feat(snippets): entrypoint-based render + sdk.yaml reduced to id#405
kinyoklion merged 3 commits intomainfrom
rlamb/snippets-entrypoints

Conversation

@kinyoklion
Copy link
Copy Markdown
Member

@kinyoklion kinyoklion commented Apr 29, 2026

Stacked on #404. Two coordinated schema changes that simplify the contract between this repo and its consumers.

1. Drop `ld-application.get-started-file(s)`; render walks entrypoints

The CLI no longer asks each sdk.yaml which file in the consumer to touch. The consumer (gonfalon, ld-docs-private) declares its own list of entrypoint directories, and the renderer walks them recursively, picking up any file that has the right extension (`.tsx` / `.jsx` / `.ts` / `.js` / `.mdx`) AND contains the `SDK_SNIPPET:RENDER:` sentinel.

# Before
snippets render --target=ld-application --out=/path/to/gonfalon

# After
snippets render --target=ld-application \
  --entrypoint=/path/to/gonfalon/static/ld/components/getStarted

`--entrypoint` is repeatable. Walks skip `node_modules`, `.git`, `.next`, `dist`, `build`, `.cache`, `coverage`, `out`, `.turbo` wholesale. Symlinks aren't followed. Overlapping entrypoints dedupe.

This decouples the consumer's directory layout from the snippet sources: gonfalon is free to reorganize `static/ld/components/getStarted/sdk/` however it wants without us shipping a release.

2. `sdk.yaml` reduces to `id:`

# Before — 11 fields, none of which the engine actually read except get-started-file*
id: python-server-sdk
sdk-meta-id: python
display-name: Python (server)
type: server-side
languages: [...]
package-managers: [...]
regions: [...]
hello-world-repo: launchdarkly/hello-python
ld-application:
  get-started-file: ...
docs:
  reference-page: /sdk/server-side/python

# After
id: python-server-sdk

The id matches the SDK's identifier in `launchdarkly/sdk-meta`'s API packages. Every other piece of per-SDK metadata (display name, language flavors, supported regions, hello-world repo URL, docs path) lives there as the authoritative source — this repo doesn't need to duplicate it. The `descriptor` struct and `loadDescriptor` function are deleted; nothing in the engine reads sdk.yaml anymore.

End-to-end check against gonfalon

Step Result
Stash gonfalon's `static/ld/components/getStarted/sdk/` (26 files, 122 markers). `/tmp/sdk-stashed-entrypoints`
`sed` every marker hash to `0`. 122 / 122 reset
`snippets render --entrypoint=...getStarted` (one --entrypoint, no `--sdks`, embedded FS). 24 of 26 files rewritten
`diff -r stash gonfalon-sdk` exit 0 — byte-identical
`snippets verify --entrypoint=...getStarted` `ok`

Companion changes

  • feat: Composite action for synchronizing SDK snippets gh-actions#82 (the `sync-snippets` action) will need an `entrypoints` input added so the workflow can pass the directories through. Will update that PR once this lands.
  • gonfalon's local sync workflow draft will be updated to declare `static/ld/components/getStarted` (and any future entrypoint dirs) as inputs.

Out of scope

  • Dropping `ld-application.slot` from snippet frontmatter — separate PR per @rlamb's call.
  • Removing other dead snippet-frontmatter fields (`kind`, `description`, `lang`, `sdk`) — pending a final decision on which to keep as required documentation.

Note

Medium Risk
Medium risk because it changes the render/verify CLI contract and the ld-application adapter’s file discovery logic (now walking directories and filtering by sentinel/extension), which could miss or newly include files in consumer repos if entrypoints are misconfigured.

Overview
Reworks ld-application rendering to be entrypoint-driven. The snippets render/verify commands now require one or more repeatable --entrypoint directories (replacing --out) and pass these through to the adapter.

Changes file discovery from sdk-declared targets to consumer-tree scanning. The ld-application adapter now walks each entrypoint recursively, skips common junk dirs (for example node_modules, .git, dist, build), ignores symlinks, filters to known extensions (.tsx/.jsx/.ts/.js/.mdx), and only processes files containing the SDK_SNIPPET:RENDER: sentinel; overlapping entrypoints are deduped.

Simplifies SDK metadata inputs. sdk.yaml is reduced to just id: across SDKs, the ld-application sdk.yaml descriptor loader is removed, and snippet frontmatter drops the ld-application hints struct (ld-application.slot). Tests and authoring docs are updated to reflect the new entrypoint workflow and discovery behavior.

Reviewed by Cursor Bugbot for commit 704448f. Bugbot is set up for automated code reviews on this repo. Configure here.

@kinyoklion kinyoklion requested a review from a team as a code owner April 29, 2026 22:50
kinyoklion added a commit to launchdarkly/gh-actions that referenced this pull request Apr 29, 2026
The CLI no longer asks each sdk.yaml which file in the consumer to
rewrite (see launchdarkly/sdk-meta#405); instead the consumer declares
its own list of search roots. Plumb that through:

- New required input `entrypoints` (newline-separated list of
  directories, relative to $GITHUB_WORKSPACE).
- Render step parses the input into individual `--entrypoint=` flags
  and passes them to `snippets render`.
- Empty / whitespace-only lines are tolerated and skipped; an empty
  list fails loudly.
- README + the in-action usage block updated to show the new shape.

Consumer wiring becomes:

    - uses: launchdarkly/gh-actions/actions/sync-snippets@main
      with:
        entrypoints: |
          static/ld/components/getStarted
        github-token: ${{ secrets.GITHUB_TOKEN }}
Comment thread snippets/internal/adapters/ldapplication/ldapplication.go
@kinyoklion kinyoklion marked this pull request as draft April 30, 2026 15:35
@kinyoklion kinyoklion force-pushed the rlamb/snippets-entrypoints branch from ff91662 to 6a5ff66 Compare April 30, 2026 15:43
@kinyoklion kinyoklion marked this pull request as ready for review April 30, 2026 16:14
@kinyoklion kinyoklion force-pushed the rlamb/snippets-entrypoints branch from 6a5ff66 to b5d7f1e Compare April 30, 2026 16:26
Base automatically changed from rlamb/snippets-release-pipeline to main April 30, 2026 16:27
Two related schema changes that simplify the contract between this
repo and its consumers:

1. Drop the `ld-application.get-started-file(s)` field entirely.
   The renderer no longer asks each sdk.yaml which file to rewrite.
   Instead the consumer (gonfalon, ld-docs-private) declares its own
   list of entrypoint directories, and the renderer walks them
   recursively for files containing the SDK_SNIPPET:RENDER sentinel.

   - `Render(sdksFS, entrypoints []string)` and the matching `Verify`
     replace the old `(sdksFS, appDir)` signatures.
   - The walk skips `node_modules`, `.git`, `.next`, `dist`, `build`,
     `.cache`, `coverage`, `out`, `.turbo`. Files are filtered by
     extension (.tsx/.jsx/.ts/.js/.mdx) and by a fast bytes.Contains
     pre-check for the marker sentinel before invoking the regex
     scanner.
   - Symlinks are skipped, so a malicious symlink farm in a vendored
     directory can't pull the renderer outside the entrypoint.
   - Overlapping entrypoints dedupe.
   - Empty entrypoint list, missing entrypoint, or entrypoint that
     points at a file all fail loudly.

2. Reduce sdk.yaml to a single field: `id:`. The id matches the
   SDK's identifier in launchdarkly/sdk-meta's API packages, where the
   authoritative metadata lives (display name, type, languages,
   regions, hello-world repo, docs URL). Every previously-modeled
   field on the descriptor was unread by this codebase; downstream
   consumers that need that metadata read sdk-meta directly.

   - The `descriptor` struct + `loadDescriptor` function are deleted.
     Nothing in the engine reads sdk.yaml today.
   - Each of the 23 sdk.yaml files reduces to a header comment plus
     `id: <sdk>`.

CLI:
  Old: snippets render --target=ld-application --out=<consumer>
  New: snippets render --target=ld-application --entrypoint=<dir> ...

  --entrypoint is a custom flag.Value that accumulates across repeats
  (mirrors the standard Go-stdlib stringSliceFlag idiom).

End-to-end against gonfalon's static/ld/components/getStarted:
  - Reset every marker hash to 0 (122 markers in 24 files).
  - `snippets render --entrypoint=...getStarted` rewrites all 24
    files via the embedded sdks/ tree.
  - Diff against pre-reset stash: byte-identical.
  - `snippets verify --entrypoint=...getStarted`: ok.

Companion changes follow:
  - launchdarkly/gh-actions PR #82 needs to add an `entrypoints` input
    that the consumer-side workflow declares.
  - This PR stacks on #404 (the embedding +
    release pipeline PR).
@kinyoklion kinyoklion force-pushed the rlamb/snippets-entrypoints branch from b5d7f1e to 90fd12a Compare April 30, 2026 16:29
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 90fd12a. Configure here.

Comment thread snippets/internal/model/model.go
@kinyoklion kinyoklion force-pushed the rlamb/snippets-entrypoints branch from 90fd12a to 560f8c0 Compare April 30, 2026 17:12
Per the schema audit, slot was a write-only field — declared on the
LDApplicationHints struct, decoded from frontmatter, never read by any
production code. The marker comment carries the snippet id directly,
so the per-page lookup slot was meant to power doesn't happen.

- Remove the LDApplicationHints struct and the LDApplication field on
  Frontmatter.
- Strip the `ld-application:\n  slot: <name>\n` block from all 122
  snippet files (KnownFields(true) would otherwise fail to parse them
  after the struct change).
- Render output unaffected: end-to-end against gonfalon shows
  `no changes` + `ok` from verify.
skipDirNames prunes well-known noise (node_modules, dist, build, …) under
each entrypoint, but filepath.WalkDir invokes the callback for the root
itself first. If a consumer pointed `--entrypoint=./build` at a project
that emits TSX there, the root's basename matched the skip-list and the
walk silently produced zero files. Guard the skip check so it only fires
beneath the root, and add a regression test.
@kinyoklion kinyoklion force-pushed the rlamb/snippets-entrypoints branch from 560f8c0 to 704448f Compare April 30, 2026 17:15
@kinyoklion kinyoklion merged commit 82a9c4f into main Apr 30, 2026
29 checks passed
@kinyoklion kinyoklion deleted the rlamb/snippets-entrypoints branch April 30, 2026 17:45
This was referenced Apr 30, 2026
kinyoklion added a commit to launchdarkly/gh-actions that referenced this pull request May 1, 2026
* feat(sync-snippets): composite action that pulls SDK snippets

New action at actions/sync-snippets/. Resolves the latest
launchdarkly/sdk-meta `snippets/*` GitHub Release, downloads the
platform's signed binary, cosign-verifies it (keyless / OIDC,
identity pinned to sdk-meta's release-please workflow), runs
`snippets render` against the consumer checkout, and opens or
updates a sync PR via peter-evans/create-pull-request when the
render produced any diff.

Designed for gonfalon (and future ld-docs-private use); both call
this with `@main`. Daily cron + workflow_dispatch on the consumer
side keeps the consumer current without a coordinated rollout.

Implementation notes:

- The `snippets` CLI binary embeds the canonical sdks/ tree at build
  time (sdk-meta side, separate PR). One artifact, one signature
  check, atomic content + engine pinning.
- `--certificate-identity-regexp` matches sdk-meta's release-please
  workflow path on main, so a token leaked from any other workflow
  cannot produce a matching OIDC claim.
- Per-platform archive name resolution mirrors goreleaser's default
  template (snippets_<version>_<os>_<arch>.tar.gz).

* feat(sync-snippets): switch to consumer-declared entrypoints

The CLI no longer asks each sdk.yaml which file in the consumer to
rewrite (see launchdarkly/sdk-meta#405); instead the consumer declares
its own list of search roots. Plumb that through:

- New required input `entrypoints` (newline-separated list of
  directories, relative to $GITHUB_WORKSPACE).
- Render step parses the input into individual `--entrypoint=` flags
  and passes them to `snippets render`.
- Empty / whitespace-only lines are tolerated and skipped; an empty
  list fails loudly.
- README + the in-action usage block updated to show the new shape.

Consumer wiring becomes:

    - uses: launchdarkly/gh-actions/actions/sync-snippets@main
      with:
        entrypoints: |
          static/ld/components/getStarted
        github-token: ${{ secrets.GITHUB_TOKEN }}

* feat(sync-snippets): switch to github attestation verification

The upstream sdk-meta release pipeline is moving from cosign keyless
sidecars to GitHub's first-party SLSA build-provenance attestation
(launchdarkly/sdk-meta#409). Match it on the verifier side:

- Replace `cosign verify-blob` with `gh attestation verify --signer-
  workflow launchdarkly/sdk-meta/.github/workflows/release-please.yml`.
  Same identity pinning, but the attestation is fetched from sdk-meta's
  repo-level attestation store rather than as `.sig`/`.pem` release
  assets.
- Drop the `sigstore/cosign-installer` step. `gh` ships preinstalled on
  every GitHub-hosted runner.
- Drop the `id-token: write` permission from the consumer wiring
  example — verification is read-only against sdk-meta's attestation
  store and doesn't need an OIDC token.
- Update README copy to match.

* fix(sync-snippets): drop literal ${{ from action description

The action manifest's description: field is template-evaluated at action-load
time. Embedding a copy-paste-able workflow snippet that contained
${{ secrets.GITHUB_TOKEN }} caused 'Unrecognized named-value: secrets'
parse failures whenever a consumer used the action — secrets aren't in
scope when the manifest itself is being loaded.

Replace the inline workflow example with a prose pointer to README.md,
which carries the full consumer wiring snippet.

* fix(sync-snippets): exclude drafts and pre-releases from latest resolution

A 'latest' lookup that catches sdk-meta's release-pipeline mid-publish
window would point at a draft release whose archives and attestations
haven't uploaded yet — and the subsequent 'gh release download' /
'gh attestation verify' would fail noisily but pointlessly. Pre-releases
are excluded under the same logic: consumers asking for 'latest' want
the latest stable, not a release-candidate the pipeline cut for testing.

Per @keelerm84 review on PR #82.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants