@regardio/dev 2.4.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as git, c as runScript, i as getWorkspacePackages, n as chooseForEach, o as gitRead, s as runQualityChecks, t as branchExists } from "./utils-rnvdLrkF.mjs";
2
+ import { a as git, c as runQualityChecks, i as getWorkspacePackages, n as chooseForEach, o as gitRead, s as runPackageRelease, t as branchExists } from "./utils-BPxpSgv6.mjs";
3
3
  //#region src/bin/ship/hotfix.ts
4
4
  /**
5
5
  * ship-hotfix: Manage hotfix branches based on production code.
@@ -52,6 +52,8 @@ function runShipHotfix(subcommand, subArgs, cwd = process.cwd()) {
52
52
  console.error("Working directory has uncommitted changes. Commit or stash them first.");
53
53
  process.exit(1);
54
54
  }
55
+ console.log("\nFetching latest state from origin...");
56
+ git("fetch", "origin");
55
57
  console.log("\nRunning quality checks...");
56
58
  try {
57
59
  runQualityChecks();
@@ -61,29 +63,21 @@ function runShipHotfix(subcommand, subArgs, cwd = process.cwd()) {
61
63
  }
62
64
  console.log("✅ Quality checks passed");
63
65
  const packages = getWorkspacePackages(cwd);
64
- if (packages.length === 0) {
65
- console.error("No publishable workspace packages found.");
66
- process.exit(1);
67
- }
68
- console.log("\nSelect a version bump for each package (or skip):");
69
- const bumps = chooseForEach(packages);
70
- if (bumps.length === 0) {
71
- console.log("\nNo packages selected for release. Aborted.");
72
- process.exit(0);
73
- }
74
- console.log("\nBumping versions and updating CHANGELOGs...");
75
- for (const { package: pkg, bump } of bumps) {
76
- const originalCwd = process.cwd();
77
- process.chdir(pkg.path);
78
- try {
79
- runScript(`--filter-root release:${bump}`);
66
+ const bumps = [];
67
+ if (packages.length > 0) {
68
+ console.log("\nSelect a version bump for each package (or skip):");
69
+ bumps.push(...chooseForEach(packages));
70
+ if (bumps.length === 0) {
71
+ console.log("\nNo packages selected for release. Aborted.");
72
+ process.exit(0);
73
+ }
74
+ console.log("\nBumping versions and updating CHANGELOGs...");
75
+ for (const { package: pkg, bump } of bumps) try {
76
+ runPackageRelease(pkg.path, bump);
80
77
  } catch {
81
- runScript(`commit-and-tag-version ${bump === "patch" ? "" : `--release-as ${bump}`}`);
78
+ console.log(`No versioning configured for ${pkg.name} — skipping.`);
82
79
  }
83
- process.chdir(originalCwd);
84
- }
85
- console.log("\nFetching latest state from origin...");
86
- git("fetch", "origin");
80
+ } else console.log("No publishable packages — skipping versioning.");
87
81
  console.log("\nMerging hotfix into production...");
88
82
  git("checkout", "production");
89
83
  git("pull", "--ff-only", "origin", "production");
@@ -103,9 +97,11 @@ function runShipHotfix(subcommand, subArgs, cwd = process.cwd()) {
103
97
  try {
104
98
  git("push", "origin", "--delete", currentBranch);
105
99
  } catch {}
106
- const shipped = bumps.map((b) => b.package.name).join(", ");
107
- console.log(`\n✅ Hotfix ${shipped} shipped to production staging → main`);
108
- console.log("CI will publish changed packages to npm.");
100
+ if (bumps.length > 0) {
101
+ const shipped = bumps.map((b) => b.package.name).join(", ");
102
+ console.log(`\n✅ Hotfix ${shipped} shipped to production → staging → main`);
103
+ console.log("CI will publish changed packages to npm.");
104
+ } else console.log("\n✅ Hotfix shipped to production → staging → main.");
109
105
  console.log("You are on main and ready to keep working.");
110
106
  process.exit(0);
111
107
  }
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as git, c as runScript, i as getWorkspacePackages, n as chooseForEach, o as gitRead, r as confirm, s as runQualityChecks, t as branchExists } from "./utils-rnvdLrkF.mjs";
2
+ import { a as git, c as runQualityChecks, i as getWorkspacePackages, n as chooseForEach, o as gitRead, r as confirm, s as runPackageRelease, t as branchExists } from "./utils-BPxpSgv6.mjs";
3
3
  //#region src/bin/ship/production.ts
4
4
  /**
5
5
  * ship-production: Promote main to production following the GitLab workflow.
@@ -42,21 +42,6 @@ function runShipProduction(cwd = process.cwd()) {
42
42
  }
43
43
  console.log("\nCommits to be shipped to production:");
44
44
  console.log(ahead);
45
- const packages = getWorkspacePackages(cwd);
46
- if (packages.length === 0) {
47
- console.error("No publishable workspace packages found.");
48
- process.exit(1);
49
- }
50
- console.log("\nSelect a version bump for each package (or skip):");
51
- const bumps = chooseForEach(packages);
52
- if (bumps.length === 0) {
53
- console.log("\nNo packages selected for release. Aborted.");
54
- process.exit(0);
55
- }
56
- if (!confirm(`\nShip to production?\n${bumps.map((b) => ` ${b.package.name} → ${b.bump}`).join("\n")}\n`)) {
57
- console.log("Aborted.");
58
- process.exit(0);
59
- }
60
45
  console.log("\nRunning quality checks on main...");
61
46
  try {
62
47
  runQualityChecks();
@@ -65,17 +50,26 @@ function runShipProduction(cwd = process.cwd()) {
65
50
  process.exit(1);
66
51
  }
67
52
  console.log("✅ Quality checks passed");
68
- console.log("\nBumping versions and updating CHANGELOGs...");
69
- for (const { package: pkg, bump } of bumps) {
70
- const originalCwd = process.cwd();
71
- process.chdir(pkg.path);
72
- try {
73
- runScript(`--filter-root release:${bump}`);
53
+ const packages = getWorkspacePackages(cwd);
54
+ const bumps = [];
55
+ if (packages.length > 0) {
56
+ console.log("\nSelect a version bump for each package (or skip):");
57
+ bumps.push(...chooseForEach(packages));
58
+ if (bumps.length === 0) {
59
+ console.log("\nNo packages selected for release. Aborted.");
60
+ process.exit(0);
61
+ }
62
+ if (!confirm(`\nShip to production?\n${bumps.map((b) => ` ${b.package.name} → ${b.bump}`).join("\n")}\n`)) {
63
+ console.log("Aborted.");
64
+ process.exit(0);
65
+ }
66
+ console.log("\nBumping versions and updating CHANGELOGs...");
67
+ for (const { package: pkg, bump } of bumps) try {
68
+ runPackageRelease(pkg.path, bump);
74
69
  } catch {
75
- runScript(`commit-and-tag-version ${bump === "patch" ? "" : `--release-as ${bump}`}`);
70
+ console.log(`No versioning configured for ${pkg.name} — skipping.`);
76
71
  }
77
- process.chdir(originalCwd);
78
- }
72
+ } else if (!confirm("\nNo publishable packages — ship commits to production?\n")) process.exit(0);
79
73
  console.log("\nMerging main into production...");
80
74
  git("checkout", "production");
81
75
  git("pull", "--ff-only", "origin", "production");
@@ -88,8 +82,10 @@ function runShipProduction(cwd = process.cwd()) {
88
82
  git("push", "origin", "staging");
89
83
  git("checkout", "main");
90
84
  git("push", "--follow-tags", "origin", "main");
91
- const shipped = bumps.map((b) => b.package.name).join(", ");
92
- console.log(`\n✅ Shipped ${shipped} to production. CI will publish changed packages to npm.`);
85
+ if (bumps.length > 0) {
86
+ const shipped = bumps.map((b) => b.package.name).join(", ");
87
+ console.log(`\n✅ Shipped ${shipped} to production. CI will publish changed packages to npm.`);
88
+ } else console.log("\n✅ Commits shipped to production.");
93
89
  console.log("You are on main and ready to keep working.");
94
90
  }
95
91
  //#endregion
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as git, o as gitRead, s as runQualityChecks, t as branchExists } from "./utils-rnvdLrkF.mjs";
2
+ import { a as git, c as runQualityChecks, o as gitRead, t as branchExists } from "./utils-BPxpSgv6.mjs";
3
3
  import { existsSync, readFileSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  //#region src/bin/ship/staging.ts
@@ -19,6 +19,13 @@ const runScript = (script) => {
19
19
  console.log(`$ pnpm ${script}`);
20
20
  execSync(`pnpm ${script}`, { stdio: "inherit" });
21
21
  };
22
+ const runPackageRelease = (pkgPath, bump) => {
23
+ console.log(`$ commit-and-tag-version --release-as ${bump}`);
24
+ execSync(`pnpm exec commit-and-tag-version --release-as ${bump}`, {
25
+ cwd: pkgPath,
26
+ stdio: "inherit"
27
+ });
28
+ };
22
29
  const runQualityChecks = () => {
23
30
  runScript("lint");
24
31
  runScript("typecheck");
@@ -120,4 +127,4 @@ const choose = (prompt, options, ttyPath = "/dev/tty") => {
120
127
  return chosen.value;
121
128
  };
122
129
  //#endregion
123
- export { git as a, runScript as c, getWorkspacePackages as i, chooseForEach as n, gitRead as o, confirm as r, runQualityChecks as s, branchExists as t };
130
+ export { git as a, runQualityChecks as c, getWorkspacePackages as i, chooseForEach as n, gitRead as o, confirm as r, runPackageRelease as s, branchExists as t };
@@ -10,9 +10,9 @@ interface BuildPlaywrightBaseConfigParams {
10
10
  webServerCommand: string;
11
11
  }
12
12
  /**
13
- * Build a base Playwright config object with Regardio defaults.
14
- * Consumers should wrap with defineConfig() in their local playwright.config.ts
15
- */
13
+ * Build a base Playwright config object with Regardio defaults.
14
+ * Consumers should wrap with defineConfig() in their local playwright.config.ts
15
+ */
16
16
  declare function buildPlaywrightBaseConfig({
17
17
  appUrl,
18
18
  appPort,
@@ -1,11 +1,11 @@
1
1
  //#region src/vitest/node.d.ts
2
2
  /**
3
- * Base Vitest configuration for Node.js packages.
4
- * Use with defineConfig() in your vitest.config.ts
5
- */
3
+ * Base Vitest configuration for Node.js packages.
4
+ * Use with defineConfig() in your vitest.config.ts
5
+ */
6
6
  declare const vitestNodeConfig: {
7
7
  coverage: {
8
- provider: "v8";
8
+ provider: 'v8';
9
9
  thresholds: {
10
10
  branches: number;
11
11
  functions: number;
@@ -1,11 +1,11 @@
1
1
  //#region src/vitest/react.d.ts
2
2
  /**
3
- * Vitest configuration for React packages with jsdom environment.
4
- * Use with defineConfig() in your vitest.config.ts
5
- */
3
+ * Vitest configuration for React packages with jsdom environment.
4
+ * Use with defineConfig() in your vitest.config.ts
5
+ */
6
6
  declare const vitestReactConfig: {
7
7
  coverage: {
8
- provider: "v8";
8
+ provider: 'v8';
9
9
  thresholds: {
10
10
  branches: number;
11
11
  functions: number;
@@ -11,7 +11,7 @@ area: "dev"
11
11
 
12
12
  # Biome
13
13
 
14
- [Biome](https://biomejs.dev/) is the linter and formatter every Regardio package reaches for. Configuration is centralised through `@regardio/dev`, and wrapper commands keep every package behaving the same way.
14
+ [Biome](https://biomejs.dev/) is the linter and formatter every Regardio package reaches for. Configuration is centralised through `@regardio/dev`.
15
15
 
16
16
  ## Configuration
17
17
 
@@ -29,34 +29,22 @@ A `biome.jsonc` at the package root is all it takes:
29
29
  ```json
30
30
  {
31
31
  "scripts": {
32
- "fix": "exec-s fix:pkg fix:md fix:biome",
33
- "fix:biome": "lint-biome check --write --unsafe .",
34
- "lint": "exec-s lint:md lint:biome",
35
- "lint:biome": "lint-biome check ."
32
+ "fix:biome": "biome check --write --unsafe .",
33
+ "lint:biome": "biome check ."
36
34
  }
37
35
  }
38
36
  ```
39
37
 
40
- ## CLI wrapper
41
-
42
- Use `lint-biome` rather than `biome` directly — the wrapper keeps behaviour consistent across the monorepo.
43
-
44
- ```bash
45
- lint-biome check . # Check for issues
46
- lint-biome check --write . # Fix the auto-fixable
47
- lint-biome format . # Format only
48
- ```
49
-
50
38
  ## `package.json` handling
51
39
 
52
- The Biome preset leaves `package.json` alone. For those files, `lint-package` does the work:
40
+ The Biome preset leaves `package.json` alone. For those files, use `sort-package-json`:
53
41
 
54
42
  1. Sorts `package.json` using the well-known field order
55
43
  2. Fixes export-condition order (`types` before `default` for TypeScript)
56
44
 
57
45
  ```bash
58
- lint-package # Sort package.json in current directory
59
- lint-package path/to/pkg # Sort a specific package.json
46
+ pnpm sort-package-json # Sort package.json in current directory
47
+ pnpm sort-package-json path/to/pkg # Sort a specific package.json
60
48
  ```
61
49
 
62
50
  It runs automatically as part of `pnpm fix`.
@@ -20,7 +20,7 @@ Husky configures itself through the `prepare` script:
20
20
  ```json
21
21
  {
22
22
  "scripts": {
23
- "prepare": "exec-husky"
23
+ "prepare": "husky"
24
24
  }
25
25
  }
26
26
  ```
@@ -35,7 +35,8 @@ Validates commit messages against the conventional-commit format:
35
35
 
36
36
  ```bash
37
37
  #!/bin/sh
38
- pnpm lint-commit --edit $1
38
+ . "$(dirname "$0")/_/husky.sh"
39
+ commitlint --edit $1
39
40
  ```
40
41
 
41
42
  ### `pre-commit` (optional)
@@ -47,10 +48,6 @@ Runs linting before a commit lands:
47
48
  pnpm lint
48
49
  ```
49
50
 
50
- ## CLI wrapper
51
-
52
- `exec-husky` takes the place of `husky` directly — the wrapper handles monorepo scenarios that bare Husky doesn't.
53
-
54
51
  ## Bypassing hooks
55
52
 
56
53
  In the rare case where a hook truly has to be skipped:
@@ -11,7 +11,7 @@ area: "dev"
11
11
 
12
12
  # Markdownlint
13
13
 
14
- Markdownlint checks Markdown structure and formatting. The rules live in `@regardio/dev`; projects reach for the `lint-md` wrapper rather than calling the tool directly.
14
+ Markdownlint checks Markdown structure and formatting. The rules live in `@regardio/dev`.
15
15
 
16
16
  ## Configuration
17
17
 
@@ -28,21 +28,12 @@ A `.markdownlint.json` at the package root:
28
28
  ```json
29
29
  {
30
30
  "scripts": {
31
- "fix:md": "lint-md --fix \"**/*.md\" \"**/*.mdx\" \"!**/node_modules/**\" \"!**/dist/**\"",
32
- "lint:md": "lint-md \"**/*.md\" \"**/*.mdx\" \"!**/node_modules/**\" \"!**/dist/**\""
31
+ "fix:md": "markdownlint-cli2 --fix \"**/*.md\" \"**/*.mdx\" \"!**/node_modules/**\" \"!**/dist/**\"",
32
+ "lint:md": "markdownlint-cli2 \"**/*.md\" \"**/*.mdx\" \"!**/node_modules/**\" \"!**/dist/**\""
33
33
  }
34
34
  }
35
35
  ```
36
36
 
37
- ## CLI wrapper
38
-
39
- Use `lint-md` rather than `markdownlint-cli2` directly:
40
-
41
- ```bash
42
- lint-md "**/*.md" # Check all Markdown files
43
- lint-md --fix "**/*.md" # Auto-fix what can be auto-fixed
44
- ```
45
-
46
37
  ## Key rules
47
38
 
48
39
  | Rule | Description |
@@ -1,8 +1,8 @@
1
1
  ---
2
2
 
3
3
  title: "Release Workflow"
4
- description: "Branch-based release workflow for Regardio packagesmain staging production, driven by commit-and-tag-version."
5
- publishedAt: 2026-04-17
4
+ description: "GitLab-flow release workflow for Regardio reposbranches mirror environments, ship tooling drives versioning, CI carries it out."
5
+ publishedAt: 2026-05-07
6
6
  language: "en"
7
7
  status: "published"
8
8
  kind: "guide"
@@ -11,114 +11,92 @@ area: "dev"
11
11
 
12
12
  # Release Workflow
13
13
 
14
- Branch-based release workflow for Regardio packages. `main` `staging` `production`. `commit-and-tag-version` reads conventional commits to determine the version bump; quality gates run locally before any branch promotion. Nothing broken reaches a shared branch.
14
+ Branches mirror environments. `main` is where development happens. `staging` reflects what is deployed to the staging server. `production` reflects what is live. Version bumps happen locally at ship time CI on `production` carries the result out (publishes to npm or deploys to a server).
15
15
 
16
- ## Overview
16
+ ## The Three-Branch Model
17
17
 
18
- Branches mirror deployment environments:
18
+ ```text
19
+ main ──── ship:staging ────► staging (optional staging server)
20
+
21
+ └──── ship:production ──► production (live / npm)
22
+
23
+ sync back to staging
24
+ ```
25
+
26
+ | Branch | Contains | CI role |
27
+ |--------|----------|---------|
28
+ | `main` | All committed, tested work | None (Husky runs local gates on commit) |
29
+ | `staging` | Deployed to staging server or preview | Verify + deploy to staging |
30
+ | `production` | Only shipped, versioned releases | Verify + deploy or publish |
31
+
32
+ **Why version bumps happen locally, not in CI.** Hotfixes start from `production` and must write their version bump back up through `staging` and `main` in one pass. Keeping the bump local means both the normal ship path and the hotfix path behave identically — there is no special case in CI.
33
+
34
+ ## Hotfix Flow
35
+
36
+ A hotfix is an urgent fix that must go to production immediately, without pulling in unreleased changes from `main`.
19
37
 
20
38
  ```text
21
- main staging (optional) production
39
+ production ──► hotfix/<name> (fix here with conventional commits)
40
+
41
+ └──► production ──► staging ──► main
22
42
  ```
23
43
 
24
- - **`main`** active development, always deployable. Version bumps happen at ship time, not per-commit.
25
- - **`staging`** — optional validation environment. No versioning happens here.
26
- - **`production`** — versioned, published code only.
44
+ `ship:hotfix start <name>` creates the branch from `production`, not from `main`. This is deliberate: it avoids accidentally shipping work-in-progress that has not yet been reviewed for production.
27
45
 
28
- When `ship-production` runs, it discovers all publishable workspace packages and asks you to assign a bump type — `0` (skip), `1` (patch), `2` (minor), or `3` (major) — to each one individually. Packages you skip are left untouched. For each package with a bump, it first tries to run the release script from the root package.json (using `--filter-root release:<type>`), and if that fails, falls back to running `commit-and-tag-version` directly with the appropriate bump flag. This bumps that package's version, rewrites its `CHANGELOG.md` using commits since its last scoped tag, and commits on `main`. The branches merge once after all bumps are applied. CI on `production` publishes only the packages whose version changed.
46
+ `ship:hotfix finish` versions the fix, merges it into `production`, then propagates through `staging` and back to `main` so nothing is lost.
29
47
 
30
- ## How It Works
48
+ ## Commands
31
49
 
32
- ### Design principles
50
+ | Command | Purpose |
51
+ |---------|---------|
52
+ | `pnpm ship:staging` | Fast-forward `main` → `staging`, push. Triggers staging CI. |
53
+ | `pnpm ship:production` | Version selected packages (if any), fast-forward `main` → `production`, sync `staging`. Triggers production CI. |
54
+ | `pnpm ship:hotfix start <name>` | Create `hotfix/<name>` from `production`. |
55
+ | `pnpm ship:hotfix finish` | Version selected packages (if any), merge hotfix → `production` → `staging` → `main`. |
33
56
 
34
- 1. **Branches mirror environments.** `staging` reflects what is deployed to the staging server (when used); `production` always reflects what is published to npm. There is never ambiguity about what is running where.
35
- 2. **You choose the bump per package at ship time.** When running `ship-production` or `ship-hotfix finish`, every publishable package is listed and you assign it `0` (skip), `1` (patch), `2` (minor), or `3` (major). Packages you skip are untouched. The conventional commits in the log inform each choice; the decisions are always explicit.
36
- 3. **Version numbers are a production guarantee.** Bumps are applied only at `ship-production` time. Every version tag in git and every release on npm corresponds to code that has been validated and shipped.
37
- 4. **Staging is optional.** You can ship directly from `main` to `production`. Use `ship-staging` when you want to test changes in a staging environment first.
38
- 5. **Tests are a local gate, not a CI gate.** Quality checks (`lint`, `typecheck`, `test`) run on your machine before any commit is made. Broken code cannot enter the repository.
39
- 6. **You always land back on `main`.** Every command returns you to `main` when it finishes.
57
+ ## Versioning Per-Package, at Ship Time
40
58
 
41
- ### Full flow diagram
59
+ `ship:production` and `ship:hotfix finish` discover all publishable workspace packages (those without `"private": true`) and ask you to assign a bump type to each:
42
60
 
43
- ```text
44
- ship-staging (OPTIONAL)
45
- ┌──────────────────────────┐
46
- main ───────────┤ quality checks pass ├──► staging (pushed)
47
- │ ff-merge main → staging │
48
- └──────────────────────────┘
49
-
50
- (validated in staging)
51
-
52
-
53
- ship-production
54
- ┌──────────────────────────┐
55
- │ quality checks on main │
56
- │ pnpm release │
57
- main ───────────┤ bumps version + ├──► production
58
- │ rewrites CHANGELOG.md │
59
- │ ff-merge main → prod │
60
- │ ff-merge prod → staging │
61
- │ push --follow-tags main │
62
- └──────────────────────────┘
63
-
64
- (CI: pnpm -r publish → npm)
65
-
66
- production
67
-
68
-
69
- ship-hotfix start fix-name
70
- ┌──────────────────────────┐
71
- production ─────┤ create hotfix/fix-name ├──► hotfix/fix-name
72
- └──────────────────────────┘
73
-
74
- (author fixes with conventional commits)
75
-
76
- ship-hotfix finish
77
- ┌──────────────────────────┐
78
- │ quality checks │
79
- │ pnpm release │
80
- hotfix/fix-name ┤ bumps version + ├──► production → staging → main
81
- │ rewrites CHANGELOG.md │
82
- │ merge to production │
83
- │ propagate to staging │
84
- │ propagate to main │
85
- └──────────────────────────┘
86
- ```
61
+ - `0` — skip (no version change)
62
+ - `1` — patch
63
+ - `2` — minor
64
+ - `3` major
87
65
 
88
- ### What each branch represents at any point in time
66
+ For each selected package, `commit-and-tag-version` runs from within that package's directory, reading the package's own `.versionrc.json`. It bumps the `version` field in `package.json`, rewrites `CHANGELOG.md` to include only commits that touched files under that package's directory (`gitRawCommitsOpts.path: "."`), commits the bump, and creates a scoped tag (e.g. `@regardio/dev@v1.2.0`).
89
67
 
90
- | Branch | Contains | Version bumped? |
91
- |--------|----------|-----------------|
92
- | `main` | All committed, tested work | Only after `ship-production` / `ship-hotfix finish` |
93
- | `staging` | Synced from `production` after each ship, or from `main` via `ship-staging` | After a ship propagates |
94
- | `production` | Only shipped, versioned releases | Yes — always |
68
+ If a repo has no publishable packages (all are `private: true` or there are no `.versionrc.json` files), the versioning UI is skipped entirely and `ship:production` proceeds directly to the git merge.
95
69
 
96
- ### CI role
70
+ ## CI — What Happens on Each Branch
97
71
 
98
- CI is intentionally minimal. On push to `production` it:
72
+ CI behaviour differs by what the repo produces.
99
73
 
100
- 1. Installs dependencies and builds
101
- 2. Runs `pnpm -r publish --access public --no-git-checks` — publishes every workspace package whose current version is not yet on npm, skipping `private: true` packages
102
- 3. Tags are already present (pushed by `ship-production` via `--follow-tags`)
74
+ ### Publishing monorepos (e.g. `commons`)
103
75
 
104
- ## Commands
76
+ - **staging**: Run typecheck and tests. Nothing is published.
77
+ - **production**: Run typecheck, tests, then `pnpm -r publish --access public --no-git-checks`. Only packages whose current version is not yet on npm are published — packages with `"private": true` are skipped automatically.
105
78
 
106
- | Command | Usage | Purpose |
107
- |---------|-------|---------|
108
- | `pnpm release` | auto | Bump version from conventional commits, update CHANGELOG.md, commit |
109
- | `pnpm release:patch` | explicit | Force a patch bump |
110
- | `pnpm release:minor` | explicit | Force a minor bump |
111
- | `pnpm release:major` | explicit | Force a major bump |
112
- | `ship-staging` | `ship-staging` | (Optional) Deploy changes to staging for testing |
113
- | `ship-production` | `ship-production` | Bump version, merge main → production, trigger publish |
114
- | `ship-hotfix start <name>` | `ship-hotfix start <name>` | Create a hotfix branch from production |
115
- | `ship-hotfix finish` | `ship-hotfix finish` | Bump version, propagate hotfix through production → staging → main |
79
+ ### Server-deployed apps with Docker (e.g. `brass-berlin`)
116
80
 
117
- ## Typical Release Flow
81
+ - **staging**: Run tests, build the Docker image, push to the container registry, trigger a staging deploy (e.g. Coolify webhook).
82
+ - **production**: Run typecheck + tests, build and push the Docker image, trigger a production deploy.
83
+
84
+ ### Cloudflare Workers apps (e.g. `magic-of-open-source`)
85
+
86
+ - **staging**: Run tests, then `wrangler versions upload --preview-alias preview`.
87
+ - **production**: Run typecheck + tests, then `wrangler deploy`.
88
+
89
+ ### Documentation / vocabulary repos (e.g. `system`)
118
90
 
119
- ### 1. Develop with conventional commits
91
+ - **staging** and **production**: Verify the repo is well-formed (build, typecheck, test). No publishing or deployment — the repo is distributed via git clone, not npm.
120
92
 
121
- On `main`, use conventional commits so the bump type is inferred automatically:
93
+ ### Static sites without a configured deployment (e.g. `bam-berlin-org`)
94
+
95
+ - **staging** and **production**: Run tests, typecheck (production only), and build. A deployment step is left as a TODO until the hosting platform is chosen.
96
+
97
+ ## Typical Release Flow
98
+
99
+ ### 1. Develop on `main` with conventional commits
122
100
 
123
101
  ```bash
124
102
  feat: add user authentication # → minor bump
@@ -126,111 +104,114 @@ fix: resolve login redirect loop # → patch bump
126
104
  feat!: redesign authentication API # → major bump
127
105
  ```
128
106
 
129
- ### 2. Ship to production
107
+ ### 2. (Optional) Test in staging first
130
108
 
131
109
  ```bash
132
- pnpm ship:production
110
+ pnpm ship:staging
133
111
  ```
134
112
 
135
- This will:
136
-
137
- 1. Guard: must be on `main`, working tree clean
138
- 2. Fetch and verify `staging` + `production` branches exist
139
- 3. Show commits to be shipped
140
- 4. For each publishable package: choose `0 (skip) / 1 (patch) / 2 (minor) / 3 (major)`
141
- 5. Confirm the planned bumps — abort if declined or all skipped
142
- 6. Run full quality suite on `main` — aborts on failure
143
- 7. For each non-skipped package: run release script from root (if defined) or `commit-and-tag-version` directly — bumps version, rewrites `CHANGELOG.md`, commits
144
- 8. Fast-forward merge `main` into `production` and push
145
- 9. Sync `staging` with `production`
146
- 10. Push `main` with `--follow-tags` to push all new version tags
147
- 11. Return to `main`
148
-
149
- CI on `production` runs `pnpm -r publish` to push changed packages to npm.
113
+ Deploys `main` to `staging` without touching versions.
150
114
 
151
- ### Option: Test in staging first
115
+ ### 3. Ship to production
152
116
 
153
117
  ```bash
154
- pnpm ship:staging
118
+ pnpm ship:production
155
119
  ```
156
120
 
157
- Deploys `main` to `staging` without touching versions. Useful for validating in a staging environment before shipping to production.
121
+ 1. Guard: must be on `main`, working tree clean.
122
+ 2. Fetch and verify `staging` and `production` branches exist.
123
+ 3. Show commits to be shipped.
124
+ 4. Run full quality suite — aborts on failure.
125
+ 5. If publishable packages exist: assign a bump type to each; confirm the planned bumps.
126
+ 6. If no publishable packages: confirm whether to ship (versioning is skipped).
127
+ 7. For each selected package: run `commit-and-tag-version --release-as <type>` from within the package directory.
128
+ 8. Fast-forward merge `main` → `production`, push.
129
+ 9. Sync `staging` with `production`, push.
130
+ 10. Push `main` with `--follow-tags` to carry the new version tags.
131
+ 11. Return to `main`.
158
132
 
159
- ## Hotfix Flow
160
-
161
- For urgent fixes that must go directly to production:
133
+ ### 4. Hotfix flow
162
134
 
163
135
  ```bash
164
136
  pnpm ship:hotfix start fix-auth-bug
165
- # apply your fix with conventional commits
137
+ # apply fix with conventional commits
166
138
  git add . && git commit -m "fix: ..."
167
139
  pnpm ship:hotfix finish
168
140
  ```
169
141
 
170
- `finish` asks you to assign a bump type (`0` (skip), `1` (patch), `2` (minor), or `3` (major)) to each publishable package, runs the release script from root (if defined) or `commit-and-tag-version` directly for each non-skipped one to bump versions and update `CHANGELOG.md`, then merges `hotfix → production → staging → main` and deletes the hotfix branch. CI publishes from `production`.
142
+ `finish` fetches origin, runs quality checks, assigns bump types (if publishable packages exist), versions the fix, then merges `hotfix → production → staging → main` and deletes the hotfix branch.
171
143
 
172
144
  ## Adoption
173
145
 
174
- Install `@regardio/dev` and:
146
+ ### 1. Add ship scripts to `package.json`
175
147
 
176
- 1. **Add the scripts to `package.json`** (optional, for monorepos):
148
+ ```json
149
+ {
150
+ "scripts": {
151
+ "ship:hotfix": "ship-hotfix",
152
+ "ship:production": "ship-production",
153
+ "ship:staging": "ship-staging"
154
+ }
155
+ }
156
+ ```
177
157
 
178
- In a monorepo, you can define release scripts in the root `package.json` to centralize version management. The ship scripts will use these if available, and fall back to running `commit-and-tag-version` directly if not.
158
+ For repos that publish to npm, also add:
159
+
160
+ ```json
161
+ {
162
+ "scripts": {
163
+ "release": "commit-and-tag-version",
164
+ "release:major": "commit-and-tag-version --release-as major",
165
+ "release:minor": "commit-and-tag-version --release-as minor",
166
+ "release:patch": "commit-and-tag-version --release-as patch"
167
+ }
168
+ }
169
+ ```
179
170
 
180
- ```json
181
- {
182
- "scripts": {
183
- "release": "commit-and-tag-version",
184
- "release:major": "commit-and-tag-version --release-as major",
185
- "release:minor": "commit-and-tag-version --release-as minor",
186
- "release:patch": "commit-and-tag-version --release-as patch",
187
- "ship:hotfix": "ship-hotfix",
188
- "ship:production": "ship-production",
189
- "ship:staging": "ship-staging"
190
- }
191
- }
192
- ```
171
+ ### 2. Add `.versionrc.json` to each publishable package
193
172
 
194
- For single-package repos or when you prefer direct `commit-and-tag-version` usage, you can omit the release scripts — the ship commands will call `commit-and-tag-version` directly.
173
+ ```bash
174
+ cp node_modules/@regardio/dev/templates/versionrc/.versionrc.json packages/my-pkg/.versionrc.json
175
+ # then set "tagPrefix": "@my-scope/my-pkg@v" inside that file
176
+ ```
195
177
 
196
- 2. **Add a `.versionrc.json` to each publishable package** with a scoped `tagPrefix` so tags and changelogs stay isolated per package:
178
+ Every `.versionrc.json` must include `"gitRawCommitsOpts": { "path": "." }` to filter the git log to commits that actually touched files under that package's directory. Without this, the changelog accumulates noise from unrelated packages.
197
179
 
198
- ```bash
199
- cp node_modules/@regardio/dev/templates/versionrc/.versionrc.json packages/my-pkg/.versionrc.json
200
- # then set "tagPrefix": "@my-scope/my-pkg@v" inside that file
201
- ```
180
+ For a single-package repo, place `.versionrc.json` at the root instead.
202
181
 
203
- For a single-package repo, put `.versionrc.json` at the root instead.
182
+ ### 3. Create the branches
204
183
 
205
- 3. **Create the branches**:
184
+ ```bash
185
+ git checkout -b staging && git push -u origin staging
186
+ git checkout -b production && git push -u origin production
187
+ git checkout main
188
+ ```
206
189
 
207
- ```bash
208
- git checkout -b staging && git push -u origin staging
209
- git checkout -b production && git push -u origin production
210
- git checkout main
211
- ```
190
+ ### 4. Copy and adapt the CI workflow
212
191
 
213
- 4. **Copy the release workflow** for your forge:
192
+ ```bash
193
+ # Forgejo / Codeberg
194
+ mkdir -p .forgejo/workflows
195
+ cp node_modules/@regardio/dev/templates/github/release.yml .forgejo/workflows/production.yml
214
196
 
215
- ```bash
216
- # GitHub
217
- mkdir -p .github/workflows
218
- cp node_modules/@regardio/dev/templates/github/release.yml .github/workflows/release.yml
197
+ # GitHub
198
+ mkdir -p .github/workflows
199
+ cp node_modules/@regardio/dev/templates/github/release.yml .github/workflows/release.yml
200
+ ```
219
201
 
220
- # Forgejo / Codeberg
221
- mkdir -p .forgejo/workflows
222
- cp node_modules/@regardio/dev/templates/github/release.yml .forgejo/workflows/release.yml
223
- ```
202
+ Adapt the workflow for your repo type (see CI section above). The template covers the npm-publishing case; server-deployed and Cloudflare repos need the deployment step replaced.
224
203
 
225
- 5. **First publish of any new package must be done locally**:
204
+ ### 5. First publish of any new package must be done locally
226
205
 
227
- ```bash
228
- pnpm build && npm publish --access public
229
- ```
206
+ ```bash
207
+ pnpm build && npm publish --access public
208
+ ```
209
+
210
+ CI publishes subsequent versions; the very first one requires a local publish because the package does not yet exist on npm.
230
211
 
231
212
  ## Quality Gates
232
213
 
233
- Every ship command enforces the same gates no broken code reaches any environment:
214
+ Every ship command enforces the same gates before any commit or merge:
234
215
 
235
216
  ```bash
236
217
  pnpm build # Must succeed
@@ -238,9 +219,11 @@ pnpm typecheck # Must succeed
238
219
  pnpm test # Must succeed
239
220
  ```
240
221
 
241
- ## Private Packages
222
+ ## Private Packages and Non-Publishing Repos
223
+
224
+ Packages with `"private": true` are skipped by `pnpm -r publish` automatically.
242
225
 
243
- Packages that should never be published to npm must set `"private": true` in `package.json`. `pnpm -r publish` skips them automatically. The git flow (`ship-staging`, `ship-production`, `ship-hotfix`) works identically version bumps and branch promotion continue as normal; only the npm publish step is skipped.
226
+ For repos where all packages are private (channels, deployment repos), the ship tools skip the versioning UI entirely and only perform the git merges. This makes the same tooling reusable across publishing and non-publishing repositories.
244
227
 
245
228
  ## Related
246
229
 
@@ -17,33 +17,62 @@ Strict, shared TypeScript presets for Regardio projects. Extending from `@regard
17
17
 
18
18
  | Preset | Use case |
19
19
  |--------|----------|
20
- | `@regardio/dev/typescript/base` | Node.js packages, libraries |
21
- | `@regardio/dev/typescript/react` | React applications and components |
22
- | `@regardio/dev/typescript/build` | Build-specific settings (extends base) |
20
+ | `@regardio/dev/typescript/base` | Non-publishing packages and meta-configs (e.g. the workspace root) |
21
+ | `@regardio/dev/typescript/library` | Published Node.js packages adds declaration enforcement |
22
+ | `@regardio/dev/typescript/react` | React apps and channels — adds JSX and DOM libs |
23
+ | `@regardio/dev/typescript/react-library` | Published React packages — JSX, DOM libs, and declaration enforcement |
24
+ | `@regardio/dev/typescript/build` | Build-time emission settings (pair with any of the above) |
25
+
26
+ ### Choosing a preset
27
+
28
+ The guiding question is whether the package **publishes type declarations** to consumers.
29
+
30
+ - **App or channel** (`private: true`) → `react` (or `base` for non-React)
31
+ - **Published library without React** → `library`
32
+ - **Published library with React** → `react-library`
33
+
34
+ The `library` and `react-library` presets add two settings on top of the base:
35
+
36
+ - `declaration: true` — validates that `.d.ts` files can be generated (even when `noEmit: true` during typecheck)
37
+ - `isolatedDeclarations: true` — enforces that every exported declaration carries an explicit type annotation, enabling per-file parallel declaration generation
38
+
39
+ `isolatedDeclarations` is a library-authoring constraint. It requires explicit return types on exported functions and explicit annotations on exported variables. Applying it to app code is unnecessarily strict — especially with frameworks like TanStack Router whose `Route` objects carry deeply inferred generics that cannot be spelled out without extraordinary verbosity.
23
40
 
24
41
  ## Configuration
25
42
 
26
- ### `tsconfig.json`
43
+ ### App or channel
27
44
 
28
45
  ```json
29
46
  {
30
- "extends": "@regardio/dev/typescript/base",
47
+ "compilerOptions": {
48
+ "paths": { "#/*": ["./src/*"] }
49
+ },
50
+ "extends": "@regardio/dev/typescript/react",
51
+ "include": ["**/*.ts", "**/*.tsx", "**/*.d.ts"]
52
+ }
53
+ ```
54
+
55
+ ### Published Node.js library
56
+
57
+ ```json
58
+ {
59
+ "extends": "@regardio/dev/typescript/library",
31
60
  "include": ["src/**/*.ts"]
32
61
  }
33
62
  ```
34
63
 
35
- For React projects:
64
+ ### Published React library
36
65
 
37
66
  ```json
38
67
  {
39
- "extends": "@regardio/dev/typescript/react",
68
+ "extends": "@regardio/dev/typescript/react-library",
40
69
  "include": ["src/**/*.ts", "src/**/*.tsx"]
41
70
  }
42
71
  ```
43
72
 
44
73
  ### `tsconfig.build.json`
45
74
 
46
- A separate build config for production output:
75
+ Pair any preset with `build` for production output:
47
76
 
48
77
  ```json
49
78
  {
@@ -51,28 +80,26 @@ A separate build config for production output:
51
80
  "outDir": "./dist",
52
81
  "rootDir": "./src"
53
82
  },
54
- "extends": ["./tsconfig.json", "@regardio/dev/typescript/build"],
55
- "include": ["src/**/*.ts"]
83
+ "extends": ["./tsconfig.json", "@regardio/dev/typescript/build"]
56
84
  }
57
85
  ```
58
86
 
59
87
  ## Strict settings
60
88
 
61
- The base config turns on strict type-checking across the board:
89
+ All presets share the same strict type-checking baseline:
62
90
 
63
91
  - `strict: true` — every strict type-checking option
64
92
  - `noUncheckedIndexedAccess: true` — adds `undefined` to index signatures
65
- - `exactOptionalPropertyTypes: true` — distinguishes `undefined` from missing
66
93
  - `noImplicitReturns: true` — every code path returns a value
67
94
  - `noFallthroughCasesInSwitch: true` — prevents switch-fallthrough surprises
95
+ - `verbatimModuleSyntax: true` — `import type` vs. `import` must be explicit
68
96
 
69
97
  ## Scripts
70
98
 
71
99
  ```json
72
100
  {
73
101
  "scripts": {
74
- "build": "exec-tsc --project tsconfig.build.json",
75
- "typecheck": "exec-tsc --noEmit"
102
+ "typecheck": "tsc --noEmit"
76
103
  }
77
104
  }
78
105
  ```
@@ -65,7 +65,7 @@ export default defineConfig({ test: vitestReactConfig });
65
65
  ```json
66
66
  {
67
67
  "scripts": {
68
- "test": "exec-s test:*",
68
+ "test": "vitest run",
69
69
  "test:unit": "vitest run",
70
70
  "report": "vitest run --coverage"
71
71
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://www.schemastore.org/package.json",
3
3
  "name": "@regardio/dev",
4
- "version": "2.4.0",
4
+ "version": "2.5.0",
5
5
  "private": false,
6
6
  "description": "Regardio development presets: biome, typescript, commitlint, markdownlint, vitest, playwright, sqlfluff, husky, and GitLab-flow ship tooling",
7
7
  "keywords": [
@@ -44,7 +44,9 @@
44
44
  },
45
45
  "./typescript/base": "./src/typescript/base.json",
46
46
  "./typescript/build": "./src/typescript/build.json",
47
+ "./typescript/library": "./src/typescript/library.json",
47
48
  "./typescript/react": "./src/typescript/react.json",
49
+ "./typescript/react-library": "./src/typescript/react-library.json",
48
50
  "./vitest/node": {
49
51
  "import": "./dist/vitest/node.mjs",
50
52
  "types": "./dist/vitest/node.d.mts"
@@ -61,6 +63,7 @@
61
63
  },
62
64
  "files": [
63
65
  "dist",
66
+ "LICENSE",
64
67
  "src/biome",
65
68
  "src/commitlint",
66
69
  "src/markdownlint",
@@ -71,9 +74,9 @@
71
74
  ],
72
75
  "devDependencies": {
73
76
  "@total-typescript/ts-reset": "0.6.1",
74
- "@types/node": "25.6.0",
77
+ "@types/node": "25.6.1",
75
78
  "@vitest/coverage-v8": "4.1.5",
76
- "tsdown": "0.21.10",
79
+ "tsdown": "0.22.0",
77
80
  "vitest": "4.1.5"
78
81
  },
79
82
  "peerDependencies": {
@@ -90,9 +93,6 @@
90
93
  "typescript": ">=6",
91
94
  "vitest": ">=4"
92
95
  },
93
- "engines": {
94
- "node": ">=24"
95
- },
96
96
  "scripts": {
97
97
  "build": "tsdown",
98
98
  "clean": "rimraf .turbo dist",
@@ -101,7 +101,7 @@
101
101
  "fix:biome": "biome check --write --unsafe .",
102
102
  "fix:md": "markdownlint-cli2 --config ../../.markdownlint-cli2.jsonc --fix",
103
103
  "fix:pkg": "sort-package-json",
104
- "lint": "run-s lint:md lint:biome",
104
+ "lint": "run-p lint:*",
105
105
  "lint:biome": "biome check .",
106
106
  "lint:md": "markdownlint-cli2 --config ../../.markdownlint-cli2.jsonc",
107
107
  "lint:pkg": "sort-package-json --check",
@@ -7,13 +7,11 @@
7
7
  "allowUnusedLabels": false,
8
8
  "alwaysStrict": true,
9
9
  "checkJs": false,
10
- "declaration": true,
11
10
  "esModuleInterop": true,
12
11
  "exactOptionalPropertyTypes": false,
13
12
  "forceConsistentCasingInFileNames": true,
14
13
  "incremental": false,
15
14
  "inlineSources": false,
16
- "isolatedDeclarations": true,
17
15
  "isolatedModules": true,
18
16
  "module": "ESNext",
19
17
  "moduleDetection": "force",
@@ -0,0 +1,8 @@
1
+ {
2
+ "$schema": "https://www.schemastore.org/tsconfig.json",
3
+ "compilerOptions": {
4
+ "declaration": true,
5
+ "isolatedDeclarations": true
6
+ },
7
+ "extends": "./base.json"
8
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "$schema": "https://www.schemastore.org/tsconfig.json",
3
+ "compilerOptions": {
4
+ "declaration": true,
5
+ "isolatedDeclarations": true
6
+ },
7
+ "extends": "./react.json"
8
+ }
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "commitAll": false,
3
+ "gitRawCommitsOpts": { "path": "." },
3
4
  "sign": false,
4
5
  "tagPrefix": "v",
5
6
  "types": [