https://github.com/dmno-dev/fledgling
π£ Create and set up new npm packages w/ trusted (OIDC) publishing - for one package or a whole monorepo
https://github.com/dmno-dev/fledgling
npm package-management setup
Last synced: 30 days ago
JSON representation
π£ Create and set up new npm packages w/ trusted (OIDC) publishing - for one package or a whole monorepo
- Host: GitHub
- URL: https://github.com/dmno-dev/fledgling
- Owner: dmno-dev
- License: mit
- Created: 2026-06-12T21:00:39.000Z (about 1 month ago)
- Default Branch: main
- Last Pushed: 2026-06-12T23:07:51.000Z (about 1 month ago)
- Last Synced: 2026-06-12T23:09:14.227Z (about 1 month ago)
- Topics: npm, package-management, setup
- Language: TypeScript
- Homepage: https://fledgling.varlock.dev
- Size: 2.65 MB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# fledgling π£
[](https://www.npmjs.com/package/fledgling)
[](https://www.npmjs.com/package/fledgling)
[](https://www.npmjs.com/package/fledgling)
[](./LICENSE)
**Create and set up packages on npm with trusted publishing.**
> Brought to you by [Varlock](https://varlock.dev) π§ββοΈπ β [check it out to keep your secrets out of plaintext](https://varlock.dev).
`fledgling` claims your package name on npm and sets up token-less ([OIDC trusted](https://docs.npmjs.com/trusted-publishers/)) publishing β no `NPM_TOKEN`, no clicking through the npm website. It works for a single package or a whole monorepo, and it's idempotent, so you can re-run it any time you add a package.
Designed to be run with `npx` (or `bunx` / `pnpm dlx`):
```sh
npx fledgling # interactive walkthrough (in a terminal)
npx fledgling add my-great-new-idea --new # claim a brand-new name, nothing in the repo yet
npx fledgling add "*" --yes # every package in a monorepo (globs ok)
npx fledgling sync # reconcile trusted publishing to your config
```
Run bare `fledgling` in a terminal and you get an interactive wizard (powered by [clack](https://github.com/bombshell-dev/clack)); pass packages to `add` (or `--yes` / run in CI) and it goes non-interactive.
### Commands
| Command | What it does |
|---------|--------------|
| `fledgling` | Interactive wizard (the default) |
| `fledgling add [packagesβ¦]` | Claim names + set up trusted publishing for the given packages |
| `fledgling sync` | Reconcile trusted publishing on npm with your config |
| `fledgling init` | Write the trusted-publishing config to your `package.json` |
## Why
Setting up a new npm package the modern way is more fiddly than it should be:
1. npm won't let you configure trusted publishing until the package **already exists** β so you have to publish *something* first.
2. Then you configure the trusted publisher **per package**, by hand, on the website.
3. In a monorepo, you do that **N times**.
`fledgling` does all of it: publishes a tiny placeholder to claim each name, then configures the trusted publisher for every package via npm's own [`npm trust`](https://docs.npmjs.com/cli/v11/commands/npm-trust/). It's **idempotent** β re-run it whenever you add a package and it only does what's missing.
## Quick start
```sh
npm login # needs 2FA enabled
npx fledgling # interactive: pick packages, confirm, apply
```
Prefer non-interactive (or in CI)?
```sh
npx fledgling --dry-run # print a plan, change nothing
npx fledgling --yes # apply: claim names + configure trusted publishing
```
Then add the matching publish step to your CI (e.g. a GitHub Actions job with `permissions: id-token: write` running `npm publish`). Your real releases now publish over OIDC β no token required.
> πΈ **Next step: [Bumpy](https://bumpy.varlock.dev)** handles exactly that publish step for you β it versions your packages, writes changelogs, and publishes over OIDC trusted publishing (no `NPM_TOKEN`). `fledgling` sets up the trust, Bumpy does the releasing β they pair perfectly. (This repo is released with it.)
## Configuration
**The recommended way to configure `fledgling` is a `"fledgling"` block in your root `package.json`.** Set it once and every run reads it β CLI flags are just per-run overrides. Create it interactively:
```sh
npx fledgling init
```
```jsonc
{
"fledgling": {
"provider": "github", // github | gitlab | circleci
"workflow": "release.yml", // the workflow whose job runs `npm publish`
"environment": "publish", // CI environment for the trusted publisher (optional)
"permissions": "publish" // publish | stage | both
}
}
```
**CircleCI** uses IDs instead of a workflow/repo:
```jsonc
{
"fledgling": {
"provider": "circleci",
"orgId": "β¦",
"projectId": "β¦",
"pipelineDefinitionId": "β¦",
"vcsOrigin": "github/owner/repo",
"contextIds": ["β¦"], // optional
"permissions": "publish"
}
}
```
Add `"registry"` to either block to target a non-default npm registry.
### Excluding packages
fledgling skips any package marked `"private": true`. To exclude **public** packages too β
internal-but-published things you never want it to claim or manage trust for β add an
`"ignore"` list of names or globs:
```jsonc
{
"fledgling": {
"ignore": ["@scope/internal-*", "playground"]
}
}
```
Ignored packages are invisible to fledgling: they're left out of `add`, `sync`, `"*"`
globs, and tab completion.
### Defaults
| Option | Default | Notes |
|--------|---------|-------|
| `provider` | `github` | also `gitlab`, `circleci` |
| `repo` | _auto-detected_ from git `origin` | override with `--repo` |
| `workflow` | `release.yml` | the workflow whose job publishes |
| `environment` | **none** | Optional and **unset by default** β the trusted publisher then isn't tied to a CI environment (it works, but adds no environment gate). Setting one (e.g. `publish`) is recommended for security, and `fledgling init` pre-fills it. |
| `permissions` | `publish` | `publish`, `stage` (held for 2FA approval), or `both` |
| `registry` | _your npm config_ | optional custom npm registry URL |
**CircleCI** uses `orgId`, `projectId`, `pipelineDefinitionId`, `vcsOrigin`, and optional `contextIds` instead of `repo`/`workflow`/`environment`.
Precedence is **CLI flag β `fledgling` config β built-in default**.
### Just want to claim names?
To skip trusted publishing entirely and only reserve package names, you can:
- pass **`--skip-trust`** for a single run,
- decline the wizard's "Set up trusted publishing?" prompt, or
- set **`"trust": false`** in your `fledgling` config to make it the default.
## Usage
```sh
npx fledgling add [packages...] [options]
```
With no package arguments, `add` targets **every public package** in your workspace. Pass names or globs to narrow it down:
```sh
npx fledgling add my-pkg --yes # one package
npx fledgling add "@scope/*" --yes # a glob (quote it)
npx fledgling add "*-plugin" --yes # all the plugins
npx fledgling add @scope/brand-new --new --yes # claim a name that doesn't exist locally yet
```
Running bare `npx fledgling` (no subcommand) in a terminal drops you into the same flow interactively.
### Run options
| Flag | Description |
|------|-------------|
| `-y, --yes` | Apply changes without prompting (default in a terminal is the interactive wizard) |
| `--dry-run` | Print a plan without prompting (non-interactive) |
| `--new` | Treat unmatched names as brand-new packages to claim (squat a name) |
| `--skip-publish` | Only set up trusted publishing |
| `--skip-trust` | Only claim names |
| `--force` | Replace an existing trusted publisher (revoke + re-create) |
| `--placeholder-version ` | Placeholder version (default: `0.0.0`) |
| `--tag ` | dist-tag for placeholders (default: `latest`) |
| `--otp ` | npm 2FA one-time password, used for every npm call this run |
| `--otp-secret ` | TOTP secret to generate 2FA codes from as needed (also `$FLEDGLING_OTP_SECRET`) |
### 2FA / one-time passwords
npm requires 2FA to claim names and configure trusted publishing. By default fledgling
just lets **npm handle it interactively** β it opens your browser to approve, and caches
that approval for ~5 minutes, so one approval covers the whole run. When prompted, tick
**"don't ask again for 5 minutes"** so it doesn't ask per-package.
For non-interactive runs (CI, scripts) there's no browser, so pass a code yourself:
- **`--otp `** β a single one-time password, reused for every npm call in the run.
- **`--otp-secret `** β your authenticator's TOTP secret (base32); fledgling
generates a fresh code for each npm call. Prefer the **`FLEDGLING_OTP_SECRET`** env var
over the flag so the secret doesn't land in your shell history or process list.
Pull credentials straight from a password manager β e.g. 1Password's CLI (`op`). Read the
**generated code** with `?attribute=otp` and pass it to `--otp`:
```sh
fledgling sync --otp "$(op read "op://Private/npm/Security/one-time password?attribute=otp")"
```
β¦or read the **secret itself** β the field's value with no attribute, an `otpauth://` URI β
into `FLEDGLING_OTP_SECRET`, and fledgling mints a fresh code for every npm call:
```sh
FLEDGLING_OTP_SECRET="$(op read "op://Private/npm/Security/one-time password")" fledgling sync
```
### Config flags
Better set once in `package.json` (see [Configuration](#configuration)); as flags they override the config for that run.
| Flag | Config key | Default |
|------|-----------|---------|
| `--provider
` | `provider` | `github` |
| `--permissions
` | `permissions` | `publish` |
| `--registry ` | `registry` | _npm config_ |
| `--repo ` | _(auto-detected)_ | git `origin` |
| `--workflow ` | `workflow` | `release.yml` |
| `--env ` | `environment` | none |
| `--org-id ` | `orgId` | _(circleci)_ |
| `--project-id ` | `projectId` | _(circleci)_ |
| `--pipeline-definition-id ` | `pipelineDefinitionId` | _(circleci)_ |
| `--vcs-origin ` | `vcsOrigin` | _(circleci)_ |
| `--context-id ` | `contextIds` | _(circleci, repeatable)_ |
## What it does, precisely
For each target package:
1. **Claim** β if the name isn't on npm yet, publish a `package.json`-only placeholder (`0.0.0`, no code) to reserve it.
2. **Trust** β if there's no trusted publisher configured, set one up for your CI provider via `npm trust` (or replace an existing one with `--force`). Supports **GitHub, GitLab, and CircleCI**, with every option `npm trust` accepts.
Both steps are skipped when already done. Placeholders are packed from a throwaway temp dir, so your real `package.json` files are never touched.
## `fledgling sync` β reconcile trusted publishing
Where the default command focuses on **new** packages (and hides already-published ones), `fledgling sync` reconciles **trusted publishing across every package** against your `fledgling` config.
It authenticates, reads each package's actual config on npm, and shows what's **not configured** or **out of sync** β with the exact difference (e.g. `environment publish β (none)`) β then asks before fixing it to match your config:
```sh
fledgling sync # auth, check, then confirm + apply
fledgling sync --yes # skip the confirm
fledgling sync "@scope/*" # a subset
```
Use it after changing your `fledgling` config, or to set up trust on packages that were published without it. (It uses the same config/flags as the main command.)
## Shell completions
`fledgling` ships tab-completion (via [`@bomb.sh/tab`](https://github.com/bombshell-dev/tab)) that completes package names and flags. Install it for your shell:
```sh
fledgling complete zsh >> ~/.zshrc
fledgling complete bash >> ~/.bashrc
fledgling complete fish > ~/.config/fish/completions/fledgling.fish
```
Then `fledgling ` completes the packages in your workspace.
## Requirements
- **Node** β₯ 18
- **npm** β₯ 11.15.0 (for `npm trust` + OIDC/staged publishing)
- `npm login` with **2FA enabled** for the trust step (npm requires it)
Supports **npm / yarn / bun** (`workspaces`) and **pnpm** (`pnpm-workspace.yaml`) monorepos, plus single-package repos.
## License
[MIT](./LICENSE) Β© DMNO Inc
---
fledgling is a creation of the team behind Varlock π§ββοΈ
Check it out for secure secret sorcery β get your keys out of plaintext!