An open API service indexing awesome lists of open source software.

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

Awesome Lists containing this project

README

          

# fledgling 🐣

[![npm version](https://img.shields.io/npm/v/fledgling?color=cb3837&logo=npm)](https://www.npmjs.com/package/fledgling)
[![npm downloads](https://img.shields.io/npm/dm/fledgling?color=cb3837&logo=npm)](https://www.npmjs.com/package/fledgling)
[![node](https://img.shields.io/node/v/fledgling)](https://www.npmjs.com/package/fledgling)
[![license](https://img.shields.io/npm/l/fledgling?color=blue)](./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.


fledgling claiming a new package in a monorepo and setting up trusted publishing

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 was created by Varlock



fledgling is a creation of the team behind Varlock πŸ§™β€β™‚οΈ

Check it out for secure secret sorcery β€” get your keys out of plaintext!