@regardio/dev 2.1.0 → 2.2.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.
- package/dist/bin/ship/hotfix.bin.mjs +33 -46
- package/dist/bin/ship/production.bin.mjs +31 -46
- package/dist/bin/ship/staging.bin.mjs +1 -1
- package/dist/bin/ship/{utils-BQ-JZ2D5.mjs → utils-CGXmpKxp.mjs} +42 -1
- package/docs/en/README.md +14 -11
- package/docs/en/standards/commits.md +1 -1
- package/docs/en/tools/releases.md +90 -96
- package/package.json +8 -4
- package/templates/github/release.yml +10 -19
- package/templates/versionrc/.versionrc.json +16 -0
- package/templates/changeset/README.md +0 -14
- package/templates/changeset/config.json +0 -11
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as
|
|
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 choose, o as gitRead, s as runQualityChecks, t as branchExists } from "./utils-CGXmpKxp.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,
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* CI on `production` handles
|
|
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
|
|
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,35 @@ function runShipHotfix(subcommand, subArgs, cwd = process.cwd()) {
|
|
|
71
60
|
process.exit(1);
|
|
72
61
|
}
|
|
73
62
|
console.log("✅ Quality checks passed");
|
|
74
|
-
const
|
|
75
|
-
if (
|
|
76
|
-
console.error("No
|
|
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
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
});
|
|
100
|
-
} catch {}
|
|
68
|
+
const packageName = packages.length === 1 ? packages[0]?.name ?? "" : choose("Which package to ship?", packages.map((p) => ({
|
|
69
|
+
label: p.name,
|
|
70
|
+
value: p.name
|
|
71
|
+
})));
|
|
72
|
+
if (!packageName) {
|
|
73
|
+
console.error("Could not determine package to ship.");
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
const bumpType = choose("Select version bump:", [
|
|
77
|
+
{
|
|
78
|
+
label: "patch — bug fixes",
|
|
79
|
+
value: "patch"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
label: "minor — new features",
|
|
83
|
+
value: "minor"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
label: "major — breaking changes",
|
|
87
|
+
value: "major"
|
|
101
88
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
89
|
+
]);
|
|
90
|
+
console.log("\nBumping version and updating CHANGELOG...");
|
|
91
|
+
runScript(`--filter ${packageName} release:${bumpType}`);
|
|
105
92
|
console.log("\nFetching latest state from origin...");
|
|
106
93
|
git("fetch", "origin");
|
|
107
94
|
console.log("\nMerging hotfix into production...");
|
|
@@ -118,7 +105,7 @@ function runShipHotfix(subcommand, subArgs, cwd = process.cwd()) {
|
|
|
118
105
|
git("checkout", "main");
|
|
119
106
|
git("pull", "--ff-only", "origin", "main");
|
|
120
107
|
git("merge", "--no-ff", "staging", "-m", "chore(hotfix): merge staging into main");
|
|
121
|
-
git("push", "origin", "main");
|
|
108
|
+
git("push", "--follow-tags", "origin", "main");
|
|
122
109
|
git("branch", "-d", currentBranch);
|
|
123
110
|
try {
|
|
124
111
|
git("push", "origin", "--delete", currentBranch);
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as
|
|
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 choose, o as gitRead, r as confirm, s as runQualityChecks, t as branchExists } from "./utils-CGXmpKxp.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 → (
|
|
10
|
+
* main → (commit-and-tag-version) → production → staging → main
|
|
14
11
|
*
|
|
15
|
-
* Versioning is driven by
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* CI on `production` handles
|
|
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,19 @@ function runShipProduction(cwd = process.cwd()) {
|
|
|
45
42
|
}
|
|
46
43
|
console.log("\nCommits to be shipped to production:");
|
|
47
44
|
console.log(ahead);
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
const packages = getWorkspacePackages(cwd);
|
|
46
|
+
if (packages.length === 0) {
|
|
47
|
+
console.error("No publishable workspace packages found.");
|
|
50
48
|
process.exit(1);
|
|
51
49
|
}
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
const packageName = packages.length === 1 ? packages[0]?.name ?? "" : choose("Which package to ship?", packages.map((p) => ({
|
|
51
|
+
label: p.name,
|
|
52
|
+
value: p.name
|
|
53
|
+
})));
|
|
54
|
+
if (!packageName) {
|
|
55
|
+
console.error("Could not determine package to ship.");
|
|
55
56
|
process.exit(1);
|
|
56
57
|
}
|
|
57
|
-
const packageJsonPath = join(cwd, "package.json");
|
|
58
|
-
if (!existsSync(packageJsonPath)) {
|
|
59
|
-
console.error("No package.json found in current directory.");
|
|
60
|
-
process.exit(1);
|
|
61
|
-
}
|
|
62
|
-
const { name: packageName } = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
63
|
-
console.log(`\n${pendingChangesets} pending changeset(s) will be consumed.`);
|
|
64
58
|
if (!confirm(`Ship ${packageName} to production?`)) {
|
|
65
59
|
console.log("Aborted.");
|
|
66
60
|
process.exit(0);
|
|
@@ -73,31 +67,22 @@ function runShipProduction(cwd = process.cwd()) {
|
|
|
73
67
|
process.exit(1);
|
|
74
68
|
}
|
|
75
69
|
console.log("✅ Quality checks passed");
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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 {}
|
|
70
|
+
const bumpType = choose("Select version bump:", [
|
|
71
|
+
{
|
|
72
|
+
label: "patch — bug fixes",
|
|
73
|
+
value: "patch"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
label: "minor — new features",
|
|
77
|
+
value: "minor"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
label: "major — breaking changes",
|
|
81
|
+
value: "major"
|
|
97
82
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
83
|
+
]);
|
|
84
|
+
console.log("\nBumping version and updating CHANGELOG...");
|
|
85
|
+
runScript(`--filter ${packageName} release:${bumpType}`);
|
|
101
86
|
console.log("\nMerging main into production...");
|
|
102
87
|
git("checkout", "production");
|
|
103
88
|
git("pull", "--ff-only", "origin", "production");
|
|
@@ -109,7 +94,7 @@ function runShipProduction(cwd = process.cwd()) {
|
|
|
109
94
|
git("merge", "--ff-only", "production");
|
|
110
95
|
git("push", "origin", "staging");
|
|
111
96
|
git("checkout", "main");
|
|
112
|
-
git("push", "origin", "main");
|
|
97
|
+
git("push", "--follow-tags", "origin", "main");
|
|
113
98
|
console.log(`\n✅ Shipped ${packageName} to production. CI will publish changed packages to npm.`);
|
|
114
99
|
console.log("You are on main and ready to keep working.");
|
|
115
100
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as
|
|
2
|
+
import { a as git, o as gitRead, s as runQualityChecks, t as branchExists } from "./utils-CGXmpKxp.mjs";
|
|
3
3
|
import { existsSync, readFileSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
//#region src/bin/ship/staging.ts
|
|
@@ -24,6 +24,22 @@ const runQualityChecks = () => {
|
|
|
24
24
|
runScript("typecheck");
|
|
25
25
|
runScript("test");
|
|
26
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
|
+
};
|
|
27
43
|
const branchExists = (name) => gitRead("branch", "--list", name) !== "" || gitRead("branch", "--list", "--remotes", `origin/${name}`) !== "";
|
|
28
44
|
const confirm = (prompt, ttyPath = "/dev/tty") => {
|
|
29
45
|
process.stdout.write(`${prompt} (y/N) `);
|
|
@@ -41,5 +57,30 @@ const confirm = (prompt, ttyPath = "/dev/tty") => {
|
|
|
41
57
|
const answer = buf.slice(0, bytesRead).toString().trim();
|
|
42
58
|
return answer === "y" || answer === "Y";
|
|
43
59
|
};
|
|
60
|
+
const choose = (prompt, options, ttyPath = "/dev/tty") => {
|
|
61
|
+
const keys = options.map((_, i) => String(i + 1));
|
|
62
|
+
const optionList = options.map((o, i) => ` ${i + 1}) ${o.label}`).join("\n");
|
|
63
|
+
process.stdout.write(`${prompt}\n${optionList}\nChoice [${keys.join("/")}]: `);
|
|
64
|
+
const buf = Buffer.alloc(1024);
|
|
65
|
+
let fd;
|
|
66
|
+
let shouldClose = false;
|
|
67
|
+
try {
|
|
68
|
+
fd = openSync(ttyPath, "r");
|
|
69
|
+
shouldClose = true;
|
|
70
|
+
} catch {
|
|
71
|
+
fd = process.stdin.fd;
|
|
72
|
+
}
|
|
73
|
+
const bytesRead = readSync(fd, buf, 0, buf.length, null);
|
|
74
|
+
if (shouldClose) closeSync(fd);
|
|
75
|
+
const answer = buf.slice(0, bytesRead).toString().trim();
|
|
76
|
+
const index = keys.indexOf(answer);
|
|
77
|
+
if (index === -1) {
|
|
78
|
+
console.error(`\nInvalid choice: "${answer}". Expected one of: ${keys.join(", ")}`);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
const chosen = options[index];
|
|
82
|
+
if (!chosen) process.exit(1);
|
|
83
|
+
return chosen.value;
|
|
84
|
+
};
|
|
44
85
|
//#endregion
|
|
45
|
-
export {
|
|
86
|
+
export { git as a, runScript as c, getWorkspacePackages as i, choose 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
|
|
50
|
-
pnpm dev
|
|
51
|
-
pnpm fix
|
|
52
|
-
pnpm lint
|
|
53
|
-
pnpm report
|
|
54
|
-
pnpm
|
|
55
|
-
pnpm
|
|
56
|
-
pnpm
|
|
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
|
|
59
|
-
pnpm test
|
|
60
|
-
pnpm typecheck
|
|
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
|
|
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
|
|
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`.
|
|
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.
|
|
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
|
-
|
|
28
|
+
When `ship-production` runs, it discovers publishable workspace packages. In a monorepo with multiple packages, you choose which package to ship; in a single-package repo the choice is automatic. Then you choose the bump type (`patch`, `minor`, `major`) and 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` before merging to `production`. CI on `production` publishes changed packages to npm.
|
|
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. **
|
|
36
|
-
3. **Version numbers are a production guarantee.** Bumps are applied only at `ship-production` time.
|
|
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.
|
|
35
|
+
2. **You choose the bump at ship time.** When running `ship-production` or `ship-hotfix finish`, you are prompted to select `patch`, `minor`, or `major`. The conventional commits in the log inform that choice; the decision is 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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
|
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.
|
|
98
|
+
CI is intentionally minimal. On push to `production` it:
|
|
104
99
|
|
|
105
100
|
1. Installs dependencies and builds
|
|
106
|
-
2. Runs `pnpm
|
|
107
|
-
3.
|
|
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
|
|
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` |
|
|
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` |
|
|
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.
|
|
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
|
-
#
|
|
129
|
-
|
|
130
|
-
|
|
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,
|
|
139
|
+
3. Show commits to be shipped, ask for confirmation
|
|
148
140
|
4. Run full quality suite on `main` — aborts on failure
|
|
149
|
-
5.
|
|
150
|
-
6.
|
|
151
|
-
7.
|
|
141
|
+
5. If multiple publishable packages exist: prompt for which package to ship
|
|
142
|
+
6. Prompt for bump type: `patch`, `minor`, or `major`
|
|
143
|
+
7. Run `pnpm --filter <package> 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.
|
|
146
|
+
10. Push `main` with `--follow-tags` to push the version tag
|
|
147
|
+
11. Return to `main`
|
|
155
148
|
|
|
156
|
-
CI on `production` runs `pnpm
|
|
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`
|
|
170
|
+
`finish` prompts for a bump type (`patch`, `minor`, or `major`), runs `pnpm release:<type>` to bump the version 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. **
|
|
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
|
-
"
|
|
196
|
-
"
|
|
197
|
-
"
|
|
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
|
|
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,7 +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`. `
|
|
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.
|
|
246
240
|
|
|
247
241
|
## Related
|
|
248
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.
|
|
4
|
+
"version": "2.2.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.
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
# "
|
|
9
|
-
# "
|
|
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
|
-
# -
|
|
21
|
-
# - `pnpm ship:
|
|
22
|
-
#
|
|
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
|
-
|
|
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,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
|
-
}
|