chore: prepare dev release

This commit is contained in:
CrazyBoyM 2025-08-31 02:16:59 +08:00
parent fdf27ed0b7
commit 01e8827fde
7 changed files with 492 additions and 132 deletions

View File

@ -1,5 +1,8 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx lint-staged
# Temporarily disabled - no lint-staged configuration
# npx lint-staged
echo "Pre-commit hook: skipping lint-staged (not configured)"

169
PUBLISH_GUIDE.md Normal file
View File

@ -0,0 +1,169 @@
# 发包脚本使用指南
Kode 项目提供了两套发包流程,用于不同的发布场景:
## 🚀 快速使用
### 开发版本发布 (测试用)
```bash
npm run publish:dev
```
### 正式版本发布
```bash
npm run publish:release
```
## 📦 发包策略
### 1. 开发版本 (`dev` tag)
- **目的**: 内部测试和预发布验证
- **版本格式**: `1.1.16-dev.1`, `1.1.16-dev.2`
- **安装方式**: `npm install -g @shareai-lab/kode@dev`
- **特点**:
- 自动递增 dev 版本号
- 不影响正式版本的用户
- 可以快速迭代测试
### 2. 正式版本 (`latest` tag)
- **目的**: 面向最终用户的稳定版本
- **版本格式**: `1.1.16`, `1.1.17`, `1.2.0`
- **安装方式**: `npm install -g @shareai-lab/kode` (默认)
- **特点**:
- 语义化版本控制
- 严格的发布流程
- 包含完整的测试和检查
## 🛠️ 脚本功能详解
### 开发版本发布 (`scripts/publish-dev.js`)
**自动化流程**:
1. ✅ 检查当前分支和工作区状态
2. 🔢 自动生成递增的 dev 版本号
3. 🔨 构建项目
4. 🔍 运行预发布检查
5. 📤 发布到 npm 的 `dev` tag
6. 🏷️ 创建 git tag
7. 🔄 恢复 package.json (不提交版本变更)
**使用场景**:
- 功能开发完成,需要内部测试
- PR 合并前的最终验证
- 快速修复验证
**安全特性**:
- 临时修改 package.json发布后自动恢复
- 失败时自动回滚
- 不污染主分支版本号
### 正式版本发布 (`scripts/publish-release.js`)
**交互式流程**:
1. 🔍 检查分支 (建议在 main/master)
2. 🧹 确保工作区干净
3. 📡 拉取最新代码
4. 🔢 选择版本升级类型:
- **patch** (1.1.16 → 1.1.17): 修复 bug
- **minor** (1.1.16 → 1.2.0): 新功能
- **major** (1.1.16 → 2.0.0): 破坏性变更
- **custom**: 自定义版本号
5. ✅ 确认发布信息
6. 🧪 运行测试和类型检查
7. 🔨 构建项目
8. 📝 提交版本更新
9. 🏷️ 创建 git tag
10. 📤 发布到 npm (默认 `latest` tag)
11. 📡 推送到 git 仓库
**安全特性**:
- 交互式确认,避免误发布
- 测试失败时自动回滚版本号
- 完整的 git 历史记录
## 🎯 最佳实践
### 开发流程建议
```bash
# 1. 开发功能
git checkout -b feature/new-feature
# ... 开发代码 ...
git commit -am "feat: add new feature"
# 2. 发布开发版本测试
npm run publish:dev
# 安装测试: npm install -g @shareai-lab/kode@dev
# 3. 测试通过后合并到主分支
git checkout main
git merge feature/new-feature
# 4. 发布正式版本
npm run publish:release
```
### 版本号管理
- **开发版**: 基于当前正式版本自动递增
- **正式版**: 遵循 [语义化版本](https://semver.org/lang/zh-CN/) 规范
- **Git 标签**: 自动创建,格式 `v1.1.16`
### 标签管理
```bash
# 查看所有版本
npm view @shareai-lab/kode versions --json
# 查看 dev 版本
npm view @shareai-lab/kode@dev version
# 查看最新正式版本
npm view @shareai-lab/kode@latest version
```
## 🔧 故障排除
### 常见问题
**发布失败怎么办?**
- 脚本会自动回滚 package.json
- 检查错误信息,修复后重新运行
**版本号冲突?**
- 开发版本会自动递增,不会冲突
- 正式版本发布前会检查是否已存在
**权限问题?**
- 确保已登录 npm: `npm whoami`
- 确保有包的发布权限
**Git 相关错误?**
- 确保有 git 推送权限
- 检查远程仓库配置: `git remote -v`
### 手动清理
```bash
# 如果发布过程中断,可能需要手动清理
git tag -d v1.1.16-dev.1 # 删除本地标签
git push origin :v1.1.16-dev.1 # 删除远程标签
```
## 📊 监控和分析
```bash
# 查看包下载统计
npm view @shareai-lab/kode
# 查看所有版本的详细信息
npm view @shareai-lab/kode versions --json
# 测试安装
npm install -g @shareai-lab/kode@dev
kode --version
```
---
通过这套双发包系统,你可以:
- 🚀 快速发布开发版本进行内部测试
- 🛡️ 安全发布正式版本给最终用户
- 📈 保持清晰的版本管理和发布历史
- ⚡ 自动化大部分重复操作,减少人为错误

View File

@ -1,15 +1,15 @@
{
"name": "@shareai-lab/kode",
"version": "1.1.15",
"version": "1.1.16",
"bin": {
"kode": "dist/index.js",
"kwa": "dist/index.js",
"kd": "dist/index.js"
"kode": "cli.js",
"kwa": "cli.js",
"kd": "cli.js"
},
"engines": {
"node": ">=20.18.1"
},
"main": "dist/index.js",
"main": "cli.js",
"author": "ShareAI-lab <ai-lab@foxmail.com>",
"license": "Apache-2.0",
"description": "AI-powered terminal assistant that understands your codebase, edits files, runs commands, and automates development workflows.",
@ -25,7 +25,6 @@
"cli.js",
"yoga.wasm",
"dist/**/*",
"src/**/*",
"scripts/postinstall.js",
".npmrc"
],
@ -33,7 +32,7 @@
"dev": "bun run ./src/entrypoints/cli.tsx --verbose",
"build": "node scripts/build.mjs",
"clean": "rm -rf cli.js",
"prepublishOnly": "bun run build && node scripts/prepublish-check.js",
"prepublishOnly": "node scripts/build.mjs && node scripts/prepublish-check.js",
"postinstall": "node scripts/postinstall.js || true",
"format": "prettier --write \"src/**/*.{ts,tsx,js,jsx,json}\"",
"format:check": "prettier --check \"src/**/*.{ts,tsx,js,jsx,json}\"",
@ -41,100 +40,8 @@
"lint:fix": "eslint . --ext .ts,.tsx,.js --fix",
"test": "bun test",
"typecheck": "tsc --noEmit",
"prepare": "husky install"
},
"optionalDependencies": {
"@img/sharp-darwin-arm64": "^0.33.5",
"@img/sharp-linux-arm": "^0.33.5",
"@img/sharp-linux-x64": "^0.33.5",
"@img/sharp-win32-x64": "^0.33.5"
},
"dependencies": {
"@anthropic-ai/bedrock-sdk": "^0.12.6",
"@anthropic-ai/sdk": "^0.39.0",
"@anthropic-ai/vertex-sdk": "^0.7.0",
"@commander-js/extra-typings": "^13.1.0",
"@inkjs/ui": "^2.0.0",
"@modelcontextprotocol/sdk": "^1.15.1",
"@statsig/js-client": "^3.18.2",
"@types/lodash-es": "^4.17.12",
"@types/react": "^19.1.12",
"ansi-escapes": "^7.0.0",
"chalk": "^5.4.1",
"cli-highlight": "^2.1.11",
"cli-table3": "^0.6.5",
"commander": "^13.1.0",
"debug": "^4.4.1",
"diff": "^7.0.0",
"dotenv": "^16.6.1",
"env-paths": "^3.0.0",
"figures": "^6.1.0",
"glob": "^11.0.3",
"gray-matter": "^4.0.3",
"highlight.js": "^11.11.1",
"ink": "^6.2.3",
"ink-link": "^4.1.0",
"ink-select-input": "^6.2.0",
"ink-text-input": "^6.0.0",
"lodash-es": "^4.17.21",
"lru-cache": "^11.1.0",
"marked": "^15.0.12",
"nanoid": "^5.1.5",
"node-fetch": "^3.3.2",
"node-html-parser": "^7.0.1",
"openai": "^4.104.0",
"react": "^19.1.1",
"semver": "^7.7.2",
"sharp": "^0.34.3",
"shell-quote": "^1.8.3",
"spawn-rx": "^5.1.2",
"tsx": "^4.20.3",
"turndown": "^7.2.1",
"undici": "^7.11.0",
"wrap-ansi": "^9.0.0",
"zod": "^3.25.76",
"zod-to-json-schema": "^3.24.6"
},
"devDependencies": {
"@types/bun": "latest",
"@types/jest": "^30.0.0",
"@types/node": "^24.1.0",
"@typescript-eslint/eslint-plugin": "^8.4.0",
"@typescript-eslint/parser": "^8.4.0",
"esbuild": "^0.23.0",
"eslint": "^9.9.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-react": "^7.36.1",
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
"husky": "^9.1.6",
"lint-staged": "^15.2.9",
"@types/sharp": "^0.32.0",
"bun-types": "latest",
"prettier": "^3.6.2",
"typescript": "^5.9.2"
},
"lint-staged": {
"*.{ts,tsx,js,jsx,json}": [
"prettier --write",
"eslint --fix"
]
},
"overrides": {
"string-width": "^7.2.0",
"strip-ansi": "^7.1.0"
},
"directories": {
"doc": "docs",
"test": "test"
},
"keywords": [
"cli",
"ai",
"assistant",
"agent",
"kode",
"shareai",
"terminal",
"command-line"
]
"prepare": "",
"publish:dev": "node scripts/publish-dev.js",
"publish:release": "node scripts/publish-release.js"
}
}

View File

@ -46,7 +46,7 @@ function fixRelativeImports(dir) {
// Handle: dynamic import('...')
text = text.replace(/(import\(\s*['"])(\.{1,2}\/[^'"\n]+)(['"]\s*\))/gm, (m, a, spec, c) => {
if (/\.(js|json|node|mjs|cjs)$/.test(spec)) return m
return a + spec + '.js' + c + ')'
return a + spec + '.js' + c
})
writeFileSync(p, text)
}

104
scripts/publish-dev.js Executable file
View File

@ -0,0 +1,104 @@
#!/usr/bin/env node
const { execSync } = require('child_process');
const { readFileSync, writeFileSync } = require('fs');
const path = require('path');
/**
* 发布开发版本到 npm
* 使用 -dev tag版本号自动递增 dev 后缀
*/
async function publishDev() {
try {
console.log('🚀 Starting dev version publish process...\n');
// 1. 确保在正确的分支
const currentBranch = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf8' }).trim();
console.log(`📍 Current branch: ${currentBranch}`);
// 2. 检查工作区是否干净
try {
execSync('git diff --exit-code', { stdio: 'ignore' });
execSync('git diff --cached --exit-code', { stdio: 'ignore' });
} catch {
console.log('⚠️ Working directory has uncommitted changes, committing...');
execSync('git add .');
execSync('git commit -m "chore: prepare dev release"');
}
// 3. 读取当前版本
const packagePath = path.join(process.cwd(), 'package.json');
const packageJson = JSON.parse(readFileSync(packagePath, 'utf8'));
const baseVersion = packageJson.version;
// 4. 生成开发版本号
let devVersion;
try {
// 获取当前 dev tag 的最新版本
const npmResult = execSync(`npm view @shareai-lab/kode@dev version`, { encoding: 'utf8' }).trim();
const currentDevVersion = npmResult;
if (currentDevVersion.startsWith(baseVersion + '-dev.')) {
const devNumber = parseInt(currentDevVersion.split('-dev.')[1]) + 1;
devVersion = `${baseVersion}-dev.${devNumber}`;
} else {
devVersion = `${baseVersion}-dev.1`;
}
} catch {
// 如果没有找到现有的 dev 版本,从 1 开始
devVersion = `${baseVersion}-dev.1`;
}
console.log(`📦 Publishing version: ${devVersion} with tag 'dev'`);
// 5. 临时更新 package.json 版本号
const originalPackageJson = { ...packageJson };
packageJson.version = devVersion;
writeFileSync(packagePath, JSON.stringify(packageJson, null, 2));
// 6. 构建项目
console.log('🔨 Building project...');
execSync('npm run build', { stdio: 'inherit' });
// 7. 运行预发布检查
console.log('🔍 Running pre-publish checks...');
execSync('node scripts/prepublish-check.js', { stdio: 'inherit' });
// 8. 发布到 npm 的 dev tag
console.log('📤 Publishing to npm...');
execSync(`npm publish --tag dev --access public`, { stdio: 'inherit' });
// 9. 恢复原始 package.json
writeFileSync(packagePath, JSON.stringify(originalPackageJson, null, 2));
// 10. 创建 git tag
console.log('🏷️ Creating git tag...');
execSync(`git tag -a v${devVersion} -m "Dev release ${devVersion}"`);
execSync(`git push origin v${devVersion}`);
console.log('\n✅ Dev version published successfully!');
console.log(`📦 Version: ${devVersion}`);
console.log(`🔗 Install with: npm install -g @shareai-lab/kode@dev`);
console.log(`🔗 Or: npm install -g @shareai-lab/kode@${devVersion}`);
} catch (error) {
console.error('❌ Dev publish failed:', error.message);
// 尝试恢复 package.json
try {
const packagePath = path.join(process.cwd(), 'package.json');
const packageJson = JSON.parse(readFileSync(packagePath, 'utf8'));
if (packageJson.version.includes('-dev.')) {
// 恢复到基础版本
const baseVersion = packageJson.version.split('-dev.')[0];
packageJson.version = baseVersion;
writeFileSync(packagePath, JSON.stringify(packageJson, null, 2));
console.log('🔄 Restored package.json version');
}
} catch {}
process.exit(1);
}
}
publishDev();

159
scripts/publish-release.js Executable file
View File

@ -0,0 +1,159 @@
#!/usr/bin/env node
const { execSync } = require('child_process');
const { readFileSync, writeFileSync } = require('fs');
const path = require('path');
const readline = require('readline');
/**
* 发布正式版本到 npm
* 使用 latest tag支持语义化版本升级
*/
async function publishRelease() {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const question = (query) => new Promise(resolve => rl.question(query, resolve));
try {
console.log('🚀 Starting production release process...\n');
// 1. 确保在主分支
const currentBranch = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf8' }).trim();
if (currentBranch !== 'main' && currentBranch !== 'master') {
console.log('⚠️ Not on main/master branch. Current branch:', currentBranch);
const proceed = await question('Continue anyway? (y/N): ');
if (proceed.toLowerCase() !== 'y') {
console.log('❌ Cancelled');
process.exit(0);
}
}
// 2. 检查工作区是否干净
try {
execSync('git diff --exit-code', { stdio: 'ignore' });
execSync('git diff --cached --exit-code', { stdio: 'ignore' });
console.log('✅ Working directory is clean');
} catch {
console.log('❌ Working directory has uncommitted changes');
console.log('Please commit or stash your changes before releasing');
process.exit(1);
}
// 3. 拉取最新代码
console.log('📡 Pulling latest changes...');
execSync('git pull origin ' + currentBranch, { stdio: 'inherit' });
// 4. 读取当前版本
const packagePath = path.join(process.cwd(), 'package.json');
const packageJson = JSON.parse(readFileSync(packagePath, 'utf8'));
const currentVersion = packageJson.version;
console.log(`📦 Current version: ${currentVersion}`);
// 5. 选择版本升级类型
console.log('\n🔢 Version bump options:');
const versionParts = currentVersion.split('.');
const major = parseInt(versionParts[0]);
const minor = parseInt(versionParts[1]);
const patch = parseInt(versionParts[2]);
console.log(` 1. patch → ${major}.${minor}.${patch + 1} (bug fixes)`);
console.log(` 2. minor → ${major}.${minor + 1}.0 (new features)`);
console.log(` 3. major → ${major + 1}.0.0 (breaking changes)`);
console.log(` 4. custom → enter custom version`);
const choice = await question('\nSelect version bump (1-4): ');
let newVersion;
switch (choice) {
case '1':
newVersion = `${major}.${minor}.${patch + 1}`;
break;
case '2':
newVersion = `${major}.${minor + 1}.0`;
break;
case '3':
newVersion = `${major + 1}.0.0`;
break;
case '4':
newVersion = await question('Enter custom version: ');
break;
default:
console.log('❌ Invalid choice');
process.exit(1);
}
// 6. 确认发布
console.log(`\n📋 Release Summary:`);
console.log(` Current: ${currentVersion}`);
console.log(` New: ${newVersion}`);
console.log(` Branch: ${currentBranch}`);
console.log(` Tag: latest`);
const confirm = await question('\n🤔 Proceed with release? (y/N): ');
if (confirm.toLowerCase() !== 'y') {
console.log('❌ Cancelled');
process.exit(0);
}
// 7. 更新版本号
console.log('📝 Updating version...');
packageJson.version = newVersion;
writeFileSync(packagePath, JSON.stringify(packageJson, null, 2));
// 8. 运行测试
console.log('🧪 Running tests...');
try {
execSync('npm run typecheck', { stdio: 'inherit' });
execSync('npm test', { stdio: 'inherit' });
} catch (error) {
console.log('❌ Tests failed, rolling back version...');
packageJson.version = currentVersion;
writeFileSync(packagePath, JSON.stringify(packageJson, null, 2));
process.exit(1);
}
// 9. 构建项目
console.log('🔨 Building project...');
execSync('npm run build', { stdio: 'inherit' });
// 10. 运行预发布检查
console.log('🔍 Running pre-publish checks...');
execSync('node scripts/prepublish-check.js', { stdio: 'inherit' });
// 11. 提交版本更新
console.log('📝 Committing version update...');
execSync('git add package.json');
execSync(`git commit -m "chore: bump version to ${newVersion}"`);
// 12. 创建 git tag
console.log('🏷️ Creating git tag...');
execSync(`git tag -a v${newVersion} -m "Release ${newVersion}"`);
// 13. 发布到 npm
console.log('📤 Publishing to npm...');
execSync('npm publish --access public', { stdio: 'inherit' });
// 14. 推送到 git
console.log('📡 Pushing to git...');
execSync(`git push origin ${currentBranch}`);
execSync(`git push origin v${newVersion}`);
console.log('\n🎉 Production release published successfully!');
console.log(`📦 Version: ${newVersion}`);
console.log(`🔗 Install with: npm install -g @shareai-lab/kode`);
console.log(`🔗 Or: npm install -g @shareai-lab/kode@${newVersion}`);
console.log(`📊 View on npm: https://www.npmjs.com/package/@shareai-lab/kode`);
} catch (error) {
console.error('❌ Production release failed:', error.message);
process.exit(1);
} finally {
rl.close();
}
}
publishRelease();

View File

@ -181,9 +181,12 @@ async function showSetupScreens(
grantReadPermissionForOriginalDir()
resolve()
}
render(<TrustDialog onDone={onDone} />, {
exitOnCtrlC: false,
})
;(async () => {
const { render } = await import('ink')
render(<TrustDialog onDone={onDone} />, {
exitOnCtrlC: false,
})
})()
})
}
@ -292,7 +295,10 @@ async function setup(cwd: string, safeMode?: boolean): Promise<void> {
if (autoUpdaterStatus === 'not_configured') {
logEvent('tengu_setup_auto_updater_not_configured', {})
await new Promise<void>(resolve => {
render(<Doctor onDone={() => resolve()} />)
;(async () => {
const { render } = await import('ink')
render(<Doctor onDone={() => resolve()} />)
})()
})
}
}
@ -1260,7 +1266,10 @@ ${commandList}`,
logEvent('tengu_doctor_command', {})
await new Promise<void>(resolve => {
render(<Doctor onDone={() => resolve()} doctorMode={true} />)
;(async () => {
const { render } = await import('ink')
render(<Doctor onDone={() => resolve()} doctorMode={true} />)
})()
})
process.exit(0)
})
@ -1313,11 +1322,14 @@ ${commandList}`,
await setup(cwd, false)
logEvent('tengu_view_logs', { number: number?.toString() ?? '' })
const context: { unmount?: () => void } = {}
const { unmount } = render(
<LogList context={context} type="messages" logNumber={number} />,
renderContextWithExitOnCtrlC,
)
context.unmount = unmount
;(async () => {
const { render } = await import('ink')
const { unmount } = render(
<LogList context={context} type="messages" logNumber={number} />,
renderContextWithExitOnCtrlC,
)
context.unmount = unmount
})()
})
// claude resume
@ -1409,17 +1421,20 @@ ${commandList}`,
} else {
// Show the conversation selector UI
const context: { unmount?: () => void } = {}
const { unmount } = render(
<ResumeConversation
context={context}
commands={commands}
logs={logs}
tools={tools}
verbose={verbose}
/>,
renderContextWithExitOnCtrlC,
)
context.unmount = unmount
;(async () => {
const { render } = await import('ink')
const { unmount } = render(
<ResumeConversation
context={context}
commands={commands}
logs={logs}
tools={tools}
verbose={verbose}
/>,
renderContextWithExitOnCtrlC,
)
context.unmount = unmount
})()
}
})
@ -1439,11 +1454,14 @@ ${commandList}`,
await setup(cwd, false)
logEvent('tengu_view_errors', { number: number?.toString() ?? '' })
const context: { unmount?: () => void } = {}
const { unmount } = render(
<LogList context={context} type="errors" logNumber={number} />,
renderContextWithExitOnCtrlC,
)
context.unmount = unmount
;(async () => {
const { render } = await import('ink')
const { unmount } = render(
<LogList context={context} type="errors" logNumber={number} />,
renderContextWithExitOnCtrlC,
)
context.unmount = unmount
})()
})
// claude context (TODO: deprecate)