steadylog 0.1.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/README.md +26 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +45 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +13 -0
- package/dist/lib/api.d.ts +13 -0
- package/dist/lib/api.js +20 -0
- package/dist/lib/auth.d.ts +7 -0
- package/dist/lib/auth.js +127 -0
- package/dist/lib/config.d.ts +2 -0
- package/dist/lib/config.js +10 -0
- package/dist/lib/detect.d.ts +6 -0
- package/dist/lib/detect.js +50 -0
- package/dist/lib/files.d.ts +6 -0
- package/dist/lib/files.js +24 -0
- package/dist/templates/STEADYLOG.md +39 -0
- package/dist/templates/steadylog-claude.md +23 -0
- package/dist/templates/steadylog-codex.md +16 -0
- package/dist/templates/steadylog.mdc +71 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# steadylog
|
|
2
|
+
|
|
3
|
+
Connect a GitHub repo and set up [Steadylog](https://steadylog-phi.vercel.app) changelogs from your terminal.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx steadylog init
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
This will:
|
|
12
|
+
|
|
13
|
+
1. Authenticate with GitHub
|
|
14
|
+
2. Connect your repo and register a webhook
|
|
15
|
+
3. Add AI agent rule files (Cursor, Claude Code, Codex, or Windsurf)
|
|
16
|
+
4. Create a `STEADYLOG.md` config in your repo
|
|
17
|
+
|
|
18
|
+
## Local development
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
STEADYLOG_APP_URL=http://localhost:3000 npx steadylog init
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## License
|
|
25
|
+
|
|
26
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function init(): Promise<void>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import inquirer from "inquirer";
|
|
3
|
+
import ora from "ora";
|
|
4
|
+
import { authenticate } from "../lib/auth.js";
|
|
5
|
+
import { connectRepo, listRepos } from "../lib/api.js";
|
|
6
|
+
import { detectAgent } from "../lib/detect.js";
|
|
7
|
+
import { createConfig, placeRuleFile } from "../lib/files.js";
|
|
8
|
+
export async function init() {
|
|
9
|
+
console.log(chalk.green("\n Welcome to Steadylog 🪵\n"));
|
|
10
|
+
const spinner = ora();
|
|
11
|
+
spinner.start("Authenticating with GitHub...");
|
|
12
|
+
const token = await authenticate();
|
|
13
|
+
spinner.succeed(`Authenticated as @${token.github_username}`);
|
|
14
|
+
const repos = await listRepos(token);
|
|
15
|
+
const { repo } = await inquirer.prompt([
|
|
16
|
+
{
|
|
17
|
+
type: "list",
|
|
18
|
+
name: "repo",
|
|
19
|
+
message: "Which repo is this for?",
|
|
20
|
+
choices: repos.map((r) => ({ name: r.full_name, value: r })),
|
|
21
|
+
},
|
|
22
|
+
]);
|
|
23
|
+
spinner.start("Connecting repo...");
|
|
24
|
+
const project = await connectRepo(token, repo);
|
|
25
|
+
spinner.succeed(`Repo connected — ${project.slug}`);
|
|
26
|
+
const agent = await detectAgent();
|
|
27
|
+
if (agent) {
|
|
28
|
+
spinner.start(`Setting up ${agent.name} integration...`);
|
|
29
|
+
await placeRuleFile(agent);
|
|
30
|
+
spinner.succeed(`Added Steadylog rules to ${agent.ruleFilePath}`);
|
|
31
|
+
}
|
|
32
|
+
await createConfig({
|
|
33
|
+
slug: project.slug,
|
|
34
|
+
repo: repo.full_name,
|
|
35
|
+
});
|
|
36
|
+
spinner.succeed("Created STEADYLOG.md");
|
|
37
|
+
console.log(`
|
|
38
|
+
✅ You're all set.
|
|
39
|
+
|
|
40
|
+
Your changelog: ${chalk.green(project.changelog_url)}
|
|
41
|
+
|
|
42
|
+
Push your next release tag to ship your first changelog.
|
|
43
|
+
Your AI agent will handle the rest.
|
|
44
|
+
`);
|
|
45
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { init } from "./commands/init.js";
|
|
4
|
+
const program = new Command();
|
|
5
|
+
program
|
|
6
|
+
.name("steadylog")
|
|
7
|
+
.description("Automated changelogs from GitHub releases")
|
|
8
|
+
.version("0.1.0");
|
|
9
|
+
program
|
|
10
|
+
.command("init")
|
|
11
|
+
.description("Connect a repo and set up Steadylog in your project")
|
|
12
|
+
.action(init);
|
|
13
|
+
program.parse();
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { SteadylogToken } from "./auth.js";
|
|
2
|
+
export type GitHubRepo = {
|
|
3
|
+
id: number;
|
|
4
|
+
full_name: string;
|
|
5
|
+
name: string;
|
|
6
|
+
private: boolean;
|
|
7
|
+
};
|
|
8
|
+
export type ConnectedProject = {
|
|
9
|
+
slug: string;
|
|
10
|
+
changelog_url: string;
|
|
11
|
+
};
|
|
12
|
+
export declare function listRepos(token: SteadylogToken): Promise<GitHubRepo[]>;
|
|
13
|
+
export declare function connectRepo(token: SteadylogToken, repo: GitHubRepo): Promise<ConnectedProject>;
|
package/dist/lib/api.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import { getApiUrl } from "./config.js";
|
|
3
|
+
function authHeaders(token) {
|
|
4
|
+
return {
|
|
5
|
+
Authorization: `Bearer ${token.access_token}`,
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
export async function listRepos(token) {
|
|
9
|
+
const { data } = await axios.get(`${getApiUrl()}/api/cli/repos`, {
|
|
10
|
+
headers: authHeaders(token),
|
|
11
|
+
});
|
|
12
|
+
return data;
|
|
13
|
+
}
|
|
14
|
+
export async function connectRepo(token, repo) {
|
|
15
|
+
const { data } = await axios.post(`${getApiUrl()}/api/cli/connect`, {
|
|
16
|
+
repo_full_name: repo.full_name,
|
|
17
|
+
github_repo_id: repo.id,
|
|
18
|
+
}, { headers: authHeaders(token) });
|
|
19
|
+
return data;
|
|
20
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type SteadylogToken = {
|
|
2
|
+
access_token: string;
|
|
3
|
+
github_username: string;
|
|
4
|
+
};
|
|
5
|
+
export type StoredConfig = SteadylogToken;
|
|
6
|
+
export declare function loadConfig(): Promise<StoredConfig | null>;
|
|
7
|
+
export declare function authenticate(): Promise<SteadylogToken>;
|
package/dist/lib/auth.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import http from "node:http";
|
|
3
|
+
import { URL } from "node:url";
|
|
4
|
+
import axios from "axios";
|
|
5
|
+
import fs from "fs-extra";
|
|
6
|
+
import open from "open";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import os from "node:os";
|
|
9
|
+
import { getApiUrl, getAppUrl } from "./config.js";
|
|
10
|
+
const DEFAULT_CALLBACK_PORT = 9898;
|
|
11
|
+
const CALLBACK_PORT_ATTEMPTS = 10;
|
|
12
|
+
const CALLBACK_TIMEOUT_MS = 10 * 60 * 1000;
|
|
13
|
+
const CONFIG_DIR = path.join(os.homedir(), ".steadylog");
|
|
14
|
+
const CONFIG_PATH = path.join(CONFIG_DIR, "config.json");
|
|
15
|
+
function generateRandomString(length) {
|
|
16
|
+
return crypto.randomBytes(length).toString("base64url");
|
|
17
|
+
}
|
|
18
|
+
function generateCodeChallenge(verifier) {
|
|
19
|
+
return crypto.createHash("sha256").update(verifier).digest("base64url");
|
|
20
|
+
}
|
|
21
|
+
async function saveConfig(token) {
|
|
22
|
+
await fs.ensureDir(CONFIG_DIR);
|
|
23
|
+
await fs.writeJson(CONFIG_PATH, token, { spaces: 2 });
|
|
24
|
+
}
|
|
25
|
+
export async function loadConfig() {
|
|
26
|
+
if (!(await fs.pathExists(CONFIG_PATH))) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
return fs.readJson(CONFIG_PATH);
|
|
30
|
+
}
|
|
31
|
+
function listenForCallback(port, state) {
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
const server = http.createServer((req, res) => {
|
|
34
|
+
if (!req.url?.startsWith("/callback")) {
|
|
35
|
+
res.writeHead(404);
|
|
36
|
+
res.end();
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const url = new URL(req.url, `http://localhost:${port}`);
|
|
40
|
+
const code = url.searchParams.get("code");
|
|
41
|
+
const returnedState = url.searchParams.get("state");
|
|
42
|
+
if (!code || !returnedState) {
|
|
43
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
44
|
+
res.end("<h1>Missing code or state</h1>");
|
|
45
|
+
cleanup(new Error("OAuth callback missing code or state"));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (returnedState !== state) {
|
|
49
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
50
|
+
res.end("<h1>State mismatch</h1>");
|
|
51
|
+
cleanup(new Error("OAuth state mismatch"));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
55
|
+
res.end("<h1>Authenticated!</h1><p>You can close this tab and return to the terminal.</p>");
|
|
56
|
+
cleanup(null, { code, state: returnedState });
|
|
57
|
+
});
|
|
58
|
+
const timeout = setTimeout(() => {
|
|
59
|
+
cleanup(new Error("Timed out waiting for GitHub authentication"));
|
|
60
|
+
}, CALLBACK_TIMEOUT_MS);
|
|
61
|
+
function cleanup(error, result) {
|
|
62
|
+
clearTimeout(timeout);
|
|
63
|
+
server.close(() => {
|
|
64
|
+
if (error) {
|
|
65
|
+
reject(error);
|
|
66
|
+
}
|
|
67
|
+
else if (result) {
|
|
68
|
+
resolve(result);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
server.on("error", (error) => {
|
|
73
|
+
clearTimeout(timeout);
|
|
74
|
+
if (error.code === "EADDRINUSE") {
|
|
75
|
+
reject(new Error(`Port ${port} is already in use. Kill the stale process with: lsof -ti :${port} | xargs kill`));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
reject(error);
|
|
79
|
+
});
|
|
80
|
+
server.listen(port, "127.0.0.1");
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
async function findAvailableCallbackPort() {
|
|
84
|
+
const envPort = process.env.STEADYLOG_CALLBACK_PORT;
|
|
85
|
+
if (envPort) {
|
|
86
|
+
return Number(envPort);
|
|
87
|
+
}
|
|
88
|
+
for (let i = 0; i < CALLBACK_PORT_ATTEMPTS; i++) {
|
|
89
|
+
const port = DEFAULT_CALLBACK_PORT + i;
|
|
90
|
+
try {
|
|
91
|
+
await new Promise((resolve, reject) => {
|
|
92
|
+
const probe = http.createServer();
|
|
93
|
+
probe.once("error", reject);
|
|
94
|
+
probe.listen(port, "127.0.0.1", () => {
|
|
95
|
+
probe.close((err) => (err ? reject(err) : resolve()));
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
return port;
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
const err = error;
|
|
102
|
+
if (err.code !== "EADDRINUSE") {
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
throw new Error(`No available callback port (${DEFAULT_CALLBACK_PORT}-${DEFAULT_CALLBACK_PORT + CALLBACK_PORT_ATTEMPTS - 1}). Kill stale processes with: lsof -i :${DEFAULT_CALLBACK_PORT}`);
|
|
108
|
+
}
|
|
109
|
+
export async function authenticate() {
|
|
110
|
+
const state = generateRandomString(32);
|
|
111
|
+
const codeVerifier = generateRandomString(64);
|
|
112
|
+
const codeChallenge = generateCodeChallenge(codeVerifier);
|
|
113
|
+
const callbackPort = await findAvailableCallbackPort();
|
|
114
|
+
const authUrl = new URL(`${getAppUrl()}/cli-auth`);
|
|
115
|
+
authUrl.searchParams.set("state", state);
|
|
116
|
+
authUrl.searchParams.set("code_challenge", codeChallenge);
|
|
117
|
+
authUrl.searchParams.set("callback_port", String(callbackPort));
|
|
118
|
+
const callbackPromise = listenForCallback(callbackPort, state);
|
|
119
|
+
await open(authUrl.toString());
|
|
120
|
+
const { code } = await callbackPromise;
|
|
121
|
+
const { data } = await axios.post(`${getApiUrl()}/api/cli/token`, {
|
|
122
|
+
code,
|
|
123
|
+
code_verifier: codeVerifier,
|
|
124
|
+
});
|
|
125
|
+
await saveConfig(data);
|
|
126
|
+
return data;
|
|
127
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
function trimTrailingSlash(url) {
|
|
2
|
+
return url.replace(/\/$/, "");
|
|
3
|
+
}
|
|
4
|
+
const PRODUCTION_APP_URL = "https://steadylog-phi.vercel.app";
|
|
5
|
+
export function getAppUrl() {
|
|
6
|
+
return trimTrailingSlash(process.env.STEADYLOG_APP_URL ?? PRODUCTION_APP_URL);
|
|
7
|
+
}
|
|
8
|
+
export function getApiUrl() {
|
|
9
|
+
return trimTrailingSlash(process.env.STEADYLOG_API_URL ?? getAppUrl());
|
|
10
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import fs from "fs-extra";
|
|
2
|
+
import inquirer from "inquirer";
|
|
3
|
+
const AGENT_CHOICES = {
|
|
4
|
+
Cursor: {
|
|
5
|
+
name: "Cursor",
|
|
6
|
+
ruleFilePath: ".cursor/rules/steadylog.mdc",
|
|
7
|
+
templateFile: "steadylog.mdc",
|
|
8
|
+
},
|
|
9
|
+
"Claude Code": {
|
|
10
|
+
name: "Claude Code",
|
|
11
|
+
ruleFilePath: "CLAUDE.md",
|
|
12
|
+
templateFile: "steadylog-claude.md",
|
|
13
|
+
},
|
|
14
|
+
Codex: {
|
|
15
|
+
name: "Codex",
|
|
16
|
+
ruleFilePath: "AGENTS.md",
|
|
17
|
+
templateFile: "steadylog-codex.md",
|
|
18
|
+
},
|
|
19
|
+
Windsurf: {
|
|
20
|
+
name: "Windsurf",
|
|
21
|
+
ruleFilePath: ".windsurfcontext",
|
|
22
|
+
templateFile: "steadylog-claude.md",
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
export async function detectAgent() {
|
|
26
|
+
if (await fs.pathExists(".cursor")) {
|
|
27
|
+
return AGENT_CHOICES.Cursor;
|
|
28
|
+
}
|
|
29
|
+
if (await fs.pathExists("CLAUDE.md")) {
|
|
30
|
+
return AGENT_CHOICES["Claude Code"];
|
|
31
|
+
}
|
|
32
|
+
if (await fs.pathExists("AGENTS.md")) {
|
|
33
|
+
return AGENT_CHOICES.Codex;
|
|
34
|
+
}
|
|
35
|
+
if (await fs.pathExists(".windsurfcontext")) {
|
|
36
|
+
return AGENT_CHOICES.Windsurf;
|
|
37
|
+
}
|
|
38
|
+
const { agent } = await inquirer.prompt([
|
|
39
|
+
{
|
|
40
|
+
type: "list",
|
|
41
|
+
name: "agent",
|
|
42
|
+
message: "Which AI coding tool do you use?",
|
|
43
|
+
choices: ["Cursor", "Claude Code", "Codex", "Windsurf", "None / Skip"],
|
|
44
|
+
},
|
|
45
|
+
]);
|
|
46
|
+
if (agent === "None / Skip") {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
return AGENT_CHOICES[agent];
|
|
50
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import fs from "fs-extra";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
5
|
+
const TEMPLATES_DIR = path.join(__dirname, "..", "templates");
|
|
6
|
+
function readTemplate(filename) {
|
|
7
|
+
return fs.readFileSync(path.join(TEMPLATES_DIR, filename), "utf-8");
|
|
8
|
+
}
|
|
9
|
+
export async function placeRuleFile(agent) {
|
|
10
|
+
const template = readTemplate(agent.templateFile);
|
|
11
|
+
if (agent.name === "Claude Code" || agent.name === "Codex") {
|
|
12
|
+
await fs.appendFile(agent.ruleFilePath, "\n\n" + template);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
await fs.ensureDir(path.dirname(agent.ruleFilePath));
|
|
16
|
+
await fs.writeFile(agent.ruleFilePath, template);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export async function createConfig({ slug, repo, }) {
|
|
20
|
+
const template = readTemplate("STEADYLOG.md")
|
|
21
|
+
.replace("{{slug}}", slug)
|
|
22
|
+
.replace("{{repo}}", repo);
|
|
23
|
+
await fs.writeFile("STEADYLOG.md", template);
|
|
24
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Steadylog config
|
|
2
|
+
# Docs: https://steadylog.dev/docs/config
|
|
3
|
+
|
|
4
|
+
project: {{slug}}
|
|
5
|
+
repo: {{repo}}
|
|
6
|
+
|
|
7
|
+
# How changelog entries are published
|
|
8
|
+
# auto: publishes immediately on release
|
|
9
|
+
# manual: sends email for one-click approval (recommended)
|
|
10
|
+
publish: manual
|
|
11
|
+
|
|
12
|
+
# What triggers a changelog generation
|
|
13
|
+
# tag: when you push a git tag (v1.2.3)
|
|
14
|
+
# weekly: every Monday, bundles the week's PRs
|
|
15
|
+
release_trigger: tag
|
|
16
|
+
|
|
17
|
+
# Writing tone for AI-generated entries
|
|
18
|
+
# professional | casual | technical
|
|
19
|
+
tone: professional
|
|
20
|
+
|
|
21
|
+
# How your product is referred to in the changelog
|
|
22
|
+
# product_name: Acme
|
|
23
|
+
|
|
24
|
+
# Paths to always ignore (changes here never appear in changelog)
|
|
25
|
+
ignore_paths:
|
|
26
|
+
- .github/
|
|
27
|
+
- tests/
|
|
28
|
+
- __tests__/
|
|
29
|
+
- "*.test.ts"
|
|
30
|
+
- "*.spec.ts"
|
|
31
|
+
- infrastructure/
|
|
32
|
+
- scripts/
|
|
33
|
+
- docs/
|
|
34
|
+
|
|
35
|
+
# Slack webhook for notifications (optional)
|
|
36
|
+
# slack_webhook: https://hooks.slack.com/services/...
|
|
37
|
+
|
|
38
|
+
# Email to notify on new draft (defaults to GitHub account email)
|
|
39
|
+
# notify_email: team@yourcompany.com
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
## Steadylog (Changelog Automation)
|
|
2
|
+
|
|
3
|
+
This repo uses Steadylog to auto-generate changelogs. Follow these rules.
|
|
4
|
+
|
|
5
|
+
### PR labeling
|
|
6
|
+
Add a `changelog` label to PRs that affect users:
|
|
7
|
+
✅ New features, bug fixes users notice, perf improvements, API changes
|
|
8
|
+
❌ Deps, refactors, CI, tests, infra, comments
|
|
9
|
+
|
|
10
|
+
### PR description
|
|
11
|
+
For changelog PRs, append to the PR body:
|
|
12
|
+
```
|
|
13
|
+
## Steadylog
|
|
14
|
+
**Type:** feature | improvement | fix
|
|
15
|
+
**Summary:** One sentence for users, not engineers.
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### Releases
|
|
19
|
+
Always use `v` prefix: `v1.2.3`
|
|
20
|
+
After tagging: "Steadylog will generate your changelog — check email to approve."
|
|
21
|
+
|
|
22
|
+
### Not set up yet?
|
|
23
|
+
Run: npx steadylog init
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
## Steadylog changelog rules
|
|
2
|
+
|
|
3
|
+
Repo uses Steadylog (npx steadylog init to set up).
|
|
4
|
+
|
|
5
|
+
PR labels:
|
|
6
|
+
- Add `changelog` label if change affects end users
|
|
7
|
+
- Skip for: deps, refactors, CI, tests, infra
|
|
8
|
+
|
|
9
|
+
PR description (changelog PRs only):
|
|
10
|
+
```
|
|
11
|
+
## Steadylog
|
|
12
|
+
**Type:** feature | improvement | fix
|
|
13
|
+
**Summary:** <one user-facing sentence>
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Releases: use semver with v prefix (v1.2.3). Steadylog auto-generates changelog on tag push.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Steadylog changelog automation rules
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Steadylog
|
|
7
|
+
|
|
8
|
+
This repo uses Steadylog (steadylog.dev) to automatically generate changelogs
|
|
9
|
+
from GitHub releases. Follow these rules on every PR and release.
|
|
10
|
+
|
|
11
|
+
## PR rules
|
|
12
|
+
|
|
13
|
+
When creating or reviewing a PR, decide if the change is user-facing:
|
|
14
|
+
|
|
15
|
+
USER-FACING (add `changelog` label):
|
|
16
|
+
- New features or UI changes
|
|
17
|
+
- Bug fixes that affected user experience
|
|
18
|
+
- Performance improvements users will notice
|
|
19
|
+
- New API endpoints or changed API behavior
|
|
20
|
+
- Changes to pricing, limits, or permissions
|
|
21
|
+
|
|
22
|
+
NOT USER-FACING (no label needed):
|
|
23
|
+
- Dependency updates
|
|
24
|
+
- Refactors with no behavior change
|
|
25
|
+
- CI/CD changes
|
|
26
|
+
- Test additions
|
|
27
|
+
- Code comments or documentation
|
|
28
|
+
- Infrastructure / DevOps changes
|
|
29
|
+
- Internal tooling
|
|
30
|
+
|
|
31
|
+
## PR description format
|
|
32
|
+
|
|
33
|
+
For changelog-labeled PRs, always include this at the bottom of the PR description:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
## Steadylog
|
|
37
|
+
**Type:** feature | improvement | fix (pick one)
|
|
38
|
+
**Summary:** One sentence describing what changed from the user's perspective.
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Example:
|
|
42
|
+
```
|
|
43
|
+
## Steadylog
|
|
44
|
+
**Type:** feature
|
|
45
|
+
**Summary:** Users can now export their dashboard data as a CSV file.
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
This helps Steadylog's AI write a better changelog entry.
|
|
49
|
+
|
|
50
|
+
## Release rules
|
|
51
|
+
|
|
52
|
+
When the developer asks you to cut a release or ship a version:
|
|
53
|
+
|
|
54
|
+
1. Remind them to use semver format: `v1.2.3` (with the `v` prefix)
|
|
55
|
+
2. Suggest the right version bump:
|
|
56
|
+
- New features → minor bump (v1.1.0 → v1.2.0)
|
|
57
|
+
- Bug fixes only → patch bump (v1.1.0 → v1.1.1)
|
|
58
|
+
- Breaking changes → major bump (v1.1.0 → v2.0.0)
|
|
59
|
+
3. After tagging, remind them: "Steadylog will generate your changelog automatically. Check your email to approve it."
|
|
60
|
+
|
|
61
|
+
## STEADYLOG.md
|
|
62
|
+
|
|
63
|
+
This repo has a STEADYLOG.md config file. Respect its settings:
|
|
64
|
+
- `ignore_paths` — never suggest changelog labels for PRs that only touch these paths
|
|
65
|
+
- `tone` — match this tone when writing PR summaries
|
|
66
|
+
- `release_trigger` — how releases are detected
|
|
67
|
+
|
|
68
|
+
## Setup
|
|
69
|
+
|
|
70
|
+
If Steadylog is not yet set up for this repo, tell the developer to run:
|
|
71
|
+
npx steadylog init
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "steadylog",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Automated changelogs from GitHub releases",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"steadylog": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "rm -rf dist && tsc && cp -r src/templates dist/templates",
|
|
14
|
+
"dev": "tsx src/index.ts",
|
|
15
|
+
"prepublishOnly": "npm run build"
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/majamil16/steadylog.git",
|
|
20
|
+
"directory": "packages/cli"
|
|
21
|
+
},
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/majamil16/steadylog/issues"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://steadylog-phi.vercel.app",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"keywords": [
|
|
28
|
+
"changelog",
|
|
29
|
+
"github",
|
|
30
|
+
"releases",
|
|
31
|
+
"cli",
|
|
32
|
+
"steadylog"
|
|
33
|
+
],
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"axios": "^1.0.0",
|
|
36
|
+
"chalk": "^5.0.0",
|
|
37
|
+
"commander": "^11.0.0",
|
|
38
|
+
"fs-extra": "^11.0.0",
|
|
39
|
+
"inquirer": "^9.0.0",
|
|
40
|
+
"open": "^9.0.0",
|
|
41
|
+
"ora": "^7.0.0"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/fs-extra": "^11.0.0",
|
|
45
|
+
"@types/inquirer": "^9.0.0",
|
|
46
|
+
"@types/node": "^20.0.0",
|
|
47
|
+
"tsx": "^4.0.0",
|
|
48
|
+
"typescript": "^5.0.0"
|
|
49
|
+
},
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=18"
|
|
52
|
+
}
|
|
53
|
+
}
|