@regardio/dev 2.1.1 → 2.3.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,8 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as runQualityChecks, i as gitRead, o as runScript, r as git, t as branchExists } from "./utils-BQ-JZ2D5.mjs";
3
- import { execFileSync, execSync } from "node:child_process";
4
- import { existsSync, readFileSync } from "node:fs";
5
- import { join } from "node:path";
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-VktE94Vs.mjs";
6
3
  //#region src/bin/ship/hotfix.ts
7
4
  /**
8
5
  * ship-hotfix: Manage hotfix branches based on production code.
@@ -14,10 +11,10 @@ import { join } from "node:path";
14
11
  * GitLab workflow:
15
12
  * production → hotfix/<name> → production → staging → main
16
13
  *
17
- * During a hotfix, add a changeset (`pnpm changeset`) describing the fix.
18
- * `ship-hotfix finish` consumes all pending changesets, commits the version
19
- * bumps, and propagates the hotfix through production → staging → main.
20
- * CI on `production` handles the `changeset publish` to npm.
14
+ * During a hotfix, commit your fix with conventional commits. `ship-hotfix
15
+ * finish` runs `pnpm release` to bump the version based on those commits,
16
+ * updates CHANGELOG.md, then propagates the hotfix through
17
+ * production → staging → main. CI on `production` handles publishing to npm.
21
18
  */
22
19
  function runShipHotfix(subcommand, subArgs, cwd = process.cwd()) {
23
20
  if (subcommand === "start") {
@@ -41,7 +38,7 @@ function runShipHotfix(subcommand, subArgs, cwd = process.cwd()) {
41
38
  git("pull", "--ff-only", "origin", "production");
42
39
  git("checkout", "-b", hotfixBranch);
43
40
  console.log(`\n✅ Hotfix branch "${hotfixBranch}" created from production.`);
44
- console.log("Apply your fix, add a changeset (`pnpm changeset`), commit, then run:");
41
+ console.log("Apply your fix using conventional commits, then run:");
45
42
  console.log(" ship-hotfix finish");
46
43
  process.exit(0);
47
44
  }
@@ -55,14 +52,6 @@ function runShipHotfix(subcommand, subArgs, cwd = process.cwd()) {
55
52
  console.error("Working directory has uncommitted changes. Commit or stash them first.");
56
53
  process.exit(1);
57
54
  }
58
- if (!existsSync(join(cwd, ".changeset"))) {
59
- console.error("\nNo .changeset/ directory found. This tooling requires Changesets.\nCopy the template from @regardio/dev/templates/changeset and run:\n pnpm changeset init");
60
- process.exit(1);
61
- }
62
- if (execFileSync("sh", ["-c", `ls .changeset/*.md 2>/dev/null | grep -v README.md | wc -l | tr -d ' '`], { encoding: "utf-8" }).trim() === "0") {
63
- console.error("\nNo pending changesets. Run `pnpm changeset` before finishing the hotfix.");
64
- process.exit(1);
65
- }
66
55
  console.log("\nRunning quality checks...");
67
56
  try {
68
57
  runQualityChecks();
@@ -71,37 +60,19 @@ function runShipHotfix(subcommand, subArgs, cwd = process.cwd()) {
71
60
  process.exit(1);
72
61
  }
73
62
  console.log("✅ Quality checks passed");
74
- const packageJsonPath = join(cwd, "package.json");
75
- if (!existsSync(packageJsonPath)) {
76
- console.error("No package.json found in current directory.");
63
+ const packages = getWorkspacePackages(cwd);
64
+ if (packages.length === 0) {
65
+ console.error("No publishable workspace packages found.");
77
66
  process.exit(1);
78
67
  }
79
- const { name: packageName } = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
80
- console.log("\nApplying changesets (bumping versions and updating CHANGELOGs)...");
81
- runScript("changeset version");
82
- try {
83
- runScript("fix:pkg");
84
- } catch {}
85
- try {
86
- git("add", "-A");
87
- const changedFiles = gitRead("diff", "--cached", "--name-only").split("\n").filter(Boolean);
88
- for (const file of changedFiles) {
89
- if (file.endsWith(".json")) try {
90
- execSync(`npx biome check --write ${file}`, {
91
- cwd,
92
- stdio: "inherit"
93
- });
94
- } catch {}
95
- if (file.endsWith(".md")) try {
96
- execSync(`npx markdownlint-cli2 --fix ${file}`, {
97
- cwd,
98
- stdio: "inherit"
99
- });
100
- } catch {}
101
- }
102
- } catch {}
103
- git("add", "-A");
104
- git("commit", "-m", "chore(hotfix): version packages");
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) runScript(`--filter ${pkg.name} release:${bump}`);
105
76
  console.log("\nFetching latest state from origin...");
106
77
  git("fetch", "origin");
107
78
  console.log("\nMerging hotfix into production...");
@@ -118,12 +89,13 @@ function runShipHotfix(subcommand, subArgs, cwd = process.cwd()) {
118
89
  git("checkout", "main");
119
90
  git("pull", "--ff-only", "origin", "main");
120
91
  git("merge", "--no-ff", "staging", "-m", "chore(hotfix): merge staging into main");
121
- git("push", "origin", "main");
92
+ git("push", "--follow-tags", "origin", "main");
122
93
  git("branch", "-d", currentBranch);
123
94
  try {
124
95
  git("push", "origin", "--delete", currentBranch);
125
96
  } catch {}
126
- console.log(`\n✅ Hotfix ${packageName} shipped to production staging → main`);
97
+ const shipped = bumps.map((b) => b.package.name).join(", ");
98
+ console.log(`\n✅ Hotfix ${shipped} shipped to production → staging → main`);
127
99
  console.log("CI will publish changed packages to npm.");
128
100
  console.log("You are on main and ready to keep working.");
129
101
  process.exit(0);
@@ -1,8 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as runQualityChecks, i as gitRead, n as confirm, o as runScript, r as git, t as branchExists } from "./utils-BQ-JZ2D5.mjs";
3
- import { execFileSync, execSync } from "node:child_process";
4
- import { existsSync, readFileSync } from "node:fs";
5
- import { join } from "node:path";
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-VktE94Vs.mjs";
6
3
  //#region src/bin/ship/production.ts
7
4
  /**
8
5
  * ship-production: Promote main to production following the GitLab workflow.
@@ -10,12 +7,12 @@ import { join } from "node:path";
10
7
  * Usage: ship-production
11
8
  *
12
9
  * GitLab workflow:
13
- * main → (changeset version + commit) → production → staging → main
10
+ * main → (commit-and-tag-version) → production → staging → main
14
11
  *
15
- * Versioning is driven by Changesets. Authors add a changeset (`pnpm changeset`)
16
- * alongside their change on main. This script consumes all pending changesets
17
- * at ship time, commits the version bumps, then merges main → production.
18
- * CI on `production` handles the actual `changeset publish` to npm.
12
+ * Versioning is driven by commit-and-tag-version. Running `pnpm release`
13
+ * at ship time reads conventional commits since the last tag, bumps the
14
+ * version, updates CHANGELOG.md, and commits the result on `main` before
15
+ * merging to `production`. CI on `production` handles publishing to npm.
19
16
  */
20
17
  function runShipProduction(cwd = process.cwd()) {
21
18
  const currentBranch = gitRead("branch", "--show-current");
@@ -45,22 +42,18 @@ function runShipProduction(cwd = process.cwd()) {
45
42
  }
46
43
  console.log("\nCommits to be shipped to production:");
47
44
  console.log(ahead);
48
- const packageJsonPath = join(cwd, "package.json");
49
- if (!existsSync(packageJsonPath)) {
50
- console.error("No package.json found in current directory.");
45
+ const packages = getWorkspacePackages(cwd);
46
+ if (packages.length === 0) {
47
+ console.error("No publishable workspace packages found.");
51
48
  process.exit(1);
52
49
  }
53
- const { name: packageName } = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
54
- const hasChangesets = existsSync(join(cwd, ".changeset"));
55
- if (hasChangesets) {
56
- const pendingChangesets = execFileSync("sh", ["-c", `ls .changeset/*.md 2>/dev/null | grep -v README.md | wc -l | tr -d ' '`], { encoding: "utf-8" }).trim();
57
- if (pendingChangesets === "0") {
58
- console.error("\nNo pending changesets. Run `pnpm changeset` before shipping to production.");
59
- process.exit(1);
60
- }
61
- console.log(`\n${pendingChangesets} pending changeset(s) will be consumed.`);
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);
62
55
  }
63
- if (!confirm(`Ship ${packageName} to production?`)) {
56
+ if (!confirm(`\nShip to production?\n${bumps.map((b) => ` ${b.package.name} → ${b.bump}`).join("\n")}\n`)) {
64
57
  console.log("Aborted.");
65
58
  process.exit(0);
66
59
  }
@@ -72,33 +65,8 @@ function runShipProduction(cwd = process.cwd()) {
72
65
  process.exit(1);
73
66
  }
74
67
  console.log("✅ Quality checks passed");
75
- if (hasChangesets) {
76
- console.log("\nApplying changesets (bumping versions and updating CHANGELOGs)...");
77
- runScript("changeset version");
78
- try {
79
- runScript("fix:pkg");
80
- } catch {}
81
- try {
82
- git("add", "-A");
83
- const changedFiles = gitRead("diff", "--cached", "--name-only").split("\n").filter(Boolean);
84
- for (const file of changedFiles) {
85
- if (file.endsWith(".json")) try {
86
- execSync(`npx biome check --write ${file}`, {
87
- cwd,
88
- stdio: "inherit"
89
- });
90
- } catch {}
91
- if (file.endsWith(".md")) try {
92
- execSync(`npx markdownlint-cli2 --fix ${file}`, {
93
- cwd,
94
- stdio: "inherit"
95
- });
96
- } catch {}
97
- }
98
- } catch {}
99
- git("add", "-A");
100
- git("commit", "-m", "chore(release): version packages");
101
- }
68
+ console.log("\nBumping versions and updating CHANGELOGs...");
69
+ for (const { package: pkg, bump } of bumps) runScript(`--filter ${pkg.name} release:${bump}`);
102
70
  console.log("\nMerging main into production...");
103
71
  git("checkout", "production");
104
72
  git("pull", "--ff-only", "origin", "production");
@@ -110,8 +78,9 @@ function runShipProduction(cwd = process.cwd()) {
110
78
  git("merge", "--ff-only", "production");
111
79
  git("push", "origin", "staging");
112
80
  git("checkout", "main");
113
- git("push", "origin", "main");
114
- console.log(`\n✅ Shipped ${packageName} to production. CI will publish changed packages to npm.`);
81
+ git("push", "--follow-tags", "origin", "main");
82
+ const shipped = bumps.map((b) => b.package.name).join(", ");
83
+ console.log(`\n✅ Shipped ${shipped} to production. CI will publish changed packages to npm.`);
115
84
  console.log("You are on main and ready to keep working.");
116
85
  }
117
86
  //#endregion
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as runQualityChecks, i as gitRead, r as git, t as branchExists } from "./utils-BQ-JZ2D5.mjs";
2
+ import { a as git, o as gitRead, s as runQualityChecks, t as branchExists } from "./utils-VktE94Vs.mjs";
3
3
  import { existsSync, readFileSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  //#region src/bin/ship/staging.ts
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env node
2
+ import { execFileSync, execSync } from "node:child_process";
3
+ import { closeSync, openSync, readSync } from "node:fs";
4
+ //#region src/bin/ship/utils.ts
5
+ /**
6
+ * Shared utilities for ship-staging, ship-production, and ship-hotfix.
7
+ *
8
+ * Git commands use execFileSync (not a shell string) so user-provided
9
+ * strings such as commit messages are never interpolated by the shell.
10
+ * pnpm script invocations use execSync via shell since script names are
11
+ * developer-controlled and pnpm itself is resolved through PATH.
12
+ */
13
+ const git = (...args) => {
14
+ console.log(`$ git ${args.join(" ")}`);
15
+ execFileSync("git", args, { stdio: "inherit" });
16
+ };
17
+ const gitRead = (...args) => execFileSync("git", args, { encoding: "utf-8" }).trim();
18
+ const runScript = (script) => {
19
+ console.log(`$ pnpm ${script}`);
20
+ execSync(`pnpm ${script}`, { stdio: "inherit" });
21
+ };
22
+ const runQualityChecks = () => {
23
+ runScript("build");
24
+ runScript("typecheck");
25
+ runScript("test");
26
+ };
27
+ const getWorkspacePackages = (cwd = process.cwd()) => {
28
+ const raw = execFileSync("pnpm", [
29
+ "list",
30
+ "--recursive",
31
+ "--json",
32
+ "--depth",
33
+ "0"
34
+ ], {
35
+ cwd,
36
+ encoding: "utf-8"
37
+ });
38
+ return JSON.parse(raw).filter((p) => p.private !== true && p.name !== void 0).map(({ name, path }) => ({
39
+ name,
40
+ path
41
+ }));
42
+ };
43
+ const chooseForEach = (packages, ttyPath = "/dev/tty") => {
44
+ const bumps = [];
45
+ const options = [
46
+ {
47
+ label: "skip",
48
+ value: "skip"
49
+ },
50
+ {
51
+ label: "patch — bug fixes",
52
+ value: "patch"
53
+ },
54
+ {
55
+ label: "minor — new features",
56
+ value: "minor"
57
+ },
58
+ {
59
+ label: "major — breaking changes",
60
+ value: "major"
61
+ }
62
+ ];
63
+ for (const pkg of packages) {
64
+ const answer = choose(`\n${pkg.name}:`, [...options], ttyPath);
65
+ if (answer !== "skip") bumps.push({
66
+ bump: answer,
67
+ package: pkg
68
+ });
69
+ }
70
+ return bumps;
71
+ };
72
+ const branchExists = (name) => gitRead("branch", "--list", name) !== "" || gitRead("branch", "--list", "--remotes", `origin/${name}`) !== "";
73
+ const confirm = (prompt, ttyPath = "/dev/tty") => {
74
+ process.stdout.write(`${prompt} (y/N) `);
75
+ const buf = Buffer.alloc(1024);
76
+ let fd;
77
+ let shouldClose = false;
78
+ try {
79
+ fd = openSync(ttyPath, "r");
80
+ shouldClose = true;
81
+ } catch {
82
+ fd = process.stdin.fd;
83
+ }
84
+ const bytesRead = readSync(fd, buf, 0, buf.length, null);
85
+ if (shouldClose) closeSync(fd);
86
+ const answer = buf.slice(0, bytesRead).toString().trim();
87
+ return answer === "y" || answer === "Y";
88
+ };
89
+ const choose = (prompt, options, ttyPath = "/dev/tty") => {
90
+ const keys = options.map((_, i) => String(i + 1));
91
+ const optionList = options.map((o, i) => ` ${i + 1}) ${o.label}`).join("\n");
92
+ process.stdout.write(`${prompt}\n${optionList}\nChoice [${keys.join("/")}]: `);
93
+ const buf = Buffer.alloc(1024);
94
+ let fd;
95
+ let shouldClose = false;
96
+ try {
97
+ fd = openSync(ttyPath, "r");
98
+ shouldClose = true;
99
+ } catch {
100
+ fd = process.stdin.fd;
101
+ }
102
+ const bytesRead = readSync(fd, buf, 0, buf.length, null);
103
+ if (shouldClose) closeSync(fd);
104
+ const answer = buf.slice(0, bytesRead).toString().trim();
105
+ const index = keys.indexOf(answer);
106
+ if (index === -1) {
107
+ console.error(`\nInvalid choice: "${answer}". Expected one of: ${keys.join(", ")}`);
108
+ process.exit(1);
109
+ }
110
+ const chosen = options[index];
111
+ if (!chosen) process.exit(1);
112
+ return chosen.value;
113
+ };
114
+ //#endregion
115
+ 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 };
package/docs/en/README.md CHANGED
@@ -46,18 +46,21 @@ Configuration and usage for each tool in the stack:
46
46
  ### Commands
47
47
 
48
48
  ```bash
49
- pnpm build # Build all packages
50
- pnpm dev # Start development
51
- pnpm fix # Run all fixes and linting
52
- pnpm lint # Run linting only
53
- pnpm report # Run tests with coverage
54
- pnpm changeset # Declare a version bump alongside your change
55
- pnpm ship:staging # Deploy main to staging for validation (optional)
56
- pnpm ship:production # Consume changesets, merge main → production, trigger publish
49
+ pnpm build # Build all packages
50
+ pnpm dev # Start development
51
+ pnpm fix # Run all fixes and linting
52
+ pnpm lint # Run linting only
53
+ pnpm report # Run tests with coverage
54
+ pnpm release # Bump version from conventional commits, update CHANGELOG.md
55
+ pnpm release:patch # Force a patch bump
56
+ pnpm release:minor # Force a minor bump
57
+ pnpm release:major # Force a major bump
58
+ pnpm ship:staging # Deploy main to staging for validation (optional)
59
+ pnpm ship:production # Bump version, merge main → production, trigger publish
57
60
  pnpm ship:hotfix start <name> # Start an urgent fix from production
58
- pnpm ship:hotfix finish # Propagate the hotfix back through production → staging → main
59
- pnpm test # Run tests
60
- pnpm typecheck # TypeScript type checking
61
+ pnpm ship:hotfix finish # Propagate the hotfix back through production → staging → main
62
+ pnpm test # Run tests
63
+ pnpm typecheck # TypeScript type checking
61
64
  ```
62
65
 
63
66
  ### Config Files
@@ -97,7 +97,7 @@ Commit messages are validated by [Commitlint](../tools/commitlint.md) through gi
97
97
 
98
98
  ## Changelog generation
99
99
 
100
- Changelogs are generated from [Changesets](../tools/releases.md) one-line descriptions authors write with `pnpm changeset` alongside their change. `ship-production` consumes pending changesets at ship time and writes them into each package's `CHANGELOG.md`. The changeset description is what users read, so write it for that reader from the start.
100
+ Changelogs are generated by [commit-and-tag-version](../tools/releases.md) from conventional commit subjects. `ship-production` runs `pnpm release` at ship time, which reads commits since the last tag and writes them into `CHANGELOG.md`. The commit subject is what users read, so write it for that reader from the start.
101
101
 
102
102
  ## Related
103
103
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
 
3
3
  title: "Release Workflow"
4
- description: "Branch-based release workflow for Regardio packages — main → staging → production, driven by Changesets."
4
+ description: "Branch-based release workflow for Regardio packages — main → staging → production, driven by commit-and-tag-version."
5
5
  publishedAt: 2026-04-17
6
6
  language: "en"
7
7
  status: "published"
@@ -11,7 +11,7 @@ area: "dev"
11
11
 
12
12
  # Release Workflow
13
13
 
14
- Branch-based release workflow for Regardio packages. `main` → `staging` → `production`. Changesets carry the version bumps; quality gates run locally before any branch promotion. Nothing broken reaches a shared branch.
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.
15
15
 
16
16
  ## Overview
17
17
 
@@ -21,119 +21,111 @@ Branches mirror deployment environments:
21
21
  main → staging (optional) → production
22
22
  ```
23
23
 
24
- - **`main`** — active development, always deployable. Authors add a changeset (`pnpm changeset`) alongside each version-worthy change.
24
+ - **`main`** — active development, always deployable. Version bumps happen at ship time, not per-commit.
25
25
  - **`staging`** — optional validation environment. No versioning happens here.
26
26
  - **`production`** — versioned, published code only.
27
27
 
28
- Changesets decide what bumps. When `ship-production` runs, it consumes every pending changeset, bumps affected packages, rewrites each package's `CHANGELOG.md`, and commits the result on `main` before merging to `production`. CI on `production` runs `changeset publish` to push the new versions to npm.
28
+ When `ship-production` runs, it discovers all publishable workspace packages and asks you to assign a bump type — `skip`, `patch`, `minor`, or `major` — to each one individually. Packages you skip are left untouched. For each package with a bump, it calls `pnpm --filter <package> release:<type>`, which 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.
29
29
 
30
30
  ## How It Works
31
31
 
32
32
  ### Design principles
33
33
 
34
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. **Changesets drive versioning.** A bump is declared in a `.changeset/*.md` file alongside the code change, not chosen at ship time. Every published version corresponds to a set of consumed changesets.
36
- 3. **Version numbers are a production guarantee.** Bumps are applied only at `ship-production` time. This means 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. Either way, `staging` is automatically synced with `production` after each ship.
38
- 5. **Tests are a local gate, not a CI gate.** Quality checks (`build`, `typecheck`, `test`) run on your machine before any commit is made. Broken code cannot enter the repository. CI only runs `build` and `publish`.
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 `skip`, `patch`, `minor`, or `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 (`build`, `typecheck`, `test`) run on your machine before any commit is made. Broken code cannot enter the repository.
39
39
  6. **You always land back on `main`.** Every command returns you to `main` when it finishes.
40
40
 
41
41
  ### Full flow diagram
42
42
 
43
43
  ```text
44
- pnpm changeset (on main, alongside your change)
45
- ┌──────────────────────────┐
46
- write .changeset/<x>.md │
47
- main ───────────────┤ commit + push ├──► main
48
- └──────────────────────────┘
49
-
50
- ship-staging (OPTIONAL)
51
- ┌──────────────────────────┐
52
- main ───────────────┤ quality checks pass ├──► staging (pushed)
53
- │ ff-merge main → staging │
54
- └──────────────────────────┘
55
-
56
- (validated in staging)
57
-
58
-
59
- ship-production
60
- ┌──────────────────────────┐
61
- quality checks on main
62
- │ pnpm changeset version │
63
- main ───────────────┤ bumps + CHANGELOG.md ├──► production
64
- │ chore(release): ... │
65
- │ ff-merge main → prod │
66
- │ ff-merge prod → staging │
67
- │ sync back to main │
68
- └──────────────────────────┘
69
-
70
- (CI: pnpm changeset publish → npm + git tags)
71
-
72
- production
73
-
74
-
75
- ship-hotfix start fix-name
76
- ┌──────────────────────────┐
77
- production ─────────┤ create hotfix/fix-name ├──► hotfix/fix-name
78
- └──────────────────────────┘
79
-
80
- (author fixes + pnpm changeset)
81
-
82
- ship-hotfix finish
83
- ┌──────────────────────────┐
84
- quality checks
85
- │ pnpm changeset version │
86
- hotfix/fix-name ────┤ bumps + CHANGELOG.md ├──► production → staging → main
87
- │ merge to production │
88
- │ propagate to staging │
89
- │ propagate to main │
90
- └──────────────────────────┘
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
+ └──────────────────────────┘
91
86
  ```
92
87
 
93
88
  ### What each branch represents at any point in time
94
89
 
95
90
  | Branch | Contains | Version bumped? |
96
91
  |--------|----------|-----------------|
97
- | `main` | All committed, tested work + pending `.changeset/*.md` files | Only after `ship-production` / `ship-hotfix finish` |
92
+ | `main` | All committed, tested work | Only after `ship-production` / `ship-hotfix finish` |
98
93
  | `staging` | Synced from `production` after each ship, or from `main` via `ship-staging` | After a ship propagates |
99
94
  | `production` | Only shipped, versioned releases | Yes — always |
100
95
 
101
96
  ### CI role
102
97
 
103
- CI is intentionally minimal. It does not re-run tests. On push to `production` it:
98
+ CI is intentionally minimal. On push to `production` it:
104
99
 
105
100
  1. Installs dependencies and builds
106
- 2. Runs `pnpm changeset publish` — publishes every workspace package whose current version is not yet on npm, skipping `private: true` packages, and creates git tags per published package
107
- 3. Pushes the tags
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`)
108
103
 
109
104
  ## Commands
110
105
 
111
106
  | Command | Usage | Purpose |
112
107
  |---------|-------|---------|
113
- | `pnpm changeset` | interactive | Declare a version bump alongside your change |
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 |
114
112
  | `ship-staging` | `ship-staging` | (Optional) Deploy changes to staging for testing |
115
- | `ship-production` | `ship-production` | Consume changesets, merge main → production, trigger publish |
113
+ | `ship-production` | `ship-production` | Bump version, merge main → production, trigger publish |
116
114
  | `ship-hotfix start <name>` | `ship-hotfix start <name>` | Create a hotfix branch from production |
117
- | `ship-hotfix finish` | `ship-hotfix finish` | Consume changesets, propagate hotfix through production → staging → main |
118
-
119
- Bump types (`patch` / `minor` / `major`) are no longer passed on the command line — they come from the changeset files.
115
+ | `ship-hotfix finish` | `ship-hotfix finish` | Bump version, propagate hotfix through production → staging → main |
120
116
 
121
117
  ## Typical Release Flow
122
118
 
123
- ### 1. Author a change with a changeset
119
+ ### 1. Develop with conventional commits
124
120
 
125
- On `main`:
121
+ On `main`, use conventional commits so the bump type is inferred automatically:
126
122
 
127
123
  ```bash
128
- # make your code change
129
- pnpm changeset # choose packages, bump type, write a one-line description
130
- git add .
131
- git commit -m "feat: ..."
132
- git push
124
+ feat: add user authentication # minor bump
125
+ fix: resolve login redirect loop # patch bump
126
+ feat!: redesign authentication API # → major bump
133
127
  ```
134
128
 
135
- A file like `.changeset/lively-otters-hop.md` is committed with the change.
136
-
137
129
  ### 2. Ship to production
138
130
 
139
131
  ```bash
@@ -144,16 +136,17 @@ This will:
144
136
 
145
137
  1. Guard: must be on `main`, working tree clean
146
138
  2. Fetch and verify `staging` + `production` branches exist
147
- 3. Show commits to be shipped, report pending changeset count, ask for confirmation
148
- 4. Run full quality suite on `main` aborts on failure
149
- 5. Run `pnpm changeset version`bumps versions, rewrites `CHANGELOG.md` per package, deletes consumed changeset files
150
- 6. Format modified files
151
- 7. Commit with `chore(release): version packages` on `main`
139
+ 3. Show commits to be shipped
140
+ 4. For each publishable package: choose `skip / patch / minor / major`
141
+ 5. Confirm the planned bumpsabort if declined or all skipped
142
+ 6. Run full quality suite on `main` — aborts on failure
143
+ 7. For each non-skipped package: run `pnpm --filter <pkg> release:<type>` — bumps version, rewrites `CHANGELOG.md`, commits
152
144
  8. Fast-forward merge `main` into `production` and push
153
145
  9. Sync `staging` with `production`
154
- 10. Return to `main`
146
+ 10. Push `main` with `--follow-tags` to push all new version tags
147
+ 11. Return to `main`
155
148
 
156
- CI on `production` runs `pnpm changeset publish` to push the new versions to npm and create tags.
149
+ CI on `production` runs `pnpm -r publish` to push changed packages to npm.
157
150
 
158
151
  ### Option: Test in staging first
159
152
 
@@ -169,32 +162,26 @@ For urgent fixes that must go directly to production:
169
162
 
170
163
  ```bash
171
164
  pnpm ship:hotfix start fix-auth-bug
172
- # apply your fix
173
- pnpm changeset # describe the fix and the bump
165
+ # apply your fix with conventional commits
174
166
  git add . && git commit -m "fix: ..."
175
167
  pnpm ship:hotfix finish
176
168
  ```
177
169
 
178
- `finish` consumes the pending changeset, bumps the version, then merges `hotfix → production → staging → main` and deletes the hotfix branch. CI publishes from `production`.
170
+ `finish` asks you to assign a bump type (`skip`, `patch`, `minor`, or `major`) to each publishable package, runs `pnpm --filter <pkg> release:<type>` 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`.
179
171
 
180
172
  ## Adoption
181
173
 
182
174
  Install `@regardio/dev` and:
183
175
 
184
- 1. **Initialize Changesets** copy the template:
185
-
186
- ```bash
187
- cp -r node_modules/@regardio/dev/templates/changeset .changeset
188
- ```
189
-
190
- 2. **Add the scripts to `package.json`**:
176
+ 1. **Add the scripts to `package.json`**:
191
177
 
192
178
  ```json
193
179
  {
194
180
  "scripts": {
195
- "changeset": "changeset",
196
- "changeset:publish": "changeset publish",
197
- "changeset:version": "changeset version",
181
+ "release": "commit-and-tag-version",
182
+ "release:major": "commit-and-tag-version --release-as major",
183
+ "release:minor": "commit-and-tag-version --release-as minor",
184
+ "release:patch": "commit-and-tag-version --release-as patch",
198
185
  "ship:hotfix": "ship-hotfix",
199
186
  "ship:production": "ship-production",
200
187
  "ship:staging": "ship-staging"
@@ -202,6 +189,15 @@ Install `@regardio/dev` and:
202
189
  }
203
190
  ```
204
191
 
192
+ 2. **Add a `.versionrc.json` to each publishable package** with a scoped `tagPrefix` so tags and changelogs stay isolated per package:
193
+
194
+ ```bash
195
+ cp node_modules/@regardio/dev/templates/versionrc/.versionrc.json packages/my-pkg/.versionrc.json
196
+ # then set "tagPrefix": "@my-scope/my-pkg@v" inside that file
197
+ ```
198
+
199
+ For a single-package repo, put `.versionrc.json` at the root instead.
200
+
205
201
  3. **Create the branches**:
206
202
 
207
203
  ```bash
@@ -210,7 +206,7 @@ Install `@regardio/dev` and:
210
206
  git checkout main
211
207
  ```
212
208
 
213
- 4. **Copy the release workflow** for your forge. A GitHub Actions template ships with `@regardio/dev`; Forgejo Actions accepts the same workflow syntax with minimal changes.
209
+ 4. **Copy the release workflow** for your forge:
214
210
 
215
211
  ```bash
216
212
  # GitHub
@@ -222,8 +218,6 @@ Install `@regardio/dev` and:
222
218
  cp node_modules/@regardio/dev/templates/github/release.yml .forgejo/workflows/release.yml
223
219
  ```
224
220
 
225
- For other CI systems, treat the template as a reference: the workflow installs dependencies, builds, runs `pnpm changeset publish`, and pushes tags on every push to `production`.
226
-
227
221
  5. **First publish of any new package must be done locally**:
228
222
 
229
223
  ```bash
@@ -242,57 +236,7 @@ pnpm test # Must succeed
242
236
 
243
237
  ## Private Packages
244
238
 
245
- Packages that should never be published to npm must set `"private": true` in `package.json`. `changeset publish` skips them automatically. The git flow (`ship-staging`, `ship-production`, `ship-hotfix`) works identically for private packages changesets, version bumps, and branch promotion all continue as normal; only the npm publish step is skipped.
246
-
247
- ## Workspace-Level Automation
248
-
249
- The meta-workspace (`workspace/`) provides scripts that operate across all repos at once. Run these from the `workspace/` directory.
250
-
251
- ### Daily maintenance: safe-upgrade-and-ship
252
-
253
- After a dependency upgrade cycle, ship everything to staging in one command:
254
-
255
- ```bash
256
- cd workspace
257
- pnpm safe-upgrade-and-ship
258
- ```
259
-
260
- What it does:
261
-
262
- 1. Runs `pnpm safe-upgrade` — upgrades all dependencies, runs build + lint + test + typecheck across the full workspace. Aborts if anything fails.
263
- 2. For every repo (workspace itself, commons, ensemble, system, channels/*): checks `git status`.
264
- 3. If only `package.json` and/or `pnpm-lock.yaml` changed: commits with `chore: upgrade dependencies`, pushes `main` to origin, fast-forward merges `main → staging`, pushes `staging`, returns to `main`.
265
- 4. If unexpected files are uncommitted: skips that repo and reports it.
266
-
267
- No further interaction needed — husky pre-commit hooks run on the commit step, and the full quality suite already passed in step 1.
268
-
269
- ### Selective staging ship
270
-
271
- If changes are already committed and ready:
272
-
273
- ```bash
274
- cd workspace
275
- pnpm ship:staging
276
- ```
277
-
278
- Delegates to each repo's `ship:staging` (which re-runs quality checks per repo). Workspace itself is handled directly to avoid recursion.
279
-
280
- ### Production ship
281
-
282
- ```bash
283
- cd workspace
284
- pnpm ship:production
285
- ```
286
-
287
- Calls each sub-repo's `ship:production` in sequence, then promotes workspace itself (`main → production → staging`). For repos with Changesets (commons), this triggers the full version-bump flow. For app repos without Changesets (ensemble, system, channels), it promotes the branch without versioning.
288
-
289
- ### When to use each command
290
-
291
- | Command | When |
292
- |---------|------|
293
- | `pnpm safe-upgrade-and-ship` | Daily/weekly dependency maintenance — upgrade all, verify all, ship all to staging |
294
- | `pnpm ship:staging` | Changes are already committed; just push everything to staging |
295
- | `pnpm ship:production` | Ready to go to production across all repos |
239
+ 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.
296
240
 
297
241
  ## Related
298
242
 
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.1.1",
4
+ "version": "2.3.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": [
@@ -66,7 +66,8 @@
66
66
  "src/markdownlint",
67
67
  "src/typescript",
68
68
  "docs",
69
- "templates"
69
+ "templates",
70
+ "pnpm-lock.yaml"
70
71
  ],
71
72
  "devDependencies": {
72
73
  "@total-typescript/ts-reset": "0.6.1",
@@ -77,13 +78,12 @@
77
78
  },
78
79
  "peerDependencies": {
79
80
  "@biomejs/biome": ">=2",
80
- "@changesets/changelog-git": ">=0.2",
81
- "@changesets/cli": ">=2",
82
81
  "@commitlint/cli": ">=20",
83
82
  "@commitlint/config-conventional": ">=20",
84
83
  "@playwright/test": ">=1",
85
84
  "@testing-library/jest-dom": ">=6",
86
85
  "@testing-library/react": ">=16",
86
+ "commit-and-tag-version": ">=12",
87
87
  "husky": ">=9",
88
88
  "jsdom": ">=29",
89
89
  "markdownlint-cli2": ">=0.22",
@@ -98,6 +98,10 @@
98
98
  "clean": "rimraf .turbo dist",
99
99
  "dev": "tsdown --watch",
100
100
  "fix": "run-s fix:pkg fix:md fix:biome",
101
+ "release": "commit-and-tag-version",
102
+ "release:major": "commit-and-tag-version --release-as major",
103
+ "release:minor": "commit-and-tag-version --release-as minor",
104
+ "release:patch": "commit-and-tag-version --release-as patch",
101
105
  "fix:biome": "biome check --write --unsafe .",
102
106
  "fix:md": "markdownlint-cli2 --config ../../.markdownlint-cli2.jsonc --fix",
103
107
  "fix:pkg": "sort-package-json",
@@ -2,14 +2,15 @@
2
2
  # Copy this file to .github/workflows/release.yml
3
3
  #
4
4
  # Required setup:
5
- # 1. Initialize Changesets in your repo:
6
- # cp -r node_modules/@regardio/dev/templates/changeset .changeset
7
- # 2. Add these scripts to package.json:
8
- # "changeset": "changeset",
9
- # "changeset:publish": "changeset publish",
5
+ # 1. Add these scripts to package.json:
6
+ # "release": "commit-and-tag-version",
7
+ # "release:major": "commit-and-tag-version --release-as major",
8
+ # "release:minor": "commit-and-tag-version --release-as minor",
9
+ # "release:patch": "commit-and-tag-version --release-as patch",
10
10
  # "ship:staging": "ship-staging",
11
11
  # "ship:production": "ship-production",
12
12
  # "ship:hotfix": "ship-hotfix"
13
+ # 2. Copy .versionrc.json from node_modules/@regardio/dev/templates/versionrc/.versionrc.json
13
14
  # 3. Create the branches:
14
15
  # git checkout -b staging && git push -u origin staging
15
16
  # git checkout -b production && git push -u origin production
@@ -17,10 +18,9 @@
17
18
  # must be done locally (`npm publish --access public`).
18
19
  #
19
20
  # Usage:
20
- # - Author adds a changeset on main: `pnpm changeset`
21
- # - `pnpm ship:staging` deploys to staging for validation
22
- # - `pnpm ship:production` consumes changesets locally, bumps versions, merges
23
- # to production. This workflow then publishes to npm.
21
+ # - `pnpm ship:staging` deploys to staging for validation (optional)
22
+ # - `pnpm ship:production` runs commit-and-tag-version locally, bumps the version,
23
+ # merges to production. This workflow then publishes to npm.
24
24
 
25
25
  name: Release
26
26
 
@@ -62,16 +62,7 @@ jobs:
62
62
  run: pnpm -r build
63
63
 
64
64
  - name: Publish changed public packages
65
- # `changeset publish`:
66
- # - walks the workspace topologically
67
- # - replaces `workspace:*` with real versions
68
- # - skips packages where the current version already exists on npm
69
- # - skips packages marked `"private": true`
70
- # - creates git tags like `@scope/pkg@1.2.3` for each published package
71
- run: pnpm changeset publish
65
+ run: pnpm -r publish --access public --no-git-checks
72
66
  env:
73
67
  NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
74
68
  NPM_CONFIG_PROVENANCE: "true"
75
-
76
- - name: Push tags
77
- run: git push --tags
@@ -0,0 +1,16 @@
1
+ {
2
+ "commitAll": false,
3
+ "sign": false,
4
+ "tagPrefix": "v",
5
+ "types": [
6
+ { "section": "Features", "type": "feat" },
7
+ { "section": "Bug Fixes", "type": "fix" },
8
+ { "section": "Performance", "type": "perf" },
9
+ { "section": "Refactoring", "type": "refactor" },
10
+ { "hidden": false, "section": "Chores", "type": "chore" },
11
+ { "hidden": true, "type": "docs" },
12
+ { "hidden": true, "type": "style" },
13
+ { "hidden": true, "type": "test" },
14
+ { "hidden": true, "type": "ci" }
15
+ ]
16
+ }
@@ -1,45 +0,0 @@
1
- #!/usr/bin/env node
2
- import { execFileSync, execSync } from "node:child_process";
3
- import { closeSync, openSync, readSync } from "node:fs";
4
- //#region src/bin/ship/utils.ts
5
- /**
6
- * Shared utilities for ship-staging, ship-production, and ship-hotfix.
7
- *
8
- * Git commands use execFileSync (not a shell string) so user-provided
9
- * strings such as commit messages are never interpolated by the shell.
10
- * pnpm script invocations use execSync via shell since script names are
11
- * developer-controlled and pnpm itself is resolved through PATH.
12
- */
13
- const git = (...args) => {
14
- console.log(`$ git ${args.join(" ")}`);
15
- execFileSync("git", args, { stdio: "inherit" });
16
- };
17
- const gitRead = (...args) => execFileSync("git", args, { encoding: "utf-8" }).trim();
18
- const runScript = (script) => {
19
- console.log(`$ pnpm ${script}`);
20
- execSync(`pnpm ${script}`, { stdio: "inherit" });
21
- };
22
- const runQualityChecks = () => {
23
- runScript("build");
24
- runScript("typecheck");
25
- runScript("test");
26
- };
27
- const branchExists = (name) => gitRead("branch", "--list", name) !== "" || gitRead("branch", "--list", "--remotes", `origin/${name}`) !== "";
28
- const confirm = (prompt, ttyPath = "/dev/tty") => {
29
- process.stdout.write(`${prompt} (y/N) `);
30
- const buf = Buffer.alloc(1024);
31
- let fd;
32
- let shouldClose = false;
33
- try {
34
- fd = openSync(ttyPath, "r");
35
- shouldClose = true;
36
- } catch {
37
- fd = process.stdin.fd;
38
- }
39
- const bytesRead = readSync(fd, buf, 0, buf.length, null);
40
- if (shouldClose) closeSync(fd);
41
- const answer = buf.slice(0, bytesRead).toString().trim();
42
- return answer === "y" || answer === "Y";
43
- };
44
- //#endregion
45
- export { runQualityChecks as a, gitRead as i, confirm as n, runScript as o, git as r, branchExists as t };
@@ -1,14 +0,0 @@
1
- # Changesets
2
-
3
- Pending version bumps live here as small Markdown files. Add one with `pnpm changeset` whenever you make a version-worthy change.
4
-
5
- ## How it fits the GitLab flow
6
-
7
- - On `main`, authors run `pnpm changeset` alongside their change to describe the bump.
8
- - `ship-staging` deploys to staging without touching versions.
9
- - `ship-production` runs quality checks, consumes pending changesets (bumping versions and updating each package's `CHANGELOG.md`), commits the bumps on `main`, and merges to `production`.
10
- - CI on `production` runs `changeset publish` to push the new versions to npm.
11
-
12
- ## Skipping a release
13
-
14
- If a commit is tooling-only and should not trigger a release, don't add a changeset.
@@ -1,11 +0,0 @@
1
- {
2
- "$schema": "https://unpkg.com/@changesets/config@3.1.4/schema.json",
3
- "access": "public",
4
- "baseBranch": "main",
5
- "changelog": "@changesets/changelog-git",
6
- "commit": false,
7
- "fixed": [],
8
- "ignore": [],
9
- "linked": [],
10
- "updateInternalDependencies": "patch"
11
- }