Skip to content

Commit 667187a

Browse files
committed
setup auto release
1 parent d3753bb commit 667187a

File tree

4 files changed

+332
-3
lines changed

4 files changed

+332
-3
lines changed

package.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@
1515
"version:minor": "npm version minor",
1616
"version:major": "npm version major",
1717
"prepublishOnly": "npm run build",
18-
"release:patch": "npm run version:patch && npm publish",
19-
"release:minor": "npm run version:minor && npm publish",
20-
"release:major": "npm run version:major && npm publish"
18+
"release:patch": "npm run version:patch && git push && git push --tags && npm publish && npm run github:release",
19+
"release:minor": "npm run version:minor && git push && git push --tags && npm publish && npm run github:release",
20+
"release:major": "npm run version:major && git push && git push --tags && npm publish && npm run github:release",
21+
"release:auto": "node scripts/auto-release.js",
22+
"github:release": "node scripts/github-release.js",
23+
"pre-release": "git add . && git status"
2124
},
2225
"repository": {
2326
"type": "git",

scripts/README.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Release Automation Scripts
2+
3+
This directory contains scripts to automate the release process for the react-query-external-sync package.
4+
5+
## Scripts
6+
7+
### `auto-release.js`
8+
9+
The main automated release script that handles the entire release workflow:
10+
11+
- ✅ Checks for uncommitted changes and offers to commit them
12+
- ✅ Interactive version selection (patch/minor/major)
13+
- ✅ Builds the package
14+
- ✅ Bumps version and creates git tag
15+
- ✅ Pushes changes and tags to git
16+
- ✅ Publishes to npm
17+
- ✅ Creates GitHub release with auto-generated release notes
18+
19+
**Usage:**
20+
21+
```bash
22+
npm run release:auto
23+
```
24+
25+
### `github-release.js`
26+
27+
Creates a GitHub release with auto-generated release notes based on commits since the last tag.
28+
29+
**Usage:**
30+
31+
```bash
32+
npm run github:release
33+
```
34+
35+
## Setup for GitHub Releases
36+
37+
To enable automatic GitHub release creation, you need to set up a GitHub token:
38+
39+
1. Go to https://github.com/settings/tokens
40+
2. Create a new token with "repo" permissions
41+
3. Add it to your environment:
42+
```bash
43+
export GITHUB_TOKEN=your_token_here
44+
```
45+
4. Or add it to your `~/.zshrc` or `~/.bashrc` for persistence
46+
47+
## Available npm Scripts
48+
49+
- `npm run release:auto` - Interactive automated release
50+
- `npm run release:patch` - Direct patch release
51+
- `npm run release:minor` - Direct minor release
52+
- `npm run release:major` - Direct major release
53+
- `npm run github:release` - Create GitHub release only
54+
- `npm run pre-release` - Check git status before release

scripts/auto-release.js

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
#!/usr/bin/env node
2+
3+
const { execSync } = require("child_process");
4+
const readline = require("readline");
5+
6+
const rl = readline.createInterface({
7+
input: process.stdin,
8+
output: process.stdout,
9+
});
10+
11+
function execCommand(command, description) {
12+
console.log(`\n🔄 ${description}...`);
13+
try {
14+
const output = execSync(command, { encoding: "utf8", stdio: "inherit" });
15+
console.log(`✅ ${description} completed`);
16+
return output;
17+
} catch (error) {
18+
console.error(`❌ ${description} failed:`, error.message);
19+
process.exit(1);
20+
}
21+
}
22+
23+
function askQuestion(question) {
24+
return new Promise((resolve) => {
25+
rl.question(question, (answer) => {
26+
resolve(answer.trim().toLowerCase());
27+
});
28+
});
29+
}
30+
31+
async function main() {
32+
console.log("🚀 React Query External Sync - Automated Release\n");
33+
34+
// Check if there are uncommitted changes
35+
try {
36+
execSync("git diff --exit-code", { stdio: "ignore" });
37+
execSync("git diff --cached --exit-code", { stdio: "ignore" });
38+
} catch (error) {
39+
console.log(
40+
"📝 You have uncommitted changes. Let me show you what needs to be committed:\n"
41+
);
42+
execCommand("git status", "Checking git status");
43+
44+
const shouldCommit = await askQuestion(
45+
"\n❓ Do you want to commit these changes? (y/n): "
46+
);
47+
if (shouldCommit === "y" || shouldCommit === "yes") {
48+
const commitMessage = await askQuestion(
49+
'💬 Enter commit message (or press Enter for "chore: update package"): '
50+
);
51+
const message = commitMessage || "chore: update package";
52+
execCommand("git add .", "Staging changes");
53+
execCommand(`git commit -m "${message}"`, "Committing changes");
54+
} else {
55+
console.log("❌ Please commit your changes before releasing");
56+
process.exit(1);
57+
}
58+
}
59+
60+
console.log("\n📦 What type of release is this?");
61+
console.log("1. patch (2.2.0 → 2.2.1) - Bug fixes");
62+
console.log("2. minor (2.2.0 → 2.3.0) - New features");
63+
console.log("3. major (2.2.0 → 3.0.0) - Breaking changes");
64+
65+
const versionType = await askQuestion(
66+
"\n❓ Enter your choice (1/2/3 or patch/minor/major): "
67+
);
68+
69+
let releaseType;
70+
switch (versionType) {
71+
case "1":
72+
case "patch":
73+
releaseType = "patch";
74+
break;
75+
case "2":
76+
case "minor":
77+
releaseType = "minor";
78+
break;
79+
case "3":
80+
case "major":
81+
releaseType = "major";
82+
break;
83+
default:
84+
console.log("❌ Invalid choice. Defaulting to patch release.");
85+
releaseType = "patch";
86+
}
87+
88+
console.log(`\n🎯 Proceeding with ${releaseType} release...\n`);
89+
90+
// Confirm before proceeding
91+
const confirm = await askQuestion(
92+
`❓ Are you sure you want to release a ${releaseType} version? (y/n): `
93+
);
94+
if (confirm !== "y" && confirm !== "yes") {
95+
console.log("❌ Release cancelled");
96+
process.exit(0);
97+
}
98+
99+
rl.close();
100+
101+
// Execute the release
102+
console.log("\n🚀 Starting automated release process...\n");
103+
104+
try {
105+
// Build the package
106+
execCommand("npm run build", "Building package");
107+
108+
// Version bump (this also creates a git tag)
109+
execCommand(`npm version ${releaseType}`, `Bumping ${releaseType} version`);
110+
111+
// Push changes and tags
112+
execCommand("git push", "Pushing changes to git");
113+
execCommand("git push --tags", "Pushing tags to git");
114+
115+
// Publish to npm
116+
execCommand("npm publish", "Publishing to npm");
117+
118+
// Create GitHub release
119+
execCommand("npm run github:release", "Creating GitHub release");
120+
121+
console.log("\n🎉 Release completed successfully!");
122+
console.log("✅ Version bumped and committed");
123+
console.log("✅ Changes pushed to git");
124+
console.log("✅ Package published to npm");
125+
console.log("✅ GitHub release created");
126+
} catch (error) {
127+
console.error("\n❌ Release failed:", error.message);
128+
process.exit(1);
129+
}
130+
}
131+
132+
main().catch(console.error);

scripts/github-release.js

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
#!/usr/bin/env node
2+
3+
const { execSync } = require("child_process");
4+
const https = require("https");
5+
const fs = require("fs");
6+
7+
function execCommand(command) {
8+
try {
9+
return execSync(command, { encoding: "utf8" }).trim();
10+
} catch (error) {
11+
console.error(`Command failed: ${command}`);
12+
throw error;
13+
}
14+
}
15+
16+
function makeGitHubRequest(options, data) {
17+
return new Promise((resolve, reject) => {
18+
const req = https.request(options, (res) => {
19+
let body = "";
20+
res.on("data", (chunk) => (body += chunk));
21+
res.on("end", () => {
22+
if (res.statusCode >= 200 && res.statusCode < 300) {
23+
resolve(JSON.parse(body));
24+
} else {
25+
reject(new Error(`GitHub API error: ${res.statusCode} - ${body}`));
26+
}
27+
});
28+
});
29+
30+
req.on("error", reject);
31+
32+
if (data) {
33+
req.write(JSON.stringify(data));
34+
}
35+
36+
req.end();
37+
});
38+
}
39+
40+
async function createGitHubRelease() {
41+
try {
42+
// Get package info
43+
const packageJson = JSON.parse(fs.readFileSync("package.json", "utf8"));
44+
const version = packageJson.version;
45+
const tagName = `v${version}`;
46+
47+
// Get repository info from package.json
48+
const repoUrl = packageJson.repository.url;
49+
const repoMatch = repoUrl.match(/github\.com[\/:]([^\/]+)\/([^\/\.]+)/);
50+
51+
if (!repoMatch) {
52+
throw new Error("Could not parse GitHub repository from package.json");
53+
}
54+
55+
const owner = repoMatch[1];
56+
const repo = repoMatch[2];
57+
58+
// Get GitHub token from environment
59+
const token = process.env.GITHUB_TOKEN;
60+
if (!token) {
61+
console.log("⚠️ GITHUB_TOKEN not found in environment variables.");
62+
console.log("📝 To create GitHub releases automatically, please:");
63+
console.log(" 1. Go to https://github.com/settings/tokens");
64+
console.log(' 2. Create a new token with "repo" permissions');
65+
console.log(
66+
" 3. Add it to your environment: export GITHUB_TOKEN=your_token"
67+
);
68+
console.log(" 4. Or add it to your ~/.zshrc or ~/.bashrc");
69+
console.log("\n✅ For now, you can manually create a release at:");
70+
console.log(
71+
` https://github.com/${owner}/${repo}/releases/new?tag=${tagName}`
72+
);
73+
return;
74+
}
75+
76+
// Get recent commits for release notes
77+
let releaseNotes = "";
78+
try {
79+
const lastTag = execCommand(
80+
'git describe --tags --abbrev=0 HEAD~1 2>/dev/null || echo ""'
81+
);
82+
const commitRange = lastTag ? `${lastTag}..HEAD` : "HEAD";
83+
const commits = execCommand(
84+
`git log ${commitRange} --pretty=format:"- %s" --no-merges`
85+
);
86+
releaseNotes = commits || "- Initial release";
87+
} catch (error) {
88+
releaseNotes = "- Package updates and improvements";
89+
}
90+
91+
// Create the release
92+
const releaseData = {
93+
tag_name: tagName,
94+
target_commitish: "main",
95+
name: `Release ${tagName}`,
96+
body: `## Changes\n\n${releaseNotes}\n\n## Installation\n\n\`\`\`bash\nnpm install ${packageJson.name}@${version}\n\`\`\``,
97+
draft: false,
98+
prerelease: version.includes("-"),
99+
};
100+
101+
const options = {
102+
hostname: "api.github.com",
103+
port: 443,
104+
path: `/repos/${owner}/${repo}/releases`,
105+
method: "POST",
106+
headers: {
107+
Authorization: `token ${token}`,
108+
"User-Agent": "npm-release-script",
109+
"Content-Type": "application/json",
110+
Accept: "application/vnd.github.v3+json",
111+
},
112+
};
113+
114+
console.log(`🔄 Creating GitHub release for ${tagName}...`);
115+
const release = await makeGitHubRequest(options, releaseData);
116+
117+
console.log(`✅ GitHub release created successfully!`);
118+
console.log(`🔗 Release URL: ${release.html_url}`);
119+
} catch (error) {
120+
console.error("❌ Failed to create GitHub release:", error.message);
121+
122+
// Provide fallback instructions
123+
const packageJson = JSON.parse(fs.readFileSync("package.json", "utf8"));
124+
const version = packageJson.version;
125+
const tagName = `v${version}`;
126+
const repoUrl = packageJson.repository.url;
127+
const repoMatch = repoUrl.match(/github\.com[\/:]([^\/]+)\/([^\/\.]+)/);
128+
129+
if (repoMatch) {
130+
const owner = repoMatch[1];
131+
const repo = repoMatch[2];
132+
console.log(`\n📝 You can manually create the release at:`);
133+
console.log(
134+
` https://github.com/${owner}/${repo}/releases/new?tag=${tagName}`
135+
);
136+
}
137+
}
138+
}
139+
140+
createGitHubRelease();

0 commit comments

Comments
 (0)