Set up a repository
Generate a repository from a starter and complete the one-time settings that files cannot hold.
Files set up most of the standard. The settings below live in GitHub, npm and
Vercel, so a person does them once per repository. Agents can run the gh
commands; the npm and Vercel steps need the maintainer's own login.
Replace OWNER/REPO with the repository, for example lupinum-dev/nuxt-tour.
Generate the repository
Pick a starter in starters/ of lupinum-dev/lupinum-oss: nuxt-module (a
Nuxt module, the usual case), library (one framework-free package) or
library-monorepo (several packages that release together in one Changesets
fixed group, for example a Vue package and its Nuxt module). From a
lupinum-oss checkout:
node starters/nuxt-module/setup.mjs --output ../nuxt-example --name nuxt-example \
--title "Nuxt Example" --description "One sentence." \
--repository lupinum-dev/nuxt-example --domain nuxt-example.lupinum.com
cd ../nuxt-example
pnpm install
pnpm verify--package defaults to @lupinum/<name>; --plausible SCRIPT_ID adds Plausible
analytics to the site. The monorepo starter takes --package once per package
and --primary for the package the docs lead with. --help prints each
starter's options.
Commit pnpm-lock.yaml. Fill in the library-specific parts of AGENTS.md and
check D1 in internals/decisions.md. For a library, create the empty GitHub
repository (gh repo create OWNER/REPO --public) and the npm environment
(below) before you push the first commit to main: that push already packs
the starting version and waits for approval in npm (see
First publication). If
the environment does not exist yet, GitHub creates it without a reviewer. Add
the ruleset after the first push.
Adopt the standard in an existing repository
Follow Migrate a repository. It uses the settings on this page in a fixed order.
GitHub settings
Merge settings: squash only, delete branches after merge, auto-merge off. The
ruleset asks for no approvals, so auto-merge would let any token that can open
a pull request merge it once ci is green.
gh repo edit OWNER/REPO --enable-squash-merge --enable-merge-commit=false \
--enable-rebase-merge=false --enable-auto-merge=false --delete-branch-on-mergeActions token: read-only by default. A library also lets Actions create pull
requests, so release.yml can open the Version packages pull request. A
repository that publishes nothing leaves that off
(can_approve_pull_request_reviews=false).
gh api -X PUT repos/OWNER/REPO/actions/permissions/workflow \
-f default_workflow_permissions=read -F can_approve_pull_request_reviews=trueDependabot security updates off: Renovate opens every update pull request. Dependabot alerts stay on (see below).
gh api -X DELETE repos/OWNER/REPO/automated-security-fixesRuleset for main: pull request required, required check ci (only from
GitHub Actions, app ID 15368, so no other app can fake it), no force push, no
deletion, linear history, squash only, review threads resolved before merge.
"Require branches to be up to date" stays off (strict_required_status_checks_policy: false): ci runs again on main after every merge, and the Version packages
pull request would otherwise need a manual update after each merge.
gh api -X POST repos/OWNER/REPO/rulesets --input - <<'EOF'
{
"name": "main",
"target": "branch",
"enforcement": "active",
"conditions": { "ref_name": { "include": ["~DEFAULT_BRANCH"], "exclude": [] } },
"rules": [
{ "type": "deletion" },
{ "type": "non_fast_forward" },
{ "type": "required_linear_history" },
{ "type": "pull_request", "parameters": {
"required_approving_review_count": 0,
"dismiss_stale_reviews_on_push": false,
"require_code_owner_review": false,
"require_last_push_approval": false,
"required_review_thread_resolution": true,
"allowed_merge_methods": ["squash"] } },
{ "type": "required_status_checks", "parameters": {
"strict_required_status_checks_policy": false,
"required_status_checks": [{ "context": "ci", "integration_id": 15368 }] } }
]
}
EOFFor a library, a second ruleset protects every tag: once release.yml has
created a tag, nobody can move or delete it. It covers all tags, so it fits
every tag format (v1.2.0, @scope/name@1.2.0) without a pattern to get
wrong. The audit fails a library without it or with bypass actors on it.
gh api -X POST repos/OWNER/REPO/rulesets --input - <<'EOF'
{
"name": "tags",
"target": "tag",
"enforcement": "active",
"conditions": { "ref_name": { "include": ["~ALL"], "exclude": [] } },
"rules": [{ "type": "update" }, { "type": "deletion" }]
}
EOFA library also needs the npm environment: the maintainer must approve every
deployment, administrators cannot bypass that, and only main can deploy.
Self-review stays allowed because the maintainer both merges the Version
packages pull request and approves the publish.
gh api -X PUT repos/OWNER/REPO/environments/npm --input - <<EOF
{
"reviewers": [{ "type": "User", "id": $(gh api user --jq .id) }],
"prevent_self_review": false,
"can_admins_bypass": false,
"deployment_branch_policy": { "protected_branches": false, "custom_branch_policies": true }
}
EOF
gh api -X POST repos/OWNER/REPO/environments/npm/deployment-branch-policies \
-f name=main -f type=branch
gh api repos/OWNER/REPO/environments/npm --jq .can_admins_bypass # must print falseIf it prints true, turn off Allow administrators to bypass configured
protection rules under Settings → Environments → npm.
Security features: secret scanning with push protection, private vulnerability reporting, Dependabot alerts (alerts only; Renovate opens the update pull requests) and CodeQL default setup. Do not add a CodeQL workflow file. Add no Actions secrets: the standard needs none, and the audit warns about any that exist.
gh api -X PATCH repos/OWNER/REPO --input - <<'EOF'
{ "security_and_analysis": {
"secret_scanning": { "status": "enabled" },
"secret_scanning_push_protection": { "status": "enabled" } } }
EOF
gh api -X PUT repos/OWNER/REPO/private-vulnerability-reporting
gh api -X PUT repos/OWNER/REPO/vulnerability-alerts
gh api -X PATCH repos/OWNER/REPO/code-scanning/default-setup -f state=configuredInstall the Renovate GitHub App on the
repository, and for libraries the pkg.pr.new
app, which preview.yml needs. Renovate installed for all repositories of an
organization starts in Silent mode: it opens no pull requests and no
Dependency Dashboard. Turn Silent mode off for the organization at
developer.mend.io (Settings → Dependency
Updates). The audit fails GH-08 until the Dependency Dashboard issue exists.
A repository that publishes nothing, such as lupinum-oss, needs the GitHub
settings above except the library-only steps: leave pull request creation by
Actions off (can_approve_pull_request_reviews=false), and do not create the
tag ruleset or the npm environment. Skip the npm settings below.
npm settings
For a library, do this for every package, after its first version exists on npm. For a brand-new package, first follow First publication of a new package.
- On npmjs.com, open the package, then Settings.
- Under Trusted publishing, add a GitHub Actions publisher: organization or
user
OWNER, repositoryREPO, workflowrelease.yml, environmentnpm, allowed to publish directly (not only to stage a version). - Under Publishing access, select Require two-factor authentication and disallow tokens. Trusted publishing keeps working; stolen tokens do not.
- Record the result as NPM-01 evidence in
fleet/libraries.jsonin lupinum-oss, with the date.
The CLI equivalent for step 2 needs an npm version that has npm trust (check with npm trust --help; Node.js 24 ships one):
npm trust github @lupinum/PACKAGE --repo OWNER/REPO --file release.yml --env npm --allow-publishNever create an NPM_TOKEN secret.
Vercel
Only for a library with a docs site (see Write library docs). Import the repository in Vercel through its Git integration:
- Root Directory:
docs. - Include files outside the Root Directory in the Build Step: on, because the docs build uses the repository's pnpm workspace.
- Build command, output and the ignore command come from
docs/vercel.json. The ignore command builds only when the docs or the workspace manifests changed since the branch's last deployment, so most pull requests do not use build minutes. - Add the production domain.
There is no Vercel token in GitHub and no custom preview workflow.
A second maintainer
The standard assumes one maintainer: nobody else can review, so the release summary before the npm approval replaces a second reviewer. When a second person becomes a maintainer, add for every library:
.github/CODEOWNERSwith both maintainers for/.github/and/scripts/release.mjs, and"require_code_owner_review": truein themainruleset. A change to the release machinery then needs the other person's approval.- The second maintainer as a required reviewer of the
npmenvironment. - The second maintainer as an owner of every npm package (with 2FA) and of the GitHub organization, so one lost account no longer stops releases.
Check the result
From a lupinum-oss checkout:
pnpm audit:repos OWNER/REPOFix every FAIL. Read every WARN: it is either excess tooling to remove, or
a setting the audit could not read with your access. Add the repository to
fleet/libraries.json in lupinum-oss.
The audit cannot see the npm Publishing access setting. Check it by hand once, after you set it.