@kaitranntt/ccs 6.5.0-dev.7 → 6.6.0-dev.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaitranntt/ccs",
3
- "version": "6.5.0-dev.7",
3
+ "version": "6.6.0-dev.2",
4
4
  "description": "Claude Code Switch - Instant profile switching between Claude Sonnet 4.5 and GLM 4.6",
5
5
  "keywords": [
6
6
  "cli",
@@ -75,8 +75,6 @@
75
75
  "ui:build": "cd ui && bun run build",
76
76
  "ui:preview": "cd ui && bun run preview",
77
77
  "ui:validate": "cd ui && bun run validate",
78
- "prepublishOnly": "node scripts/sync-version.js",
79
- "prepack": "node scripts/sync-version.js",
80
78
  "prepare": "husky",
81
79
  "postinstall": "node scripts/postinstall.js"
82
80
  },
@@ -98,7 +96,11 @@
98
96
  "@commitlint/cli": "^20.1.0",
99
97
  "@commitlint/config-conventional": "^20.0.0",
100
98
  "@semantic-release/changelog": "^6.0.3",
99
+ "@semantic-release/commit-analyzer": "^13.0.1",
101
100
  "@semantic-release/git": "^10.0.1",
101
+ "@semantic-release/github": "^12.0.2",
102
+ "@semantic-release/npm": "^13.1.3",
103
+ "@semantic-release/release-notes-generator": "^14.1.0",
102
104
  "@tailwindcss/vite": "^4.1.17",
103
105
  "@types/chokidar": "^2.1.7",
104
106
  "@types/express": "^4.17.21",
@@ -108,6 +110,7 @@
108
110
  "@typescript-eslint/eslint-plugin": "^8.48.0",
109
111
  "@typescript-eslint/parser": "^8.48.0",
110
112
  "@vitejs/plugin-react": "^5.1.1",
113
+ "conventional-changelog-conventionalcommits": "^9.1.0",
111
114
  "eslint": "^9.39.1",
112
115
  "eslint-config-prettier": "^10.1.8",
113
116
  "husky": "^9.1.7",
@@ -0,0 +1,214 @@
1
+ /**
2
+ * Send Release Notification to Discord using Embeds
3
+ *
4
+ * Usage:
5
+ * node send-discord-release.cjs <type> <webhook-url>
6
+ *
7
+ * Args:
8
+ * type: 'production' or 'dev'
9
+ * webhook-url: Discord webhook URL
10
+ */
11
+
12
+ const fs = require('fs');
13
+ const https = require('https');
14
+ const { URL } = require('url');
15
+
16
+ const releaseType = process.argv[2]; // 'production' or 'dev'
17
+ const webhookUrl = process.argv[3];
18
+
19
+ if (!releaseType || !webhookUrl) {
20
+ console.error('Usage: node send-discord-release.cjs <type> <webhook-url>');
21
+ process.exit(1);
22
+ }
23
+
24
+ // Validate webhook URL is Discord
25
+ try {
26
+ const parsed = new URL(webhookUrl);
27
+ if (!parsed.hostname.endsWith('discord.com') || !parsed.pathname.startsWith('/api/webhooks/')) {
28
+ console.error('[X] Invalid Discord webhook URL');
29
+ process.exit(1);
30
+ }
31
+ } catch {
32
+ console.error('[X] Invalid URL format');
33
+ process.exit(1);
34
+ }
35
+
36
+ /**
37
+ * Extract latest release from CHANGELOG.md
38
+ */
39
+ function extractLatestRelease() {
40
+ const changelogPath = 'CHANGELOG.md';
41
+
42
+ if (!fs.existsSync(changelogPath)) {
43
+ return {
44
+ version: 'Unknown',
45
+ date: new Date().toISOString().split('T')[0],
46
+ sections: {},
47
+ };
48
+ }
49
+
50
+ const content = fs.readFileSync(changelogPath, 'utf8');
51
+ const lines = content.split('\n');
52
+
53
+ let version = 'Unknown';
54
+ let date = new Date().toISOString().split('T')[0];
55
+ let collecting = false;
56
+ let currentSection = null;
57
+ const sections = {};
58
+
59
+ for (const line of lines) {
60
+ // Match: ## [1.0.0](url) (2025-01-01) or ## 1.0.0 (2025-01-01)
61
+ const versionMatch = line.match(/^## \[?(\d+\.\d+\.\d+(?:-dev\.\d+)?)\]?.*?\((\d{4}-\d{2}-\d{2})\)/);
62
+ if (versionMatch) {
63
+ if (!collecting) {
64
+ version = versionMatch[1];
65
+ date = versionMatch[2];
66
+ collecting = true;
67
+ continue;
68
+ } else {
69
+ break; // Found next version, stop
70
+ }
71
+ }
72
+
73
+ if (!collecting) continue;
74
+
75
+ // Match section headers: ### Features, ### Bug Fixes
76
+ const sectionMatch = line.match(/^### (.+)/);
77
+ if (sectionMatch) {
78
+ currentSection = sectionMatch[1];
79
+ sections[currentSection] = [];
80
+ continue;
81
+ }
82
+
83
+ // Collect bullet points
84
+ if (currentSection && line.trim().startsWith('*')) {
85
+ const item = line.trim().substring(1).trim();
86
+ if (item) {
87
+ sections[currentSection].push(item);
88
+ }
89
+ }
90
+ }
91
+
92
+ return { version, date, sections };
93
+ }
94
+
95
+ /**
96
+ * Create Discord embed
97
+ */
98
+ function createEmbed(release) {
99
+ const isDev = releaseType === 'dev';
100
+ const color = isDev ? 0xf59e0b : 0x10b981; // Orange for dev, Green for production
101
+ const title = isDev ? `Dev Release ${release.version}` : `Release ${release.version}`;
102
+ const url = `https://github.com/kaitranntt/ccs/releases/tag/v${release.version}`;
103
+
104
+ // Section name to indicator mapping (ASCII only per CLAUDE.md)
105
+ const sectionIndicators = {
106
+ Features: '[+]',
107
+ 'Bug Fixes': '[X]',
108
+ Documentation: '[i]',
109
+ Styles: '[~]',
110
+ 'Code Refactoring': '[~]',
111
+ 'Performance Improvements': '[!]',
112
+ Tests: '[T]',
113
+ 'Build System': '[B]',
114
+ CI: '[C]',
115
+ };
116
+
117
+ const fields = [];
118
+
119
+ for (const [sectionName, items] of Object.entries(release.sections)) {
120
+ if (items.length === 0) continue;
121
+
122
+ const indicator = sectionIndicators[sectionName] || '[*]';
123
+ let fieldValue = items.map((item) => `• ${item}`).join('\n');
124
+
125
+ // Discord field value max is 1024 characters
126
+ if (fieldValue.length > 1024) {
127
+ const truncateAt = fieldValue.lastIndexOf('\n', 1000);
128
+ fieldValue = fieldValue.substring(0, truncateAt > 0 ? truncateAt : 1000) + '\n... *(truncated)*';
129
+ }
130
+
131
+ fields.push({
132
+ name: `${indicator} ${sectionName}`,
133
+ value: fieldValue,
134
+ inline: false,
135
+ });
136
+ }
137
+
138
+ if (fields.length === 0) {
139
+ fields.push({
140
+ name: '[i] Release Notes',
141
+ value: 'Release completed. See changelog on GitHub.',
142
+ inline: false,
143
+ });
144
+ }
145
+
146
+ return {
147
+ title,
148
+ url,
149
+ color,
150
+ timestamp: new Date().toISOString(),
151
+ footer: {
152
+ text: isDev ? 'npm i @kaitranntt/ccs@dev' : 'npm i @kaitranntt/ccs@latest',
153
+ },
154
+ fields,
155
+ };
156
+ }
157
+
158
+ /**
159
+ * Send to Discord webhook
160
+ */
161
+ function sendToDiscord(embed) {
162
+ const payload = {
163
+ username: releaseType === 'dev' ? 'CCS Dev Release' : 'CCS Release',
164
+ embeds: [embed],
165
+ };
166
+
167
+ const url = new URL(webhookUrl);
168
+ const options = {
169
+ hostname: url.hostname,
170
+ path: url.pathname + url.search,
171
+ method: 'POST',
172
+ headers: {
173
+ 'Content-Type': 'application/json',
174
+ },
175
+ };
176
+
177
+ const req = https.request(options, (res) => {
178
+ let data = '';
179
+
180
+ res.on('data', (chunk) => {
181
+ data += chunk;
182
+ });
183
+
184
+ res.on('end', () => {
185
+ if (res.statusCode >= 200 && res.statusCode < 300) {
186
+ console.log('[OK] Discord notification sent');
187
+ } else {
188
+ console.error(`[X] Discord webhook failed: ${res.statusCode}`);
189
+ console.error(data);
190
+ process.exit(1);
191
+ }
192
+ });
193
+ });
194
+
195
+ req.on('error', (error) => {
196
+ console.error('[X] Error sending Discord notification:', error);
197
+ process.exit(1);
198
+ });
199
+
200
+ req.write(JSON.stringify(payload));
201
+ req.end();
202
+ }
203
+
204
+ // Main
205
+ try {
206
+ const release = extractLatestRelease();
207
+ console.log(`[i] Preparing ${releaseType} notification for v${release.version}`);
208
+
209
+ const embed = createEmbed(release);
210
+ sendToDiscord(embed);
211
+ } catch (error) {
212
+ console.error('[X] Error:', error);
213
+ process.exit(1);
214
+ }
package/VERSION DELETED
@@ -1 +0,0 @@
1
- 6.5.0-dev.7
@@ -1,41 +0,0 @@
1
- /**
2
- * semantic-release plugin to sync VERSION file
3
- *
4
- * semantic-release updates package.json but not the VERSION file.
5
- * This plugin keeps VERSION in sync for shell scripts and installers.
6
- */
7
- const fs = require('fs');
8
- const path = require('path');
9
-
10
- module.exports = {
11
- /**
12
- * Called during the prepare step before git commit
13
- */
14
- prepare(_pluginConfig, context) {
15
- const { nextRelease, logger } = context;
16
- const versionFile = path.join(process.cwd(), 'VERSION');
17
-
18
- // Write version without 'v' prefix (e.g., "5.1.0" not "v5.1.0")
19
- const version = nextRelease.version;
20
- fs.writeFileSync(versionFile, version + '\n');
21
- logger.log('[sync-version-plugin] Updated VERSION file to %s', version);
22
-
23
- // Also update installers for standalone installs
24
- const installSh = path.join(process.cwd(), 'installers', 'install.sh');
25
- const installPs1 = path.join(process.cwd(), 'installers', 'install.ps1');
26
-
27
- if (fs.existsSync(installSh)) {
28
- let content = fs.readFileSync(installSh, 'utf8');
29
- content = content.replace(/^CCS_VERSION=".*"/m, `CCS_VERSION="${version}"`);
30
- fs.writeFileSync(installSh, content);
31
- logger.log('[sync-version-plugin] Updated installers/install.sh');
32
- }
33
-
34
- if (fs.existsSync(installPs1)) {
35
- let content = fs.readFileSync(installPs1, 'utf8');
36
- content = content.replace(/^\$CcsVersion = ".*"/m, `$CcsVersion = "${version}"`);
37
- fs.writeFileSync(installPs1, content);
38
- logger.log('[sync-version-plugin] Updated installers/install.ps1');
39
- }
40
- }
41
- };
@@ -1,15 +0,0 @@
1
- #!/usr/bin/env node
2
- const fs = require('fs');
3
- const path = require('path');
4
-
5
- // Read VERSION file
6
- const versionFile = path.join(__dirname, '..', 'VERSION');
7
- const version = fs.readFileSync(versionFile, 'utf8').trim();
8
-
9
- // Update package.json
10
- const pkgPath = path.join(__dirname, '..', 'package.json');
11
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
12
- pkg.version = version;
13
- fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
14
-
15
- console.log(`✓ Synced version ${version} to package.json`);