fix(cli-generator): ship README, LICENSE and metadata in npm packages - #17551
Conversation
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
There was a problem hiding this comment.
AI Review Summary
Copies README/LICENSE into each package dir before publish and threads description/license into the generated package.json bodies. The copy loop is correct and guarded for missing files. Main gap: the new description/license values are user-supplied config interpolated raw into an unquoted shell heredoc containing JSON, so quotes, newlines, $ or backticks will corrupt the generated package.json (or worse, get expanded by the shell).
- 🟡 1 warning(s)
- 🔵 1 suggestion(s)
To request another review, comment /ai-review on this pull request.
| const { binaryName, npmPublishInfo, repoUrl, packageMetadata } = args; | ||
| const launcherDescription = packageMetadata.description ?? `CLI for ${binaryName}`; | ||
| const licenseField = | ||
| packageMetadata.license != null | ||
| ? ` | ||
| "license": "${packageMetadata.license}",` | ||
| : ""; |
There was a problem hiding this comment.
🟡 warning
packageMetadata.description / license come from consumer config and are interpolated raw into an unquoted heredoc (<<PKGJSON, since ${VERSION} etc. expand) that emits JSON inside a YAML block scalar. A description containing " produces invalid JSON, a newline breaks the block indentation, and $(...)/backticks get executed by the publish job. Escape before emitting:
| const { binaryName, npmPublishInfo, repoUrl, packageMetadata } = args; | |
| const launcherDescription = packageMetadata.description ?? `CLI for ${binaryName}`; | |
| const licenseField = | |
| packageMetadata.license != null | |
| ? ` | |
| "license": "${packageMetadata.license}",` | |
| : ""; | |
| const { binaryName, npmPublishInfo, repoUrl, packageMetadata } = args; | |
| // Emitted into an unquoted heredoc: JSON-escape quotes/newlines, then | |
| // backslash-escape `$`/backtick so the shell doesn't expand them (the | |
| // backslashes are consumed by the heredoc, leaving valid JSON). | |
| const escapeForHeredocJson = (value: string): string => | |
| JSON.stringify(value).slice(1, -1).replace(/[$`]/g, "\\$&"); | |
| const launcherDescription = escapeForHeredocJson(packageMetadata.description ?? `CLI for ${binaryName}`); | |
| const licenseField = | |
| packageMetadata.license != null | |
| ? ` | |
| "license": "${escapeForHeredocJson(packageMetadata.license)}",` | |
| : ""; |
| * `github.license`, and `set -e` would otherwise fail the publish on a | ||
| * missing file. | ||
| */ | ||
| const PACKAGE_DOCS_COPY_STEP = ` |
There was a problem hiding this comment.
🔵 suggestion
This fragment depends on the step's cwd being the checkout root (and on PKG_DIR being absolute or relative to that same cwd). In the platform step it lands just before cd "${PKG_DIR}", so that holds; worth double-checking the launcher step doesn't cd anywhere earlier, otherwise the copy silently no-ops and we're back to "no README data" without a failing publish.
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Description
Linear ticket: Refs
npm renders a package's page from the README inside the published tarball, so a README sitting in the source repo is invisible to the registry. The generated
publish/publish-launchersteps writepackage.jsonfrom scratch and copy only the binary (orbin/cli.js), so every generated CLI's npm page reads "no README data" — e.g.@elevenlabs/cliat1.1.0. The launcher also carried nolicensefield and a hardcoded"CLI for <binaryName>"description, ignoring thepackageIdentity.description/licensethe consumer already set for the crate and the Homebrew formula.Not retroactively fixable — the page is a property of a published version, so it lands with the consumer's next release.
Changes Made
emitPublishWorkflowstages docs into every package dir, in both the platform matrix and the launcher step:LICENSEonly exists when the consumer configuredgithub.license, and these steps run underset -euo pipefail.filesis left alone — npm includes README/LICENSE in the tarball regardless.packageMetadataargument (description,license) threaded fromrunPipeline, defaulting offpackageIdentityanddefaultCrateDescription(...)— the same values the crate's[package]block and the Homebrew formula already use.licenseis omitted entirely when unset rather than emitted empty.Testing
@fern-api/cli-generator: 425 passing. New cases assert the copy loop appears in both publish steps, that the configured description/license reach bothpackage.jsonbodies, and that no"license"key is emitted when unset.ci.ymlfor an ElevenLabs-shaped OIDC config; parses as YAML, and everyrun:block passesbash -n(16 steps).Stacked on #17507 (same file, and its
--manifest-pathtest steps are in the sametestjob).Link to Devin session: https://app.devin.ai/sessions/0c32af0182744cbe8d22f33a85a93160
Open in Devin Desktop: https://app.devin.ai/desktop/session/0c32af0182744cbe8d22f33a85a93160?variant=devin