Skip to content

feat: bring the vland templates to the org standard, and fix vland init's git identity and hooks - #271

Merged
rqbazan merged 8 commits into
mainfrom
feat/vland-templates-org-standard
Oct 18, 2026
Merged

rqbazan merged 8 commits into
mainfrom
feat/vland-templates-org-standard

Conversation

@rqbazan

@rqbazan rqbazan commented Oct 17, 2026 •

Copy link
Copy Markdown
Member

Summary

The vland templates get the org standard (brand, dx, env, theme-toggle, www), plus two vland init fixes. Today a generated project can't even install: @rrlab/cli@^0.1.0 doesn't exist.

 vland/templates/{library,backend,monorepo}/
-├── .node-version
 ├── mise.toml            # + min_version 2026.9.16, pnpm 10.30.3, [settings] lockfile = true
+├── mise.lock            # node 24.21.0 + pnpm 10.30.3, 7 platforms, lockfile revision 3
+├── pnpm-workspace.yaml  # release-age cooldown, lefthook in onlyBuiltDependencies, sideEffectsCache: false
 ├── package.json         # @rrlab/cli ^1.3.4, changesets 3.0.3 + changelog-github 1.0.1, pnpm@10.30.3, engines node >=22.18.0
 ├── .changeset/config.json   # schema 4.0.1, format: false, commit: true, repo from visibility
 └── .github/workflows/
     ├── ci.yml           # checkout v7.0.1 (no credentials) → mise-action v4.3.0 → install --frozen-lockfile → rr check → test → build
+    ├── workflow-audit.yml   # zizmor 1.30.1
     └── release.yml      # library: changesets/action v2.1.2 as vland-bot (theme-toggle's job, minus previews)

engines.node goes to >=22.18.0, like the published packages in #272: rr loads run-run.config.mts with Node's type stripping, unflagged from 22.18.0. Every action is pinned by SHA, and dx's own workflow audit now runs zizmor from the repo root, which also covers vland/templates/*/.github/workflows (on main they have unpinned-uses, artipacked and excessive-permissions findings; here, none). The monorepo had no .github/; its new CI tests and builds with --affected against origin/main.

vland init (own commits, patch changesets):

 runInit
+  git init                        # before the install, so lefthook's postinstall finds the repo
   pnpm install
-  git init
   git add -A
-  GIT_AUTHOR_*/GIT_COMMITTER_* = vland <noreply@variable.land>   # outranked the user's git config
-  git commit
+  git -c user.name=vland …        # only for what `git config` can't resolve in the new repo
+  git commit --no-verify          # the hooks are for the user's commits, not the scaffold

Placeholders. The changelog repo comes from the visibility: withvariablelab/<name> for a public library, variableland/<name> for a private one and for every backend and monorepo. It was <name>/<name>, which theme-toggle had to fix by hand. The library README names the scoped package (pnpm add @variablelab/<name>).

Every generated file passes oxfmt and Biome, so rr check starts green with either plugin. oxfmt sorts package.json keys (vland now puts a private library's private after version, instead of at the end), aligns Markdown tables and wraps at 100 columns. Biome sorts imports. The READMEs say to narrow oxc to oxc({ only: ["lint", "format"] }), because oxc and ts both provide the type-check.

Why sideEffectsCache: false. Of these repos only dx has its git hooks installed. theme-toggle (generated by vland) and control have none, for three reasons:

  1. pnpm 10 skips lefthook's postinstall unless lefthook is in onlyBuiltDependencies.
  2. vland installed before git init.
  3. With pnpm's side-effects cache on, a lefthook already built in the store (on any machine with dx cloned) skips its postinstall.

This PR fixes all three. The cost: every time pnpm (re)links a dependency with an allowed build script, it reruns that script. Today that's only lefthook (and biome in the monorepo), so it's negligible. It grows if a project allows heavy native builds.

From variableland/control:

Adopted Not adopted, and why
Tools only through mise jdx/mise-action@v4, checkout@v5: not pinned by SHA (zizmor policy)
timeout-minutes, concurrency, emoji step names pnpm 11, devEngines, allowBuilds: the org is on pnpm 10.30.3 + packageManager
Monorepo: fetch-depth: 0 + TURBO_SCM_BASE: origin/main + --affected GCP registry-auth and the pnpm-store cache composites: no org repo needs them
node --watch --env-file-if-exists=.env instead of tsx: nothing loaded .env before lefthook remotes: they point at variableland/dx (the old org) and need the network; decision 004 inlines the config
catalog:, oxc typecheck by default: the templates ship plugins: [], which is the user's decision
[tasks.setup]: for a new project it would only be pnpm install

mise.lock: shipped in the template, not generated at init, for now. Generating it at init is viable: mise lock takes ~1.5 s, writes all 7 platforms and, with mise 2026.10, locks only the project's tools (not global or parent ones). It would get a fresh node patch and a lock revision that matches the user's mise, with no min_version. Against it: init would need mise on PATH, a trust env for the new dir, and a fallback when mise is missing. I propose it as a follow-up.

Changesets. minor for the templates: vland init's interface doesn't change, but the generated project needs new things (mise ≥ 2026.9.16, the VLAND_BOT_CLIENT_ID variable and VLAND_BOT_PRIVATE_KEY secret) and behaves differently (pnpm changeset commits). The two init fixes are patch.

Evidence

  • Before: vland init my-lib -t library → pnpm install → ERR_PNPM_NO_MATCHING_VERSION No matching version found for @rrlab/cli@^0.1.0. mise install --locked fails: there is no lock.
    After, each template generated with the local vland, under mise isolated from my global config (MISE_CONFIG_DIR/MISE_GLOBAL_CONFIG_FILE on an empty config):

    • mise install --locked installs node 24.21.0 and pnpm 10.30.3 into an empty MISE_DATA_DIR (macOS arm64; the linux-x64 entries come from mise's metadata, not a run);
    • pnpm install, the README's rr plugins add … --yes and rr jsc --fix (see Merge Danger), then rr check, pnpm test and rr pack (pnpm build in the monorepo) pass; pnpm test --affected works;
    • the backend's pnpm dev answers {"ok":true} on the port set in .env;
    • zizmor 1.30.1 with online audits: no findings. The one suppressed is theme-toggle's too (secrets-outside-env on the bot key, auditor persona);
    • git hooks installed in all 6 projects of the plugin matrix;
    • with fix: make rr plugins add work without a TTY and leave a project rr check passes #276's rr installed through pnpm overrides: in all 6 projects (library, backend and monorepo, each with biome or with oxc), every rr plugins add without --yes and without a TTY, then rr check, rr test, and rr pack or pnpm build, exit 0. In the monorepo that includes ts and tsdown per workspace, with apps/web on the inferred React preset.
  • Tests (vland/cli/test/integration/), red on main, green here:

    init-templates  ×4  every file passes oxfmt and Biome (library, private library, backend, monorepo)
                    ×3  pins node and pnpm with mise and a lockfile
                    ×3  pins every workflow action by SHA and checks out without credentials
                    changelog repo: withvariablelab/<lib> | variableland/<private lib> | variableland/<mono>
    init-git        commits as the user's git identity     (old code: "vland <noreply@variable.land>")
                    falls back to the vland identity when git has none
                    runs git init before the install, and the first commit skips the hooks it set up
    

    The format test uses the dx workspace's oxfmt and Biome. A bump that changes their output makes it fail, and the fix is to reformat the templates.

    With my real git config, the init commit is mine and carries an SSH signature.

  • Not verified: the backend's docker build (no daemon here), the release workflow end to end, and whether a private library's repo in variableland has vland-bot's variable and secret. That last one is an assumption.

Merge Danger

Door: two-way

Blast Radius: every vland init

vland downloads the templates from withvariablelab/dx#main on every init, so this reaches every installed vland the moment it merges, before any release. With the published vland 0.3.7, {{githubOwner}} and {{packageName}} stay literal in .changeset/config.json and the library README. Merge, then release vland in the same sitting.

Until #276 (it fixes rr plugins add) is released: without a TTY, rr plugins add biome|ts|tsdown needs --yes (otherwise it waits on a prompt and exits 13), and rr check fails after rr plugins add biome until rr jsc --fix. The READMEs already tell agents to pass --yes.

Out of scope, for later: the monorepo has changesets but no release path, and the ts plugin rewrites @types/node to >=20.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Tj9MBgi2cQcRxSD9uikx6k

@changeset-bot

changeset-bot Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: a64042e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@variablelab/vland Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vland-bot

vland-bot Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Preview release

Latest commit: a64042e

Some packages have been released:

Package Version Install
@variablelab/vland 0.3.8-git-a64042e.0 @variablelab/vland@0.3.8-git-a64042e.0

Note

Use the PR number as tag to install any package. For instance:

pnpm add @variablelab/vland@pr-271

rqbazan and others added 7 commits October 17, 2026 19:03
vland init forced GIT_AUTHOR_* and GIT_COMMITTER_* to vland <noreply@variable.land>,
and those env vars outrank git config. The first commit never carried the user's
identity, and a signed one showed as "Unverified" on GitHub.

Read user.name and user.email in the new repo after git init, so includeIf
identities apply, and pass -c only for the half git can't resolve.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tj9MBgi2cQcRxSD9uikx6k
vland init installed dependencies before git init, so lefthook's postinstall
found no repo and set up no hooks: theme-toggle, generated by vland, never ran
its lefthook.yml.

Run git init before the install, and make the scaffold commit with --no-verify,
since the hooks are for the user's commits, not the template as generated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tj9MBgi2cQcRxSD9uikx6k
…standard

The templates still had setup-node, .node-version, changesets 2, checkout v4
and tag-pinned actions, and `@rrlab/cli@^0.1.0`, which doesn't exist, so a
generated project couldn't even install.

- mise.toml with `[settings] lockfile = true` and a mise.lock (revision 3,
  hence `min_version = "2026.9.16"`); no .node-version.
- CI on checkout v7.0.1 without credentials, mise-action v4.3.0 and
  `pnpm install --frozen-lockfile`, then `rr check`, tests and build. The
  monorepo, which had no workflows, gets one with turbo's --affected.
- workflow-audit.yml runs zizmor 1.30.1; every action is pinned by SHA.
- changesets 3.0.3, changelog-github 1.0.1, schema 4.0.1, `format: false`,
  `commit: true`. The library releases with changesets/action v2.1.2 as
  vland-bot, like theme-toggle, without the preview jobs.
- The changelog repo comes from the visibility (`withvariablelab/<name>`
  public, `variableland/<name>` private) instead of `<name>/<name>`.
- pnpm-workspace.yaml: the org's release-age cooldown, lefthook in
  onlyBuiltDependencies and `sideEffectsCache: false`, so lefthook's
  postinstall installs the git hooks even when its build is cached.
- From control: `node --watch --env-file-if-exists=.env` instead of tsx.
- Fixes on the way: the library's publishConfig exports point at the
  .mjs/.d.mts that rr pack builds, the library README names the scoped
  package, and the monorepo app READMEs filter by the real package names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tj9MBgi2cQcRxSD9uikx6k
rr loads run-run.config.mts with Node's built-in type stripping, unflagged
from 22.18.0, so a generated project can't run rr on Node 20. Matches #272,
which raises the floor of the published packages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tj9MBgi2cQcRxSD9uikx6k
With the oxc plugin, `rr check` runs `oxfmt --check`, which sorts package.json
keys, so it failed on every generated package.json but the web app's. And
vland appended a private library's `private: true` at the end.

Reorder the templates' package.json files the way oxfmt writes them, which
Biome accepts too, and set `private` right after `version`, mutating the
object so pkg-types keeps the file's formatting.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tj9MBgi2cQcRxSD9uikx6k
With `rr plugins add oxc`, `rr check` failed on the backend and monorepo
READMEs (oxfmt aligns Markdown tables) and on apps/api's health route (oxfmt
breaks at 100 columns, Biome joins up to 120). With `biome`, it failed on the
web app's import order.

Align the tables, give the route a block body both formatters agree on, and
sort App.tsx's imports. The init-templates test now runs oxfmt and Biome over
every generated project. The READMEs say to narrow oxc to lint and format,
since it and `ts` both provide the type-check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tj9MBgi2cQcRxSD9uikx6k
The audit scanned only .github/, so the workflows vland/templates/* generate
were never checked: on main they have unpinned-uses, artipacked and
excessive-permissions findings. Scan the repo root, like theme-toggle does;
zizmor finds every .github/workflows under it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tj9MBgi2cQcRxSD9uikx6k
@rqbazan
rqbazan merged commit 03b1373 into main Oct 18, 2026
10 checks passed
@rqbazan
rqbazan deleted the feat/vland-templates-org-standard branch October 18, 2026 04:10
@vland-bot vland-bot Bot mentioned this pull request Oct 18, 2026
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.

1 participant